You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A return deferred to finally survives an exception from that finally and is applied by an unrelated finally later #370
A return deferred to a finally block is stored in a single VM-wide slot
(VM._deferred_return) and is only cleared when a FINALLY_END consumes it. If
the finally block raises instead of completing, the deferred return is never
consumed — it stays pending and is applied by whatever unrelated FINALLY_END
runs next, in a completely different part of the program.
Found while fixing #361; the two share the same state machine.
Runtime error at ...:3:64: FINALLY_END deferred return outside function
Stack trace:
at <main> (...:3:64)
caught: x
body
fin
exit code 1. Expected:
caught: x
body
fin
after
The second try/catch/finally is unrelated to f() — it has no return at
all. It inherits f's stranded deferred return of 1, tries to pop a frame
that does not exist at module level, and dies with an internal VM message.
Expected behavior
An exception raised inside a finally supersedes a return that block was
about to complete. The pending return should be discarded when the exception
escapes the region that owes it, and unrelated finally blocks must never see it.
Fix direction
handle_exception should drop deferred state owned by a finally region it has
unwound out of. The region can be identified by the handler-stack depth recorded
when the return was deferred: if the handler receiving the exception sits outside
that depth, the region will never reach its FINALLY_END, so the pending action
is dead. A try nested inside the finally that catches its own error is at
the same depth or deeper and must not clear it.
Affected versions
v4.1.1 (current published) and dev source. Reproduces under CLI and embedded NodusRuntime. Predates 4.1.1.
Fixed in #372 (6175600), together with #361 — both are the same single-slot deferral mechanism.
handle_exception now discards deferred state owned by a finally region it has unwound out of. The region is identified by the handler-stack depth recorded when the action was deferred: if the handler receiving the exception sits outside that depth, the region will never reach its FINALLY_END and the pending action is dead.
The guard is deliberately not eager — an error raised and caught inside the finally is at the same depth or deeper, so the deferred return still lands. Both directions are tested:
test_return_stranded_by_a_raising_finally_does_not_leak — the reproduction from this issue; fails against the unfixed VM with FINALLY_END deferred return outside function
test_a_caught_error_inside_the_finally_keeps_the_deferred_return — the over-eager-guard case; passes before and after, which is what makes it a useful guard
Activity
fix(vm): run finally when catch rethrows; scope deferred state (#361, #…
Masterplanner25 commentedon Aug 15, 2026
Fixed in #372 (
6175600), together with #361 — both are the same single-slot deferral mechanism.handle_exceptionnow discards deferred state owned by afinallyregion it has unwound out of. The region is identified by the handler-stack depth recorded when the action was deferred: if the handler receiving the exception sits outside that depth, the region will never reach itsFINALLY_ENDand the pending action is dead.The guard is deliberately not eager — an error raised and caught inside the
finallyis at the same depth or deeper, so the deferred return still lands. Both directions are tested:test_return_stranded_by_a_raising_finally_does_not_leak— the reproduction from this issue; fails against the unfixed VM withFINALLY_END deferred return outside functiontest_a_caught_error_inside_the_finally_keeps_the_deferred_return— the over-eager-guard case; passes before and after, which is what makes it a useful guardBoth run under the CLI and embedded
NodusRuntime.release: Gate 10 creator validation for v4.2.0; two findings filed
release: v4.2.0 (#414)
feat: per-opcode semantic spec for the control-flow and frame opcodes (…
#412 phase 4: a semantic spec for all 49 opcodes, and a gate that req…