Skip to content

Commit 57f4169

Browse files
committed
fast-interp: unwind skipped EH entries on outer-catch dispatch
When a throw from a nested try is caught by an OUTER handler, the walker previously left the inner-try entries between the throw site and the matched outer entry on the eh-stack. The matched entry got its `EH_TRY_CATCH_STATE_BIT` set, but `frame->eh_count` stayed unchanged. After the outer catch body's END decremented eh_count by one, the inner-try slot remained at the top of the eh-stack with the matched outer entry now sitting *under* it (in-progress bit set). A subsequent throw inside (or after) the outer catch body would walk that stale state. The walker SKIPs entries with the state bit set, so the outer entry was correctly ignored — but the inner-try entry (no state bit) was treated as live. If the inner try's typed catch happened to match the new tag, the walker dispatched against that stale entry — an out-of-scope catch. Worse, in a tight loop of `outer try { inner try { throw } catch_other catch_outer { ... } }`, every iteration leaked one inner-try entry. After more iterations than the function's `exception_handler_count`, the next TRY push wrote past the static eh-stack reservation (silently in release builds since `bh_assert` is a no-op without `BH_DEBUG`). Fix: at each match-and-dispatch site in `find_a_catch_handler` — both the typed-catch branch and the catch_all branch — set `frame->eh_count = i;` before jumping to the handler. `i` is the loop counter, which equals the index of the matched entry plus one. This pops the nested-try entries above the match in a single indexed store. The matched entry stays at index i-1 with its state bit set; the catch body's END pops it normally when the body completes. Cost shape: one extra indexed store on the cold throw path, only when a typed catch or catch_all matches. CALL / LOAD / STORE handlers are untouched. Test added in the external integration suite at `crates/benchmark-core/tests/eh_correctness.rs:: outer_catch_unwinds_inner_eh_entries`. The test pattern is: outer try catches `$err`; inner try has a catch for `$err2`. Inner throw of `$err` is caught by outer. Outer catch body re-throws `$err2`, which must propagate UNCAUGHT (inner try is out of scope). Pre-fix walker found the stale inner catch and dispatched to it, producing a Ok(99) instead of the trap; post-fix the walker has no in-scope entries and the throw escapes correctly. Codex P1 review feedback on rebeckerspecialties/wasm-micro- runtime PR #2: "Unwind skipped EH entries before dispatching catches".
1 parent 04625e7 commit 57f4169

1 file changed

Lines changed: 25 additions & 0 deletions

File tree

core/iwasm/interpreter/wasm_interp_fast.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,25 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
20362036
frame_lp[throw_src_offsets[c]];
20372037
}
20382038
}
2039+
/* Pop the inner eh-stack entries that the
2040+
* throw is jumping past. When the match is
2041+
* at the topmost entry this is a no-op
2042+
* (i == frame->eh_count). When the match is
2043+
* an outer entry, the nested-try entries
2044+
* above it (indices i .. eh_count-1) are
2045+
* out of scope after the catch-dispatch;
2046+
* leaving them counted would let a
2047+
* subsequent throw inside the catch body
2048+
* see stale in-scope entries (and a tight
2049+
* loop of throw → outer-catch → throw
2050+
* would eventually overflow the fixed
2051+
* reservation). The matched entry stays
2052+
* at index i-1 with its state bit set; the
2053+
* catch body's END pops it when it
2054+
* completes. Cost: one indexed store on
2055+
* the cold throw path; CALL / LOAD / STORE
2056+
* untouched. */
2057+
frame->eh_count = i;
20392058
frame_ip = entry->catches[j].handler_pc;
20402059
HANDLE_OP_END();
20412060
}
@@ -2048,6 +2067,12 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
20482067
* documented as a known limitation. */
20492068
cells[0] = packed | EH_TRY_CATCH_STATE_BIT;
20502069
cells[1] = exception_tag_index;
2070+
/* Same unwind as the typed-catch path above —
2071+
* pop any nested-try entries the throw is
2072+
* jumping past so a subsequent throw inside
2073+
* this catch_all body doesn't dispatch
2074+
* against stale inner entries. */
2075+
frame->eh_count = i;
20512076
frame_ip = entry->catch_all_pc;
20522077
HANDLE_OP_END();
20532078
}

0 commit comments

Comments
 (0)