mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 00:50:22 +00:00
LibWasm: Implement module validation
This commit is contained in:
parent
30736c39b9
commit
7d1142e2c8
Notes:
sideshowbarker
2024-07-18 01:17:13 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/7d1142e2c8c Pull-request: https://github.com/SerenityOS/serenity/pull/10797
9 changed files with 3113 additions and 2 deletions
|
@ -442,6 +442,10 @@
|
|||
#cmakedefine01 WASM_TRACE_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef WASM_VALIDATOR_DEBUG
|
||||
#cmakedefine01 WASM_VALIDATOR_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef WEBSERVER_DEBUG
|
||||
#cmakedefine01 WEBSERVER_DEBUG
|
||||
#endif
|
||||
|
|
|
@ -194,6 +194,7 @@ set(WAITBLOCK_DEBUG ON)
|
|||
set(WAITQUEUE_DEBUG ON)
|
||||
set(WASM_BINPARSER_DEBUG ON)
|
||||
set(WASM_TRACE_DEBUG ON)
|
||||
set(WASM_VALIDATOR_DEBUG ON)
|
||||
set(WEBSERVER_DEBUG ON)
|
||||
set(WINDOWMANAGER_DEBUG ON)
|
||||
set(WSMESSAGELOOP_DEBUG ON)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
|
||||
#include <LibWasm/AbstractMachine/Configuration.h>
|
||||
#include <LibWasm/AbstractMachine/Interpreter.h>
|
||||
#include <LibWasm/AbstractMachine/Validator.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
@ -100,8 +101,29 @@ ElementInstance* Store::get(ElementAddress address)
|
|||
return &m_elements[value];
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> AbstractMachine::validate(Module& module)
|
||||
{
|
||||
if (module.validation_status() != Module::ValidationStatus::Unchecked) {
|
||||
if (module.validation_status() == Module::ValidationStatus::Valid)
|
||||
return {};
|
||||
|
||||
return ValidationError { module.validation_error() };
|
||||
}
|
||||
|
||||
auto result = Validator {}.validate(module);
|
||||
if (result.is_error()) {
|
||||
module.set_validation_error(result.error().error_string);
|
||||
return result.release_error();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<ExternValue> externs)
|
||||
{
|
||||
if (auto result = validate(const_cast<Module&>(module)); result.is_error())
|
||||
return InstantiationError { String::formatted("Validation failed: {}", result.error()) };
|
||||
|
||||
auto main_module_instance_pointer = make<ModuleInstance>();
|
||||
auto& main_module_instance = *main_module_instance_pointer;
|
||||
Optional<InstantiationResult> instantiation_result;
|
||||
|
@ -110,8 +132,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
main_module_instance.types() = section.types();
|
||||
});
|
||||
|
||||
// FIXME: Validate stuff
|
||||
|
||||
Vector<Value> global_values;
|
||||
Vector<Vector<Reference>> elements;
|
||||
ModuleInstance auxiliary_instance;
|
||||
|
|
|
@ -373,6 +373,7 @@ public:
|
|||
|
||||
auto is_mutable() const { return m_mutable; }
|
||||
auto& value() const { return m_value; }
|
||||
GlobalType type() const { return { m_value.type(), is_mutable() }; }
|
||||
void set_value(Value value)
|
||||
{
|
||||
VERIFY(is_mutable());
|
||||
|
@ -489,6 +490,8 @@ class AbstractMachine {
|
|||
public:
|
||||
explicit AbstractMachine() = default;
|
||||
|
||||
// Validate a module; permanently sets the module's validity status.
|
||||
ErrorOr<void, ValidationError> validate(Module&);
|
||||
// Load and instantiate a module, and link it into this interpreter.
|
||||
InstantiationResult instantiate(Module const&, Vector<ExternValue>);
|
||||
Result invoke(FunctionAddress, Vector<Value>);
|
||||
|
|
2737
Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp
Normal file
2737
Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp
Normal file
File diff suppressed because it is too large
Load diff
312
Userland/Libraries/LibWasm/AbstractMachine/Validator.h
Normal file
312
Userland/Libraries/LibWasm/AbstractMachine/Validator.h
Normal file
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashTable.h>
|
||||
#include <LibWasm/Forward.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
#if WASM_VALIDATOR_DEBUG
|
||||
# include <AK/SourceLocation.h>
|
||||
#endif
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
struct Context {
|
||||
Vector<FunctionType> types;
|
||||
Vector<FunctionType> functions;
|
||||
Vector<TableType> tables;
|
||||
Vector<MemoryType> memories;
|
||||
Vector<GlobalType> globals;
|
||||
Vector<ValueType> elements;
|
||||
Vector<bool> datas;
|
||||
Vector<ValueType> locals;
|
||||
Vector<ResultType> labels;
|
||||
Optional<ResultType> return_;
|
||||
AK::HashTable<FunctionIndex> references;
|
||||
size_t imported_function_count { 0 };
|
||||
};
|
||||
|
||||
struct ValidationError : public Error {
|
||||
ValidationError(String error)
|
||||
: Error(Error::from_string_literal(error))
|
||||
, error_string(move(error))
|
||||
{
|
||||
}
|
||||
|
||||
String error_string;
|
||||
};
|
||||
|
||||
class Validator {
|
||||
AK_MAKE_NONCOPYABLE(Validator);
|
||||
AK_MAKE_NONMOVABLE(Validator);
|
||||
|
||||
public:
|
||||
Validator() = default;
|
||||
|
||||
[[nodiscard]] Validator fork() const
|
||||
{
|
||||
return Validator { m_context };
|
||||
}
|
||||
|
||||
// Module
|
||||
ErrorOr<void, ValidationError> validate(Module&);
|
||||
ErrorOr<void, ValidationError> validate(ImportSection const&);
|
||||
ErrorOr<void, ValidationError> validate(ExportSection const&);
|
||||
ErrorOr<void, ValidationError> validate(StartSection const&);
|
||||
ErrorOr<void, ValidationError> validate(DataSection const&);
|
||||
ErrorOr<void, ValidationError> validate(ElementSection const&);
|
||||
ErrorOr<void, ValidationError> validate(GlobalSection const&);
|
||||
ErrorOr<void, ValidationError> validate(MemorySection const&);
|
||||
ErrorOr<void, ValidationError> validate(TableSection const&);
|
||||
ErrorOr<void, ValidationError> validate(CodeSection const&);
|
||||
ErrorOr<void, ValidationError> validate(FunctionSection const&) { return {}; }
|
||||
ErrorOr<void, ValidationError> validate(DataCountSection const&) { return {}; }
|
||||
ErrorOr<void, ValidationError> validate(TypeSection const&) { return {}; }
|
||||
ErrorOr<void, ValidationError> validate(CustomSection const&) { return {}; }
|
||||
|
||||
ErrorOr<void, ValidationError> validate(TypeIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.types.size())
|
||||
return {};
|
||||
return Errors::invalid("TypeIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(FunctionIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.functions.size())
|
||||
return {};
|
||||
return Errors::invalid("FunctionIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(MemoryIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.memories.size())
|
||||
return {};
|
||||
return Errors::invalid("MemoryIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(ElementIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.elements.size())
|
||||
return {};
|
||||
return Errors::invalid("ElementIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(DataIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.datas.size())
|
||||
return {};
|
||||
return Errors::invalid("DataIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(GlobalIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.globals.size())
|
||||
return {};
|
||||
return Errors::invalid("GlobalIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(LabelIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.labels.size())
|
||||
return {};
|
||||
return Errors::invalid("LabelIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(LocalIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.locals.size())
|
||||
return {};
|
||||
return Errors::invalid("LocalIndex"sv);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> validate(TableIndex index) const
|
||||
{
|
||||
if (index.value() < m_context.tables.size())
|
||||
return {};
|
||||
return Errors::invalid("TableIndex"sv);
|
||||
}
|
||||
|
||||
// Instructions
|
||||
struct StackEntry {
|
||||
StackEntry(ValueType type)
|
||||
: concrete_type(type)
|
||||
, is_known(true)
|
||||
{
|
||||
}
|
||||
|
||||
explicit StackEntry()
|
||||
: concrete_type(ValueType::I32)
|
||||
, is_known(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_of_kind(ValueType::Kind kind) const
|
||||
{
|
||||
if (is_known)
|
||||
return concrete_type.kind() == kind;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_numeric() const { return !is_known || concrete_type.is_numeric(); }
|
||||
bool is_reference() const { return !is_known || concrete_type.is_reference(); }
|
||||
|
||||
bool operator==(ValueType const& other) const
|
||||
{
|
||||
if (is_known)
|
||||
return concrete_type == other;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator==(StackEntry const& other) const
|
||||
{
|
||||
if (is_known && other.is_known)
|
||||
return other.concrete_type == concrete_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
ValueType concrete_type;
|
||||
bool is_known { true };
|
||||
};
|
||||
|
||||
// This is a wrapper that can model "polymorphic" stacks,
|
||||
// by treating unknown stack entries as a potentially infinite number of entries
|
||||
class Stack : private Vector<StackEntry> {
|
||||
public:
|
||||
// The unknown entry will never be popped off, so we can safely use the original `is_empty`.
|
||||
using Vector<StackEntry>::is_empty;
|
||||
using Vector<StackEntry>::last;
|
||||
using Vector<StackEntry>::at;
|
||||
|
||||
StackEntry take_last()
|
||||
{
|
||||
if (last().is_known)
|
||||
return Vector<StackEntry>::take_last();
|
||||
return last();
|
||||
}
|
||||
void append(StackEntry entry)
|
||||
{
|
||||
if (!entry.is_known)
|
||||
m_did_insert_unknown_entry = true;
|
||||
Vector<StackEntry>::append(entry);
|
||||
}
|
||||
|
||||
size_t actual_size() const { return Vector<StackEntry>::size(); }
|
||||
size_t size() const { return m_did_insert_unknown_entry ? static_cast<size_t>(-1) : actual_size(); }
|
||||
|
||||
Vector<StackEntry> release_vector() { return exchange(static_cast<Vector<StackEntry>&>(*this), Vector<StackEntry> {}); }
|
||||
|
||||
bool operator==(Stack const& other) const;
|
||||
|
||||
private:
|
||||
bool m_did_insert_unknown_entry { false };
|
||||
};
|
||||
|
||||
struct ExpressionTypeResult {
|
||||
Vector<StackEntry> result_types;
|
||||
bool is_constant { false };
|
||||
};
|
||||
ErrorOr<ExpressionTypeResult, ValidationError> validate(Expression const&, Vector<ValueType> const&);
|
||||
ErrorOr<void, ValidationError> validate(Instruction const& instruction, Stack& stack, bool& is_constant);
|
||||
template<u32 opcode>
|
||||
ErrorOr<void, ValidationError> validate_instruction(Instruction const&, Stack& stack, bool& is_constant);
|
||||
|
||||
// Types
|
||||
bool type_is_subtype_of(ValueType const& candidate_subtype, ValueType const& candidate_supertype);
|
||||
ErrorOr<void, ValidationError> validate(Limits const&, size_t k); // n <= 2^k-1 && m? <= 2^k-1
|
||||
ErrorOr<FunctionType, ValidationError> validate(BlockType const&);
|
||||
ErrorOr<void, ValidationError> validate(FunctionType const&) { return {}; }
|
||||
ErrorOr<void, ValidationError> validate(TableType const&);
|
||||
ErrorOr<void, ValidationError> validate(MemoryType const&);
|
||||
ErrorOr<void, ValidationError> validate(GlobalType const&) { return {}; }
|
||||
|
||||
private:
|
||||
explicit Validator(Context context)
|
||||
: m_context(move(context))
|
||||
{
|
||||
}
|
||||
|
||||
struct Errors {
|
||||
static ValidationError invalid(StringView name) { return String::formatted("Invalid {}", name); }
|
||||
|
||||
template<typename Expected, typename Given>
|
||||
static ValidationError invalid(StringView name, Expected expected, Given given)
|
||||
{
|
||||
return String::formatted("Invalid {}, expected {} but got {}", name, expected, given);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static ValidationError non_conforming_types(StringView name, Args... args)
|
||||
{
|
||||
return String::formatted("Non-conforming types for {}: {}", name, Vector { args... });
|
||||
}
|
||||
|
||||
static ValidationError duplicate_export_name(StringView name) { return String::formatted("Duplicate exported name '{}'", name); }
|
||||
|
||||
template<typename T, typename U, typename V>
|
||||
static ValidationError out_of_bounds(StringView name, V value, T min, U max) { return String::formatted("Value {} for {} is out of bounds ({},{})", value, name, min, max); }
|
||||
|
||||
#if WASM_VALIDATOR_DEBUG
|
||||
static ValidationError invalid_stack_state(SourceLocation location = SourceLocation::current());
|
||||
#else
|
||||
static ValidationError invalid_stack_state();
|
||||
#endif
|
||||
};
|
||||
|
||||
enum class ChildScopeKind {
|
||||
Block,
|
||||
Loop,
|
||||
IfWithoutElse,
|
||||
IfWithElse,
|
||||
Else,
|
||||
};
|
||||
|
||||
struct BlockDetails {
|
||||
size_t initial_stack_size { 0 };
|
||||
struct IfDetails {
|
||||
Stack initial_stack;
|
||||
Stack true_branch_stack;
|
||||
};
|
||||
Variant<IfDetails, Empty> details;
|
||||
};
|
||||
|
||||
Context m_context;
|
||||
Vector<Context> m_parent_contexts;
|
||||
Vector<ChildScopeKind> m_entered_scopes;
|
||||
Vector<BlockDetails> m_block_details;
|
||||
Vector<FunctionType> m_entered_blocks;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Wasm::Validator::StackEntry> : public AK::Formatter<StringView> {
|
||||
void format(FormatBuilder& builder, Wasm::Validator::StackEntry const& value)
|
||||
{
|
||||
if (value.is_known)
|
||||
return Formatter<StringView>::format(builder, Wasm::ValueType::kind_name(value.concrete_type.kind()));
|
||||
|
||||
Formatter<StringView>::format(builder, "<unknown>"sv);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Wasm::ValueType> : public AK::Formatter<StringView> {
|
||||
void format(FormatBuilder& builder, Wasm::ValueType const& value)
|
||||
{
|
||||
Formatter<StringView>::format(builder, Wasm::ValueType::kind_name(value.kind()));
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Wasm::ValidationError> : public AK::Formatter<StringView> {
|
||||
void format(FormatBuilder& builder, Wasm::ValidationError const& error)
|
||||
{
|
||||
Formatter<StringView>::format(builder, error.error_string);
|
||||
}
|
||||
};
|
|
@ -2,6 +2,7 @@ set(SOURCES
|
|||
AbstractMachine/AbstractMachine.cpp
|
||||
AbstractMachine/BytecodeInterpreter.cpp
|
||||
AbstractMachine/Configuration.cpp
|
||||
AbstractMachine/Validator.cpp
|
||||
Parser/Parser.cpp
|
||||
Printer/Printer.cpp
|
||||
)
|
||||
|
|
15
Userland/Libraries/LibWasm/Forward.h
Normal file
15
Userland/Libraries/LibWasm/Forward.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
class AbstractMachine;
|
||||
class Validator;
|
||||
struct ValidationError;
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/DistinctNumeric.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
|
@ -14,6 +15,7 @@
|
|||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibWasm/Constants.h>
|
||||
#include <LibWasm/Forward.h>
|
||||
#include <LibWasm/Opcode.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
@ -180,6 +182,8 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
bool operator==(ValueType const&) const = default;
|
||||
|
||||
auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference || m_kind == NullExternReference || m_kind == NullFunctionReference; }
|
||||
auto is_numeric() const { return !is_reference(); }
|
||||
auto kind() const { return m_kind; }
|
||||
|
@ -953,6 +957,12 @@ private:
|
|||
|
||||
class Module {
|
||||
public:
|
||||
enum class ValidationStatus {
|
||||
Unchecked,
|
||||
Invalid,
|
||||
Valid,
|
||||
};
|
||||
|
||||
class Function {
|
||||
public:
|
||||
explicit Function(TypeIndex type, Vector<ValueType> local_types, Expression body)
|
||||
|
@ -1026,12 +1036,20 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void set_validation_status(ValidationStatus status, Badge<Validator>) { set_validation_status(status); }
|
||||
ValidationStatus validation_status() const { return m_validation_status; }
|
||||
StringView validation_error() const { return *m_validation_error; }
|
||||
void set_validation_error(String error) { m_validation_error = move(error); }
|
||||
|
||||
static ParseResult<Module> parse(InputStream& stream);
|
||||
|
||||
private:
|
||||
void populate_sections();
|
||||
void set_validation_status(ValidationStatus status) { m_validation_status = status; }
|
||||
|
||||
Vector<AnySection> m_sections;
|
||||
Vector<Function> m_functions;
|
||||
ValidationStatus m_validation_status { ValidationStatus::Unchecked };
|
||||
Optional<String> m_validation_error;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue