diff options
author | Alon Zakai <azakai@google.com> | 2024-11-08 10:16:52 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-08 10:16:52 -0800 |
commit | 8c0429ac09d06d6056687e36fd4fb37f61681233 (patch) | |
tree | c26a80f84cbee89a09f3c4c114180cb8d1cb30df /src/tools/execution-results.h | |
parent | b30067658459ca167e58fe0dee9d85ea6100c223 (diff) | |
download | binaryen-8c0429ac09d06d6056687e36fd4fb37f61681233.tar.gz binaryen-8c0429ac09d06d6056687e36fd4fb37f61681233.tar.bz2 binaryen-8c0429ac09d06d6056687e36fd4fb37f61681233.zip |
[EH] Fuzz calls from JS by calling wasm exports, sometimes catching (#7067)
This adds two new imports to fuzzer modules:
* call-export, which gets an export index and calls it.
* call-export-catch, which does the call in a try-catch, swallowing
any error, and returning 1 if it saw an error.
The former gives us calls back into the wasm, possibly making various
trips between wasm and JS in interesting ways. The latter adds a
try-catch which helps fuzz wasm EH.
We do these calls using a wasm export index, i.e., the index in
the list of exports. This is simple, but it does have the downside that
it makes executing the wasm sensitive to changes in exports (e.g.
wasm-merge adds more), which requires some handling in the fuzzer.
Diffstat (limited to 'src/tools/execution-results.h')
-rw-r--r-- | src/tools/execution-results.h | 50 |
1 files changed, 49 insertions, 1 deletions
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 78cc5af1f..25d0c0772 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -40,10 +40,15 @@ private: // The name of the table exported by the name 'table.' Imports access it. Name exportedTable; + Module& wasm; + + // The ModuleRunner and this ExternalInterface end up needing links both ways, + // so we cannot init this in the constructor. + ModuleRunner* instance = nullptr; public: LoggingExternalInterface(Loggings& loggings, Module& wasm) - : loggings(loggings) { + : loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { exportedTable = exp->value; @@ -99,6 +104,18 @@ public: } tableStore(exportedTable, index, arguments[1]); return {}; + } else if (import->base == "call-export") { + callExport(arguments[0].geti32()); + // Return nothing. If we wanted to return a value we'd need to have + // multiple such functions, one for each signature. + return {}; + } else if (import->base == "call-export-catch") { + try { + callExport(arguments[0].geti32()); + return {Literal(int32_t(0))}; + } catch (const WasmException& e) { + return {Literal(int32_t(1))}; + } } else { WASM_UNREACHABLE("unknown fuzzer import"); } @@ -127,6 +144,35 @@ public: auto payload = std::make_shared<ExnData>("__private", Literals{}); throwException(WasmException{Literal(payload)}); } + + Literals callExport(Index index) { + if (index >= wasm.exports.size()) { + // No export. + throwEmptyException(); + } + auto& exp = wasm.exports[index]; + if (exp->kind != ExternalKind::Function) { + // No callable export. + throwEmptyException(); + } + auto* func = wasm.getFunction(exp->value); + + // TODO JS traps on some types on the boundary, which we should behave the + // same on. For now, this is not needed because the fuzzer will prune all + // non-JS-compatible exports anyhow. + + // Send default values as arguments, or trap if we need anything else. + Literals arguments; + for (const auto& param : func->getParams()) { + if (!param.isDefaultable()) { + throwEmptyException(); + } + arguments.push_back(Literal::makeZero(param)); + } + return instance->callFunction(func->name, arguments); + } + + void setModuleRunner(ModuleRunner* instance_) { instance = instance_; } }; // gets execution results from a wasm module. this is useful for fuzzing @@ -148,6 +194,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + interface.setModuleRunner(&instance); // execute all exported methods (that are therefore preserved through // opts) for (auto& exp : wasm.exports) { @@ -298,6 +345,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + interface.setModuleRunner(&instance); return run(func, wasm, instance); } catch (const TrapException&) { // May throw in instance creation (init of offsets). |