Repository navigation
A break, continue or return out of a catch clause ends the catch on every path out of it - #601
Merged
Merged
Conversation
…very path out of it A `break` or `continue` out of a catch clause crashed with 0x80000003 on Windows (JIT and AOT, every memory model, with or without a finally), and so did a `return` out of one whenever the clause could also fall off its end. Two defects: - TryOpLowering only marked a break/continue out of a catch as leaving the catch when the try had a finally, so without one the jump ended no catch at all. A jump out of a catch nested in another catch ended only one of the two. TSContext::catchesLeft now counts the catch clauses a return, break or continue leaves, and their lowering ends that many. The return or jump in the copy of a finally entered through a catch-all pad (#599) counts that pad as one more. - Win32ExceptionPass gave each catch region a single end: its scan follows the layout and closed the region at the first end-of-catch marker. Every other way out of the clause was left as a bare branch out of the catchpad, with no funclet bundle on its calls, which WinEHPrepare turns into unreachable code. findOtherCatchExits now walks each catch clause by its control flow from the pad (a nested catch first, continuing after its ends), collects every end-of-catch marker and the calls on the way to them, and each extra end gets its own catchret. The same goes for the copy of a finally entered through a catch-all pad (#599) that leaves on some paths only (`finally { if (c) return s; ... }`): its run-to-the-end exit, which rethrows, is now always the region's main end and its `return` the other, where the layout could put the `return` first and give the rethrow path a catchret into a `resume` (SimplifyCFG crashed on it). An invoked end-of-catch marker is also recorded among the region's invokes, so that rethrow goes to the enclosing handler rather than to the caller. 00try_jump_from_catch.ts covers break, continue and return out of a catch with and without a finally, a finally reading a local of the block around the try, a typed catch, a catch nested in another, a loop inside a catch, a labelled continue, and a finally that returns or breaks on some paths only, with and without a try around it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
A
breakorcontinueout of a catch clause crashed with 0x80000003 on Windows (JIT and AOT, gc/rc/none, with or without afinally), and so did areturnout of a catch that could also fall off its end:There were two defects.
MLIR (TryOpLowering). A break/continue out of a catch was marked as leaving the catch only when the try had a
finally. Without one, the jump ended no catch. A jump out of a catch nested in another catch ended only one of the two.TSContext::catchesLeftnow counts how many catch clauses a return, break or continue leaves, and the lowering emits one end-of-catch for each. The return/jump in #599's catch-all finally copy uses the same counter.Win32ExceptionPass. Each catch region got exactly one end. The scan follows block layout and closed the region at the first
__cxa_end_catch. Any other way out was left as a bare branch out of the catchpad, and the calls on that path had no funclet bundle, so WinEHPrepare turned them into unreachable code. The newfindOtherCatchExitswalks each catch clause by control flow from its pad. It walks nested catches first and continues after their ends. It collects every end marker and the calls on the way to each one, and each extra end gets its owncatchret. The same walk fixes the shape #599 left open: a finally that returns or breaks on only some paths (finally { if (c) return s; ... }). For that shape, the rethrowing run-to-end exit is now always the region's main end. An invoked end marker is also recorded among the region's invokes, so the rethrow goes to the enclosing handler.New test
00try_jump_from_catch.ts(compile, JIT, TSLANG_CORPUS) covers:Verified on Windows:
--opt--emit=mlir-affine --verify-ownership -mm=rcreports nothing on itctest -C Release -j 8 --timeout 300: 4168/4168 passedThe Itanium path also emits the extra
__cxa_end_catchcalls, but I have not run it locally; the Linux CI job is its check.🤖 Generated with Claude Code