diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 4173058abf8..8f11b3afe99 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -658,10 +658,20 @@ def filter_known_issues(output): ret = run_unchecked(cmd) return filter_known_issues(ret) except subprocess.CalledProcessError: - # other known issues do make it fail, so re-run without checking for + # Other known issues do make it fail, so re-run without checking for # success and see if we should ignore it - if filter_known_issues(run_unchecked(cmd)) == IGNORE: + raw = run_unchecked(cmd) + filtered = filter_known_issues(raw) + if filtered == IGNORE: return IGNORE + + # If we trap during instantiation, we do not need to ignore this run + # (we can see that all VMs have the same behavior), but we do need to + # not raise an error here. + if INSTANTIATE_ERROR in raw: + return INSTANTIATE_ERROR + + # Otherwise, raise an error. raise @@ -1111,9 +1121,11 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): run([in_bin('wasm-opt'), before_wasm_temp, '-o', before_wasm_temp] + simplification_passes + FEATURE_OPTS) # now that the before wasm is fixed up, generate a proper after wasm run([in_bin('wasm-opt'), before_wasm_temp, '-o', after_wasm_temp] + opts + FEATURE_OPTS) - # always check for compiler crashes + + # run before and after before = self.run(before_wasm_temp) after = self.run(after_wasm_temp) + if NANS: # with NaNs we can't compare the output, as a reinterpret through # memory might end up different in JS than wasm @@ -1491,8 +1503,13 @@ def traps_in_instantiation(output): if trap_index == -1: # In "fixed" output, traps are replaced with *exception*. trap_index = output.find('*exception*') - if trap_index == -1: - return False + # An exception can occur during the start function. + exception_index = output.find(EXCEPTION_PREFIX) + # Look at the first of a trap or an exception. + if exception_index >= 0 and (trap_index == -1 or exception_index < trap_index): + trap_index = exception_index + if trap_index == -1: + return False export_index = output.find(FUZZ_EXEC_EXPORT_PREFIX) if export_index == -1: return True diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index d16c49624b9..aa10f92c6c1 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -515,8 +515,7 @@ function build(binary, isSecond) { try { instance = new WebAssembly.Instance(module, imports); } catch (e) { - console.log('exception thrown: failed to instantiate module: ' + e); - quit(); + throw new Error('exception thrown: failed to instantiate module: ' + e); } // Do not add the second instance's exports to the list, as that would be diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 803e13d5d0b..0f6ac640203 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -605,6 +605,11 @@ class TranslateToFuzzReader { // Checks if a function is a callRef* import (call-ref or call-ref-catch). bool isCallRefImport(Name func); + // Pick a start function. + Name pickStart(); + // Fix the start function so it is valid for the fuzzer. + void fixStart(); + // statistical distributions // 0 to the limit, logarithmic scale diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 759061da88f..98ee95e044c 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -15,6 +15,7 @@ */ #include "tools/fuzzing.h" +#include "ir/eh-utils.h" #include "ir/gc-type-utils.h" #include "ir/glbs.h" #include "ir/iteration.h" @@ -1672,6 +1673,23 @@ void TranslateToFuzzReader::processFunctions() { } } + // Decide what to do with the start function. Most of the time we remove it, + // as that is the least risky for fuzzing (any trap in the start will make + // the entire module not execute), but other cases are important too. + switch (upTo(10)) { + case 0: + // Do not modify the start, potentially leaving the existing one. + break; + case 1: + // Pick a new start. + wasm.start = pickStart(); + break; + default: + // Remove it. + wasm.start = Name(); + } + fixStart(); + // At the very end, add hang limit checks (so no modding can override them). if (fuzzParams->HANG_LIMIT > 0) { for (auto& func : wasm.functions) { @@ -2395,14 +2413,6 @@ void TranslateToFuzzReader::modifyInitialFunctions() { func->body = make(func->getResults()); } } - - // Remove a start function - the fuzzing harness expects code to run only - // from exports. When preserving imports and exports, however, we need to - // keep any start method, as it may be important to keep the contract between - // the wasm and the outside. - if (!preserveImportsAndExports) { - wasm.start = Name(); - } } void TranslateToFuzzReader::mutateJSBoundary() { @@ -6720,4 +6730,65 @@ bool TranslateToFuzzReader::isCallRefImport(Name target) { func->base.startsWith("call-ref"); } +Name TranslateToFuzzReader::pickStart() { + // Any none-none function is an option. + std::vector options; + for (auto& func : wasm.functions) { + // Avoid imports, see fixStart(). + if (func->imported()) { + continue; + } + if (func->getParams() == Type::none && func->getResults() == Type::none) { + options.push_back(func->name); + } + } + return options.empty() ? Name() : pick(options); +} + +void TranslateToFuzzReader::fixStart() { + if (!wasm.start) { + return; + } + + // Fuzzing with a start function is tricky, as if it calls imports that could + // cause reentrancy issues (the module is not yet returned to the JS/VM side, + // yet, so things are not ready for calls to happen yet). Still, fuzzing the + // start is important, so just remove all calls, which still leaves a chance + // for interesting things like modifying globals and memory etc. + // TODO: While confusing in VMs, fuzzing this might be useful there, but we + // need to fix our own interpreter to not crash. + + auto* start = wasm.getFunction(wasm.start); + // We definitely can't call an import here in the fuzzer, for the above + // reasons, and should have only chosen something valid. + assert(!start->imported()); + + struct Fixer : public PostWalker { + Module& wasm; + TranslateToFuzzReader& parent; + + Fixer(Module& wasm, TranslateToFuzzReader& parent) + : wasm(wasm), parent(parent) {} + + void visitCall(Call* curr) { replace(); } + void visitCallIndirect(CallIndirect* curr) { replace(); } + void visitCallRef(CallRef* curr) { replace(); } + + void replace() { + std::vector list; + for (auto* child : ChildIterator(getCurrent())) { + list.push_back(parent.builder.makeDrop(child)); + } + list.push_back(parent.makeTrivial(getCurrent()->type)); + replaceCurrent(parent.builder.makeBlock(list)); + } + } fixer(wasm, *this); + + FunctionCreationContext context(*this, start); + fixer.walk(start->body); + + // Added blocks may require fixups. + EHUtils::handleBlockNestedPops(start, wasm); +} + } // namespace wasm