Skip to content

Hand an uncaught exception on past a try's parent, not back into it - #359

Merged
ASDAlexander77 merged 2 commits into
mainfrom
fix-eh-mismatch-in-catch-clause
Sep 24, 2026
Merged

ASDAlexander77 merged 2 commits into
mainfrom
fix-eh-mismatch-in-catch-clause

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

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:

try {
    try { throw 1; } catch (o) {
        try { throw "x"; } catch (e: number) {}
    }
} catch (e2) { /* never reached */ }
shape Windows on main Linux on main
mismatch in a parent's catch clause left the function uncaught (0xE06D7363) std::terminate
same, with an inner finally hung: caught again forever printed nothing
a try with using in a parent's finally compiler crash in WinEHPrepare terminate called without an active exception
  • Enclosing handler. It 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, or the finally of one whose catch clause holds it. Every edge that hands an exception on now uses it instead of the parent's own pad: the mismatch rethrow, a finally's cleanup return, a cleanup-only try's rethrow, and the Linux finally rethrow and its catch-all clause.
  • Linux rethrow after a using. The pad is a catch-all, but __cxa_begin_catch was emitted only for linuxTypedCatchChains, 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 main already: a using inside 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 in Win32ExceptionPass. The using variant 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. On main it fails.
  • 00try_mismatch_in_catch_clause_using.ts, Linux only (compile, jit): a using, with and without a finally.
  • Windows Release: 2865/2865 passed.
  • Linux (WSL): 2861/2861 passed.

🤖 Generated with Claude Code

ASDAlexander77 and others added 2 commits September 24, 2026 00:36
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>
@ASDAlexander77
ASDAlexander77 merged commit dd38108 into main Sep 24, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-eh-mismatch-in-catch-clause branch September 24, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant