From d2615d99e6995b7d8ffd325c652b178003b142a5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:02:19 -0700 Subject: [PATCH 01/23] go --- src/tools/fuzzing.h | 5 +++ src/tools/fuzzing/fuzzing.cpp | 67 ++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index e06160332b0..58c46a01331 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -602,6 +602,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(Name name); + // statistical distributions // 0 to the limit, logarithmic scale diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 7dc4a9051f7..af04dcba115 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1661,6 +1661,23 @@ void TranslateToFuzzReader::processFunctions() { } } + // Decide what to do with the start function. + switch (upTo(3)) { + case 0: + // Do not modify the start. + break; + case 1: + // Remove it. + wasm.start = Name(); + case 2: + // Pick it. + wasm.start = pickStart(); + break; + } + if (wasm.start) { + fixStart(wasm.start); + } + // At the very end, add hang limit checks (so no modding can override them). if (fuzzParams->HANG_LIMIT > 0) { for (auto& func : wasm.functions) { @@ -2384,14 +2401,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() { @@ -6709,4 +6718,46 @@ 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) { + if (func->getParams() == Type::none && func->getResults() == Type::none) { + options.push_back(func->name); + } + } + return options.empty() ? Name() : pick(options); +} + +void TranslateToFuzzReader::fixStart(Name name) { + // Fuzz the start function. This 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. + 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() { + replaceCurrent(parent.makeTrivial(getCurrent()->type)); + } + }; + Fixer fixer(wasm, *this); + fixer.walk(func->body); +} + } // namespace wasm From f8d9cf84ab897a57699daeeee7e2951f6dd36f05 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:02:27 -0700 Subject: [PATCH 02/23] fmrt --- src/tools/fuzzing/fuzzing.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index af04dcba115..113e6c615e1 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -6742,19 +6742,11 @@ void TranslateToFuzzReader::fixStart(Name name) { 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 visitCall(Call* curr) { replace(); } + void visitCallIndirect(CallIndirect* curr) { replace(); } + void visitCallRef(CallRef* curr) { replace(); } - void replace() { - replaceCurrent(parent.makeTrivial(getCurrent()->type)); - } + void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } }; Fixer fixer(wasm, *this); fixer.walk(func->body); From a8fff044f375e10f2f03c3fe665443cba49df8c2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:03:58 -0700 Subject: [PATCH 03/23] fix --- src/tools/fuzzing/fuzzing.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 113e6c615e1..141b743e157 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1669,6 +1669,7 @@ void TranslateToFuzzReader::processFunctions() { case 1: // Remove it. wasm.start = Name(); + break; case 2: // Pick it. wasm.start = pickStart(); @@ -6730,7 +6731,11 @@ Name TranslateToFuzzReader::pickStart() { } void TranslateToFuzzReader::fixStart(Name name) { - // Fuzz the start function. This is tricky, as if it calls imports that could + if (!name) { + 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 @@ -6747,9 +6752,8 @@ void TranslateToFuzzReader::fixStart(Name name) { void visitCallRef(CallRef* curr) { replace(); } void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } - }; - Fixer fixer(wasm, *this); - fixer.walk(func->body); + } fixer(wasm, *this); + fixer.walk(wasm.getFunction(name)->body); } } // namespace wasm From d1a7722283eb3e9a0194d093e90ebab3be036b3b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:10:56 -0700 Subject: [PATCH 04/23] fix --- src/tools/fuzzing.h | 2 +- src/tools/fuzzing/fuzzing.cpp | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 58c46a01331..adc755116a1 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -605,7 +605,7 @@ class TranslateToFuzzReader { // Pick a start function. Name pickStart(); // Fix the start function so it is valid for the fuzzer. - void fixStart(Name name); + void fixStart(); // statistical distributions diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 141b743e157..a21809c7130 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1675,9 +1675,7 @@ void TranslateToFuzzReader::processFunctions() { wasm.start = pickStart(); break; } - if (wasm.start) { - fixStart(wasm.start); - } + fixStart(wasm.start); // At the very end, add hang limit checks (so no modding can override them). if (fuzzParams->HANG_LIMIT > 0) { @@ -6730,8 +6728,8 @@ Name TranslateToFuzzReader::pickStart() { return options.empty() ? Name() : pick(options); } -void TranslateToFuzzReader::fixStart(Name name) { - if (!name) { +void TranslateToFuzzReader::fixStart() { + if (!wasm.start) { return; } @@ -6740,6 +6738,15 @@ void TranslateToFuzzReader::fixStart(Name name) { // 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. + + auto* start = wasm.getFunction(wasm.start); + if (start->imported()) { + // We definitely can't call an import here in the fuzzer, for the above + // reasons. + wasm.start = Name(); + return; + } + struct Fixer : public PostWalker { Module& wasm; TranslateToFuzzReader& parent; @@ -6753,7 +6760,7 @@ void TranslateToFuzzReader::fixStart(Name name) { void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } } fixer(wasm, *this); - fixer.walk(wasm.getFunction(name)->body); + fixer.walk(start->body); } } // namespace wasm From dc8b327c222161e12691e30e7cedaf00bbc83248 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:11:10 -0700 Subject: [PATCH 05/23] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a21809c7130..45e6ffecab5 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1675,7 +1675,7 @@ void TranslateToFuzzReader::processFunctions() { wasm.start = pickStart(); break; } - fixStart(wasm.start); + fixStart(); // At the very end, add hang limit checks (so no modding can override them). if (fuzzParams->HANG_LIMIT > 0) { From eb6b0a7f518ca21c7262113f4f29875102ee217a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:21:44 -0700 Subject: [PATCH 06/23] fix --- src/tools/fuzzing/fuzzing.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 45e6ffecab5..7e9a8dcd076 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -6760,6 +6760,8 @@ void TranslateToFuzzReader::fixStart() { void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } } fixer(wasm, *this); + + FunctionCreationContext context(*this, start); fixer.walk(start->body); } From 7b55ead4c8c9d0aeb0bdcdddf5f9152eb8a22f1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:35:44 -0700 Subject: [PATCH 07/23] fix --- src/tools/fuzzing/fuzzing.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 7e9a8dcd076..b04977ac5f7 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -6758,7 +6758,14 @@ void TranslateToFuzzReader::fixStart() { void visitCallIndirect(CallIndirect* curr) { replace(); } void visitCallRef(CallRef* curr) { replace(); } - void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } + 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); From c02bb91888e8269dca08619ac5e8a1d67c654c51 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:46:06 -0700 Subject: [PATCH 08/23] fix --- src/tools/fuzzing/fuzzing.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b04977ac5f7..05bc0b05251 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" @@ -6770,6 +6771,9 @@ void TranslateToFuzzReader::fixStart() { FunctionCreationContext context(*this, start); fixer.walk(start->body); + + // Added blocks may require fixups. + EHUtils::handleBlockNestedPops(func, *getModule()) } } // namespace wasm From a52568a1bc5a8481f396b7bf47c03b044b79f032 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:47:01 -0700 Subject: [PATCH 09/23] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 05bc0b05251..a94a34c5aa8 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -6773,7 +6773,7 @@ void TranslateToFuzzReader::fixStart() { fixer.walk(start->body); // Added blocks may require fixups. - EHUtils::handleBlockNestedPops(func, *getModule()) + EHUtils::handleBlockNestedPops(func, wasm); } } // namespace wasm From b6c117156d88250241548ae25fdc33304f0a85c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 11:47:17 -0700 Subject: [PATCH 10/23] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a94a34c5aa8..43a3f16dedd 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -6773,7 +6773,7 @@ void TranslateToFuzzReader::fixStart() { fixer.walk(start->body); // Added blocks may require fixups. - EHUtils::handleBlockNestedPops(func, wasm); + EHUtils::handleBlockNestedPops(start, wasm); } } // namespace wasm From 7b4a11bc024ed3313af765123ad60bd38bad5498 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 13:05:31 -0700 Subject: [PATCH 11/23] fix --- src/tools/fuzzing/fuzzing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 43a3f16dedd..b3d014fbdde 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -4152,8 +4152,8 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { case HeapType::func: { // Rarely, emit a call to imported table.get (when nullable, unshared, and // where we can emit a call). - if (type.isNullable() && share == Unshared && funcContext && - tableGetImportName && !oneIn(3)) { + if (!trivialNesting && type.isNullable() && share == Unshared && + funcContext && tableGetImportName && !oneIn(3)) { return makeImportTableGet(); } return makeRefFuncConst(type); From 4dde53b913a59c7f4387ef815bd67dc11d50c39e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 May 2026 16:14:22 -0700 Subject: [PATCH 12/23] fix --- scripts/fuzz_opt.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index c1a4d02c6d5..23007e9a85a 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1491,8 +1491,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 From 684b058d01a5cecc57beb144825eca1bf877b173 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 11:54:29 -0700 Subject: [PATCH 13/23] fix --- scripts/fuzz_opt.py | 10 +++++++++- scripts/fuzz_shell.js | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 23007e9a85a..41aab3769f9 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1167,7 +1167,15 @@ def fix_number(x): # a floating-point result that may need to be fixed up return re.sub(r' => (-?[\d+-.e\-+]+)', fix_number, x) - before = fix_output_for_js(before) + try: + before = fix_output_for_js(before) + except Exception as e: + # If the module traps during instantiation, there is nothing + # important to test here. + if INSTANTIATE_ERROR in str(e): + note_ignored_vm_run('wasm2js instantiation trap') + return + after = fix_output_for_js(after) # we must not compare if the wasm hits a trap, as wasm2js does not 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 From 5f08a1f561da4aefb10a9a6a1bcc5dea77d4d9d2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 11:57:46 -0700 Subject: [PATCH 14/23] fix --- scripts/fuzz_opt.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 41aab3769f9..3a15a12c6b0 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1111,9 +1111,19 @@ 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 - before = self.run(before_wasm_temp) + + # run before and after + try: + before = self.run(before_wasm_temp) + except Exception as e: + # If the module traps during instantiation, there is nothing + # important to test here. + if INSTANTIATE_ERROR in str(e): + note_ignored_vm_run('wasm2js instantiation trap') + return + 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 @@ -1167,15 +1177,7 @@ def fix_number(x): # a floating-point result that may need to be fixed up return re.sub(r' => (-?[\d+-.e\-+]+)', fix_number, x) - try: - before = fix_output_for_js(before) - except Exception as e: - # If the module traps during instantiation, there is nothing - # important to test here. - if INSTANTIATE_ERROR in str(e): - note_ignored_vm_run('wasm2js instantiation trap') - return - + before = fix_output_for_js(before) after = fix_output_for_js(after) # we must not compare if the wasm hits a trap, as wasm2js does not From 4a1a9a05b485898339bff3ec1b27ac463f3a76a3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 12:01:02 -0700 Subject: [PATCH 15/23] fix --- scripts/fuzz_opt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 3a15a12c6b0..8924dfcc821 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1115,10 +1115,11 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): # run before and after try: before = self.run(before_wasm_temp) - except Exception as e: + except: # If the module traps during instantiation, there is nothing # important to test here. - if INSTANTIATE_ERROR in str(e): + error = self.run(before_wasm_temp, checked=False) + if INSTANTIATE_ERROR in error: note_ignored_vm_run('wasm2js instantiation trap') return From b5bed35f41cca51a8a6bf4687945217579d448c1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 12:01:40 -0700 Subject: [PATCH 16/23] fix --- scripts/fuzz_opt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 8924dfcc821..df7a7ae22de 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1209,7 +1209,7 @@ def fix_number(x): compare_between_vms(before, interpreter, 'Wasm2JS (vs interpreter)') @override - def run(self, wasm): + def run(self, wasm, checked=True): with open(get_fuzz_shell_js()) as f: wrapper = f.read() cmd = [in_bin('wasm2js'), wasm, '--emscripten'] @@ -1233,7 +1233,7 @@ def run(self, wasm): f.write(glue) f.write(main) f.write(wrapper) - return run_vm([shared.NODEJS, js_file, abspath('a.wasm')]) + return run_vm([shared.NODEJS, js_file, abspath('a.wasm')], checked) @override def can_run_on_wasm(self, wasm): From c62d304b67a253c465d0de3ebcd2cd123eaed5af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 13:03:04 -0700 Subject: [PATCH 17/23] go --- src/tools/fuzzing/fuzzing.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b3d014fbdde..d24daab82e5 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1662,19 +1662,20 @@ void TranslateToFuzzReader::processFunctions() { } } - // Decide what to do with the start function. - switch (upTo(3)) { + // 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. + switch (upTo(3)) { // TODO 10 case 0: - // Do not modify the start. + // Do not modify the start, potentially leaving the existing one. break; case 1: - // Remove it. - wasm.start = Name(); - break; - case 2: - // Pick it. + // Pick a new start. wasm.start = pickStart(); break; + default: + // Remove it. + wasm.start = Name(); } fixStart(); From 07c2def63db5bbcd0a0013119e5c515c93982bad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 14:23:54 -0700 Subject: [PATCH 18/23] fix --- scripts/fuzz_opt.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index df7a7ae22de..75508980c50 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -658,10 +658,19 @@ 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) + if filter_known_issues(raw) == 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 + + # Otherwise, raise an error. raise @@ -1113,16 +1122,7 @@ def handle_pair(self, input, before_wasm, after_wasm, opts): run([in_bin('wasm-opt'), before_wasm_temp, '-o', after_wasm_temp] + opts + FEATURE_OPTS) # run before and after - try: - before = self.run(before_wasm_temp) - except: - # If the module traps during instantiation, there is nothing - # important to test here. - error = self.run(before_wasm_temp, checked=False) - if INSTANTIATE_ERROR in error: - note_ignored_vm_run('wasm2js instantiation trap') - return - + before = self.run(before_wasm_temp) after = self.run(after_wasm_temp) if NANS: @@ -1209,7 +1209,7 @@ def fix_number(x): compare_between_vms(before, interpreter, 'Wasm2JS (vs interpreter)') @override - def run(self, wasm, checked=True): + def run(self, wasm): with open(get_fuzz_shell_js()) as f: wrapper = f.read() cmd = [in_bin('wasm2js'), wasm, '--emscripten'] @@ -1233,7 +1233,7 @@ def run(self, wasm, checked=True): f.write(glue) f.write(main) f.write(wrapper) - return run_vm([shared.NODEJS, js_file, abspath('a.wasm')], checked) + return run_vm([shared.NODEJS, js_file, abspath('a.wasm')]) @override def can_run_on_wasm(self, wasm): From 1e1b50084a760b883248b1761937b0b681072667 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 15:18:33 -0700 Subject: [PATCH 19/23] fix --- scripts/fuzz_opt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 75508980c50..2b8abc6c166 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -661,14 +661,15 @@ def filter_known_issues(output): # Other known issues do make it fail, so re-run without checking for # success and see if we should ignore it raw = run_unchecked(cmd) - if filter_known_issues(raw) == IGNORE: + filtered = filter_known_issues + 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 + return filtered # Otherwise, raise an error. raise From 46204f88e9aa5477892084b02dc81a19e727f134 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 15:21:33 -0700 Subject: [PATCH 20/23] fix --- scripts/fuzz_opt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 2b8abc6c166..8bc699babb4 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -661,13 +661,13 @@ def filter_known_issues(output): # Other known issues do make it fail, so re-run without checking for # success and see if we should ignore it raw = run_unchecked(cmd) - filtered = filter_known_issues + 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 + # not raise an error here. if INSTANTIATE_ERROR in raw: return filtered From 9d2c970688cc998220322eee6ada0b59d83122aa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 16:20:39 -0700 Subject: [PATCH 21/23] fix --- scripts/fuzz_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 8bc699babb4..92248468e37 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -669,7 +669,7 @@ def filter_known_issues(output): # (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 filtered + return INSTANTIATE_ERROR # Otherwise, raise an error. raise From 2edd71ac43ac06a6e631b5c18165f96185b417ed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 20 May 2026 12:31:07 -0700 Subject: [PATCH 22/23] 10 --- src/tools/fuzzing/fuzzing.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 266cc334e13..71702a62ea3 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1674,9 +1674,9 @@ 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. - switch (upTo(3)) { // TODO 10 + // 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; From 11880d4fab17d1038e8abfa5e111bc66bca42e38 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 20 May 2026 16:39:00 -0700 Subject: [PATCH 23/23] filter imports earlier --- src/tools/fuzzing/fuzzing.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 71702a62ea3..98ee95e044c 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -6734,6 +6734,10 @@ 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); } @@ -6751,14 +6755,13 @@ void TranslateToFuzzReader::fixStart() { // 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); - if (start->imported()) { - // We definitely can't call an import here in the fuzzer, for the above - // reasons. - wasm.start = Name(); - return; - } + // 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;