From bb6f9299c70ec838c69276d9bdb8f5a84652abdd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 20 May 2026 17:17:37 -0700 Subject: [PATCH 1/3] Fix ContinuationStore desynchronization When a primary module execution traps or suspends to the host, its continuation store is cleared. Previously, this was done by reassigning the shared_ptr to a new ContinuationStore instance. However, secondary (linked) modules that were instantiated prior to this still hold the original shared_ptr to the old ContinuationStore. This led to desynchronization, where the secondary module would run with stale continuation state (including leaked continuations and resuming flags), eventually causing crashes like assertion failures in visitSuspend. This fix changes clearContinuationStore to clear the ContinuationStore in-place (clearing the continuations vector and resetting resuming flag) instead of reassigning the shared_ptr, ensuring all linked modules continue to share the same cleared state. Added a lit test to verify the fix and prevent regression. --- src/wasm-interpreter.h | 3 ++- test/lit/exec/continuation-leak.wast | 26 +++++++++++++++++++++ test/lit/exec/continuation-leak.wast.second | 7 ++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/lit/exec/continuation-leak.wast create mode 100644 test/lit/exec/continuation-leak.wast.second diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 447c22497d3..463b505f3cf 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -537,7 +537,8 @@ class ExpressionRunner : public OverriddenVisitor { #if WASM_INTERPRETER_DEBUG std::cout << indent() << "clear continuations\n"; #endif - continuationStore = std::make_shared(); + continuationStore->continuations.clear(); + continuationStore->resuming = false; } } diff --git a/test/lit/exec/continuation-leak.wast b/test/lit/exec/continuation-leak.wast new file mode 100644 index 00000000000..25ef7caa505 --- /dev/null +++ b/test/lit/exec/continuation-leak.wast @@ -0,0 +1,26 @@ +;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s + +;; Check that clearing the continuation store in linked modules clears it in-place, +;; so that continuations leaked from the primary module do not affect the second module. + +(module + (type $func_t (func)) + (type $cont_t (cont $func_t)) + (tag $tag) + + (func $f_suspend + (suspend $tag) + ) + + (func $test1 (export "test1") + (local $c (ref $cont_t)) + (local.set $c (cont.new $cont_t (ref.func $f_suspend))) + (resume $cont_t (local.get $c)) + ) +) + +;; CHECK: [fuzz-exec] export test1 +;; CHECK-NEXT: [exception thrown: unhandled suspend] +;; CHECK: [fuzz-exec] running second module +;; CHECK-NEXT: [fuzz-exec] export test2 +;; CHECK-NEXT: [exception thrown: unhandled suspend] diff --git a/test/lit/exec/continuation-leak.wast.second b/test/lit/exec/continuation-leak.wast.second new file mode 100644 index 00000000000..0b9851e2874 --- /dev/null +++ b/test/lit/exec/continuation-leak.wast.second @@ -0,0 +1,7 @@ +(module + (tag $tag) + + (func $test2 (export "test2") + (suspend $tag) + ) +) From e0bc3b96b395002771664704bbf23b18e24001c8 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 20 May 2026 18:07:51 -0700 Subject: [PATCH 2/3] trim whitespace --- test/lit/exec/continuation-leak.wast | 4 ++-- test/lit/exec/continuation-leak.wast.second | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lit/exec/continuation-leak.wast b/test/lit/exec/continuation-leak.wast index 25ef7caa505..b760d95ccff 100644 --- a/test/lit/exec/continuation-leak.wast +++ b/test/lit/exec/continuation-leak.wast @@ -7,11 +7,11 @@ (type $func_t (func)) (type $cont_t (cont $func_t)) (tag $tag) - + (func $f_suspend (suspend $tag) ) - + (func $test1 (export "test1") (local $c (ref $cont_t)) (local.set $c (cont.new $cont_t (ref.func $f_suspend))) diff --git a/test/lit/exec/continuation-leak.wast.second b/test/lit/exec/continuation-leak.wast.second index 0b9851e2874..70a276176ae 100644 --- a/test/lit/exec/continuation-leak.wast.second +++ b/test/lit/exec/continuation-leak.wast.second @@ -1,6 +1,6 @@ (module (tag $tag) - + (func $test2 (export "test2") (suspend $tag) ) From ad6cea974d905498083573a7ad2cad89ab5c82c4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 21 May 2026 13:16:10 -0700 Subject: [PATCH 3/3] refactor --- src/wasm-interpreter.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 463b505f3cf..8d793045dfb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -304,6 +304,13 @@ struct ContinuationStore { // Set when we are resuming execution, that is, re-winding the stack. bool resuming = false; + + // On traps or other errors that unwind the stack, we reset the continuation + // store to return to a clean state ahead of further calls to exports. + void clear() { + continuations.clear(); + resuming = false; + } }; // Execute an expression @@ -537,8 +544,7 @@ class ExpressionRunner : public OverriddenVisitor { #if WASM_INTERPRETER_DEBUG std::cout << indent() << "clear continuations\n"; #endif - continuationStore->continuations.clear(); - continuationStore->resuming = false; + continuationStore->clear(); } }