Repository navigation
Hand an uncaught exception on past a try's parent, not back into it - #359
Merged
Merged
Conversation
A try inside another try's catch clause (or finally) whose typed catch
did not match handed its exception to the parent's own landing pad,
the handler already running:
try {
try { throw 1; } catch (o) {
try { throw "x"; } catch (e: number) {}
}
} catch (e2) { /* never reached */ }
On Windows that left the function uncaught (0xE06D7363), and with an
inner finally it caught the exception again forever. On Linux it reached
std::terminate. With a `using` in a try inside the parent's finally,
WinEHPrepare crashed the compiler.
The enclosing handler was worked out one level up only. It now walks
the whole chain of tries around this one, and takes the first that
receives the exception: the landing pad of a try whose body holds it,
the finally of one whose catch clause holds it. Every edge that hands
an exception on - the mismatch rethrow, a finally's cleanup return, a
cleanup-only try's rethrow, the Linux finally rethrow and its catch-all
clause - now uses it instead of the parent's own pad.
On Linux the mismatch rethrow of a try with a `using` also ran with
nothing caught ("terminate called without an active exception"): its
pad is a catch-all, but __cxa_begin_catch was emitted only for
linuxTypedCatchChains. It is now emitted for every Linux rethrow.
Tests: 00try_mismatch_in_catch_clause.ts (untyped and typed parent
catch, inner finally, a try in the parent's finally, two levels up
through a finally), and 00try_mismatch_in_catch_clause_using.ts,
Linux only. A `using` inside a catch clause is already broken on Windows
on main: its cleanup pad is not nested in the catch funclet, which fails
LLVM's verifier ahead of time, and without a try around it the JIT
crashes in Win32ExceptionPass.
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.
Summary
A try inside another try's catch clause (or finally) whose typed catch did not match handed its exception to the parent's own landing pad, which is the handler already running:
mainmainstd::terminatefinallyusingin a parent'sfinallyterminate called without an active exceptionusing. The pad is a catch-all, but__cxa_begin_catchwas emitted only forlinuxTypedCatchChains, so the rethrow ran with nothing caught. It is now emitted for every Linux rethrow.This started as a review finding about the Linux cleanup case. The actual bug is wider and on both platforms.
Not fixed here, and broken on
mainalready: ausinginside a catch clause on Windows. Its cleanup pad is not nested in the catch funclet, which fails LLVM's verifier ahead of time ("Unwind edges out of a catch must have the same unwind dest as the parent catchswitch"). Without a try around it, the JIT crashes inWin32ExceptionPass. Theusingvariant of the test is therefore Linux-only.Verification
00try_mismatch_in_catch_clause.ts(compile, jit, and the rc/none corpus): untyped and typed parent catch, inner finally, a try in the parent's finally, and two levels up through a finally. Onmainit fails.00try_mismatch_in_catch_clause_using.ts, Linux only (compile, jit): ausing, with and without a finally.🤖 Generated with Claude Code