From b538e15548cf3760c71e4b60cf3820c902748c8f Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Thu, 1 Jul 2021 17:03:17 +0430 Subject: [PATCH] LibWasm: Give traps a reason and display it when needed This makes debugging wasm code a bit easier, as we now know what fails instead of just "too bad, something went wrong". --- Tests/LibWasm/test-wasm.cpp | 2 +- .../AbstractMachine/AbstractMachine.cpp | 8 ++-- .../LibWasm/AbstractMachine/AbstractMachine.h | 19 ++++---- .../AbstractMachine/BytecodeInterpreter.cpp | 44 +++++++++---------- .../AbstractMachine/BytecodeInterpreter.h | 14 +++--- .../LibWasm/AbstractMachine/Configuration.cpp | 6 +-- .../LibWasm/AbstractMachine/Interpreter.h | 1 + Userland/Libraries/LibWasm/Constants.h | 2 +- .../LibWeb/WebAssembly/WebAssemblyObject.cpp | 2 +- Userland/Utilities/wasm.cpp | 6 +-- 10 files changed, 54 insertions(+), 50 deletions(-) diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index 24563898c25..f2cea9f057c 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -254,7 +254,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) auto result = WebAssemblyModule::machine().invoke(function_address, arguments); if (result.is_trap()) { - vm.throw_exception(global_object, "Execution trapped"); + vm.throw_exception(global_object, String::formatted("Execution trapped: {}", result.trap().reason)); return {}; } diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 49580e31b15..6a84f52361e 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -136,7 +136,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector(); @@ -270,7 +270,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector values) - : m_values(move(values)) + : m_result(move(values)) { } - Result(Trap) - : m_is_trap(true) + Result(Trap trap) + : m_result(move(trap)) { } - auto& values() const { return m_values; } - auto& values() { return m_values; } - auto is_trap() const { return m_is_trap; } + auto is_trap() const { return m_result.has(); } + auto& values() const { return m_result.get>(); } + auto& values() { return m_result.get>(); } + auto& trap() const { return m_result.get(); } + auto& trap() { return m_result.get(); } private: - Vector m_values; - bool m_is_trap { false }; + Variant, Trap> m_result; }; using ExternValue = Variant; diff --git a/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 3b0d3dd12ce..7244b591c55 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -17,7 +17,7 @@ namespace Wasm { #define TRAP_IF_NOT(x) \ do { \ - if (trap_if_not(x)) { \ + if (trap_if_not(x, #x)) { \ dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ return; \ } \ @@ -25,14 +25,14 @@ namespace Wasm { #define TRAP_IF_NOT_NORETURN(x) \ do { \ - if (trap_if_not(x)) { \ + if (trap_if_not(x, #x)) { \ dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ } \ } while (false) void BytecodeInterpreter::interpret(Configuration& configuration) { - m_do_trap = false; + m_trap.clear(); auto& instructions = configuration.frame().expression().instructions(); auto max_ip_value = InstructionPointer { instructions.size() }; auto& current_ip_value = configuration.ip(); @@ -40,13 +40,13 @@ void BytecodeInterpreter::interpret(Configuration& configuration) while (current_ip_value < max_ip_value) { if (executed_instructions++ >= Constants::max_allowed_executed_instructions_per_call) [[unlikely]] { - m_do_trap = true; + m_trap = Trap { "Exceeded maximum allowed number of instructions" }; return; } auto& instruction = instructions[current_ip_value.value()]; auto old_ip = current_ip_value; interpret(configuration, current_ip_value, instruction); - if (m_do_trap) + if (m_trap.has_value()) return; if (current_ip_value == old_ip) // If no jump occurred ++current_ip_value; @@ -83,7 +83,7 @@ void BytecodeInterpreter::load_and_push(Configuration& configuration, Instructio auto& address = configuration.frame().module().memories().first(); auto memory = configuration.store().get(address); if (!memory) { - m_do_trap = true; + m_trap = Trap { "Nonexistent memory" }; return; } auto& arg = instruction.arguments().get(); @@ -91,12 +91,12 @@ void BytecodeInterpreter::load_and_push(Configuration& configuration, Instructio TRAP_IF_NOT(entry.has()); auto base = entry.get().to(); if (!base.has_value()) { - m_do_trap = true; + m_trap = Trap { "Memory access out of bounds" }; return; } auto instance_address = base.value() + static_cast(arg.offset); if (instance_address < 0 || static_cast(instance_address + sizeof(ReadType)) > memory->size()) { - m_do_trap = true; + m_trap = Trap { "Memory access out of bounds" }; dbgln("LibWasm: Memory access out of bounds (expected 0 <= {} and {} <= {})", instance_address, instance_address + sizeof(ReadType), memory->size()); return; } @@ -117,7 +117,7 @@ void BytecodeInterpreter::store_to_memory(Configuration& configuration, Instruct TRAP_IF_NOT(base.has_value()); auto instance_address = base.value() + static_cast(arg.offset); if (instance_address < 0 || static_cast(instance_address + data.size()) > memory->size()) { - m_do_trap = true; + m_trap = Trap { "Memory access out of bounds" }; dbgln("LibWasm: Memory access out of bounds (expected 0 <= {} and {} <= {})", instance_address, instance_address + data.size(), memory->size()); return; } @@ -146,14 +146,14 @@ void BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd configuration.stack().entries().remove(configuration.stack().size() - span.size(), span.size()); - Result result { Trap {} }; + Result result { Trap { ""sv } }; { CallFrameHandle handle { *this, configuration }; result = configuration.call(*this, address, move(args)); } if (result.is_trap()) { - m_do_trap = true; + m_trap = move(result.trap()); return; } @@ -254,7 +254,7 @@ T BytecodeInterpreter::read_value(ReadonlyBytes data) stream >> value; if (stream.handle_any_error()) { dbgln("Read from {} failed", data.data()); - m_do_trap = true; + m_trap = Trap { "Read from memory failed" }; } return value; } @@ -266,7 +266,7 @@ float BytecodeInterpreter::read_value(ReadonlyBytes data) LittleEndian raw_value; stream >> raw_value; if (stream.handle_any_error()) - m_do_trap = true; + m_trap = Trap { "Read from memory failed" }; return bit_cast(static_cast(raw_value)); } @@ -277,7 +277,7 @@ double BytecodeInterpreter::read_value(ReadonlyBytes data) LittleEndian raw_value; stream >> raw_value; if (stream.handle_any_error()) - m_do_trap = true; + m_trap = Trap { "Read from memory failed" }; return bit_cast(static_cast(raw_value)); } @@ -319,7 +319,7 @@ template MakeSigned BytecodeInterpreter::checked_signed_truncate(V value) { if (isnan(value) || isinf(value)) { // "undefined", let's just trap. - m_do_trap = true; + m_trap = Trap { "Signed truncation undefined behaviour" }; return 0; } @@ -334,7 +334,7 @@ MakeSigned BytecodeInterpreter::checked_signed_truncate(V value) return static_cast(truncated); dbgln_if(WASM_TRACE_DEBUG, "Truncate out of range error"); - m_do_trap = true; + m_trap = Trap { "Signed truncation out of range" }; return true; } @@ -342,7 +342,7 @@ template MakeUnsigned BytecodeInterpreter::checked_unsigned_truncate(V value) { if (isnan(value) || isinf(value)) { // "undefined", let's just trap. - m_do_trap = true; + m_trap = Trap { "Unsigned truncation undefined behaviour" }; return 0; } double truncated; @@ -356,7 +356,7 @@ MakeUnsigned BytecodeInterpreter::checked_unsigned_truncate(V value) return static_cast(truncated); dbgln_if(WASM_TRACE_DEBUG, "Truncate out of range error"); - m_do_trap = true; + m_trap = Trap { "Unsigned truncation out of range" }; return true; } @@ -490,7 +490,7 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi switch (instruction.opcode().value()) { case Instructions::unreachable.value(): - m_do_trap = true; + m_trap = Trap { "Unreachable" }; return; case Instructions::nop.value(): return; @@ -1087,7 +1087,7 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi default: unimplemented:; dbgln("Instruction '{}' not implemented", instruction_name(instruction.opcode())); - m_do_trap = true; + m_trap = Trap { String::formatted("Unimplemented instruction {}", instruction_name(instruction.opcode())) }; return; } } @@ -1097,7 +1097,7 @@ void DebuggerBytecodeInterpreter::interpret(Configuration& configuration, Instru if (pre_interpret_hook) { auto result = pre_interpret_hook(configuration, ip, instruction); if (!result) { - m_do_trap = true; + m_trap = Trap { "Trapped by user request" }; return; } } @@ -1106,7 +1106,7 @@ void DebuggerBytecodeInterpreter::interpret(Configuration& configuration, Instru if (post_interpret_hook) { auto result = post_interpret_hook(configuration, ip, instruction, *this); if (!result) { - m_do_trap = true; + m_trap = Trap { "Trapped by user request" }; return; } } diff --git a/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h b/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h index d494511cfe9..7a917ab6fc5 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h @@ -14,8 +14,9 @@ namespace Wasm { struct BytecodeInterpreter : public Interpreter { virtual void interpret(Configuration&) override; virtual ~BytecodeInterpreter() override = default; - virtual bool did_trap() const override { return m_do_trap; } - virtual void clear_trap() override { m_do_trap = false; } + virtual bool did_trap() const override { return m_trap.has_value(); } + virtual String trap_reason() const override { return m_trap.value().reason; } + virtual void clear_trap() override { m_trap.clear(); } struct CallFrameHandle { explicit CallFrameHandle(BytecodeInterpreter& interpreter, Configuration& configuration) @@ -48,13 +49,14 @@ protected: T read_value(ReadonlyBytes data); Vector pop_values(Configuration& configuration, size_t count); - bool trap_if_not(bool value) + bool trap_if_not(bool value, String reason) { if (!value) - m_do_trap = true; - return m_do_trap; + m_trap = Trap { move(reason) }; + return m_trap.has_value(); } - bool m_do_trap { false }; + + Optional m_trap; }; struct DebuggerBytecodeInterpreter : public BytecodeInterpreter { diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp index 8e93ca5af0e..ed7c925b19a 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp @@ -68,10 +68,10 @@ Result Configuration::execute(Interpreter& interpreter) { interpreter.interpret(*this); if (interpreter.did_trap()) - return Trap {}; + return Trap { interpreter.trap_reason() }; if (stack().size() <= frame().arity() + 1) - return Trap {}; + return Trap { "Not enough values to return from call" }; Vector results; results.ensure_capacity(frame().arity()); @@ -80,7 +80,7 @@ Result Configuration::execute(Interpreter& interpreter) auto label = stack().pop(); // ASSERT: label == current frame if (!label.has