Skip to content

Commit 54bac97

Browse files
committed
fast-interp: trap on cross-function exception payload propagation
The walker's "no handler in this frame" path previously set `prev_frame->exception_raised = true` and let `return_func` forward the throw to the caller, regardless of payload size. This silently lost the payload: the source cells (`throw_src_offsets`) live in *this* frame's `frame_lp`, which return_func is about to tear down. The caller's `find_a_catch_handler` then ran with `throw_param_cell_num = 0`, which made any typed catch in the caller bind uninitialized destination slots — the catch body would either see garbage in its payload locals or, if the typed catch's slots were used as struct-of-pointers, dereference freed memory. Cross-function payload preservation would require a per-thread scratch buffer to ferry the payload across the frame boundary (callee's frame_lp → buffer → caller's frame_lp), plus a small change to return_func to populate it before tearing down the callee. That's a meaningful design lift and out of scope for this commit. Safe action for now: when a payload-bearing throw escapes its callee (i.e. `throw_param_cell_num > 0` and we're about to return to a caller frame), trap to the host with the diagnostic `"cross-function exception payload not supported by fast- interp"`. Same-function payload routing (the common Porffor / AS shape, where a JS throw is caught by an in-function catch the JS-to-wasm compiler emitted) is unaffected — that path dispatches via the same-function match in the walker before this branch runs. A `catch_all` in the caller would technically tolerate a zero-payload bind, but the typed-vs-catch_all choice happens in the caller's walker, which we can't peek into here without coupling the frames. Trap unconditionally for payload-bearing cross-frame throws. Tests: * `cross_function_tag_with_params` stays `#[ignore]` — that's the eventual-success-case for when cross-frame payload routing is implemented. * `cross_function_tag_with_params_traps` (new) asserts the current trap-with-expected-message contract on the same module shape. Codex P1 review feedback on rebeckerspecialties/wasm-benchmark PR #3 (patch 0007 line 306): "Preserve cross-frame exception payloads".
1 parent 57f4169 commit 54bac97

1 file changed

Lines changed: 29 additions & 1 deletion

File tree

core/iwasm/interpreter/wasm_interp_fast.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2083,8 +2083,36 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
20832083
* re-enters this label with the caller's frame in
20842084
* scope. If we're already at the top of the wasm
20852085
* stack, the existing got_exception path lets the
2086-
* host observe the trap via wasm_runtime_get_exception. */
2086+
* host observe the trap via wasm_runtime_get_exception.
2087+
*
2088+
* Tag-with-params payload is intentionally NOT
2089+
* preserved across the frame boundary: the source
2090+
* cells (throw_src_offsets) live in *this* frame's
2091+
* frame_lp, which return_func is about to tear down.
2092+
* A caller-side typed catch would then bind
2093+
* uninitialized destination slots, producing wrong
2094+
* results in the catch body (or, if the typed catch
2095+
* uses the slots as a struct-of-pointers, memory
2096+
* corruption). The safe action when a payload-
2097+
* bearing throw escapes its callee is to trap to the
2098+
* host with a clear diagnostic. Same-function
2099+
* payload routing (the common Porffor / AS shape)
2100+
* is unaffected — it dispatches via the loop above
2101+
* before this branch runs. catch_all in the caller
2102+
* would technically tolerate a zero-payload bind,
2103+
* but the typed-vs-catch_all choice happens in the
2104+
* caller's walker, which we can't peek into here
2105+
* without coupling the frames; trap unconditionally
2106+
* for payload-bearing throws and let the test
2107+
* `cross_function_tag_with_params` document the
2108+
* shape. */
20872109
if (prev_frame && prev_frame->ip) {
2110+
if (throw_param_cell_num > 0) {
2111+
wasm_set_exception(module,
2112+
"cross-function exception payload "
2113+
"not supported by fast-interp");
2114+
goto got_exception;
2115+
}
20882116
prev_frame->tag_index = exception_tag_index;
20892117
prev_frame->exception_raised = true;
20902118
goto return_func;

0 commit comments

Comments
 (0)