Repository navigation
fix(generator): make try/catch real in the sync generator state machine (#4438 B2) - #4457
Merged
Merged
Conversation
…ne (#4438) B2 slice of #4438. The catch handler previously existed only inside the .throw() closure, not in the normal .next() dispatch, so (a) a throw executing inside a try during a plain .next() escaped uncaught and (b) a yield inside a catch was swallowed (rewritten to await). Linearize the catch body into its own states; the happy path skips them. A sync generator's dispatch loop is wrapped in a try/catch that routes a thrown error to the matching catch's entry state (running pending finally and completing the generator when unhandled). .throw() routes the same way and falls through to the continuation loop so a yield inside the catch suspends. The runtime-throw path uses an inclusive lower bound since a throw in a try's first state runs at exactly protected_start. Async generators are untouched (linearized catch states are dead on the async path), so behavior is byte-identical. test262 GeneratorPrototype/{throw,return}/try-*: 15 -> 21 pass. Broader sweep of try-containing generator tests: 0 regressions, 17 newly-fixed (6 GeneratorPrototype + 11 yield* delegation error-handling cases). Remaining 6 failures all involve a yielding finally (separate follow-up).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses the B2 slice of #4438 (generator
.throw()/.return()try/finallyedge cases): makes a generator's
try/catchreal in the state machine forsync generators.
Root cause (deeper than the issue's framing)
The issue described B2 as "continuation-cloning edge cases in
linearize.rs",but probing showed the catch handler was entirely absent from the normal
.next()dispatch — it existed only inside the.throw()closure. Twoconsequences, both visible in test262:
throwexecuting inside atryduring a plain.next()was nevercaught — it propagated straight out of
next()(the[object] escapedfailures);
yieldinside acatchwas swallowed — it was rewritten to anawaitin the throw closure (catch (e) { yield e }lost the yield).Fix
linearize.rs— linearize thecatchbody into its own states (the guardnow also fires when the yield lives only in the catch). The happy path skips
the catch states; the catch's own states are excluded from the protected
interval so a throw inside the catch escapes to an enclosing handler.
lower.rs— for sync generators, wrap the dispatch loop body in a realtry/catchthat routes a thrown error to the matching catch'scatch_entry_state(or runs pendingfinally+ completes the generator whenunhandled).
.throw()routes the same way and falls through to thecontinuation loop, so a
yieldinside the catch suspends correctly. Theruntime-throw path uses an inclusive lower bound (
>=) since a throw in atry's first state runs at exactly
protected_start.Async generators are untouched: the linearized catch states are dead code on the
async path (it still inlines the catch body in
.throw()), so behavior isbyte-identical.
Results
test262
GeneratorPrototype/{throw,return}/try-*: 15 → 21 pass (alltry/catch cases). Broader sweep of every try-containing generator test:
0 regressions, 17 newly-fixed — the 6 GeneratorPrototype cases plus 11
language/expressions/yieldyield*-delegation error-handling tests(
star-rhs-iter-*-err,from-catch,rhs-unresolvable).Out of scope (remaining B2 — separate follow-up)
The 6 still-failing cases all involve a yielding
finally(finally { yield N }), which needs a completion-record mechanism to thread abrupt completionthrough the finally's states and re-raise afterward. Tracked as the remaining
B2-finally work under #4438.