Skip to content

Windows: a try's cleanup hands the exception straight to its catch (#457, #456) - #472

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-try-class-let
Oct 3, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-try-class-let

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Fixes #457. Fixes #456.

On Windows, a try whose scope cleans up on the way out lost its exception whenever the function also allocated on the stack dynamically, as a two-argument print does. The cleanup comes from an owning let (its release) or a using. For example, let t = s; print("in try", t.v); throw 1; terminated with 0x80000003 instead of running the catch. This happened in every model, under JIT and AOT. A let whose initializer throws (#456) is the same cleanup and failed the same way.

Cause

Win32ExceptionPass turned the cleanup's exit into a cleanupret to a new catch-all funclet that rethrew (_CxxThrowException(null, null)), and that funclet unwound to the real handler. With a dynamic alloca in the function, the handler never caught the rethrow. Two hand edits of the LLVM IR settled it:

  • cleanupret unwind label <handler>, clang's shape: fixed it.
  • The rethrow kept, but as an invoke to the handler: did not fix it.

Fix

A cleanup with a handler to go on to now unwinds straight to it, and the invokes inside the cleanup unwind there too. The catch-all funclet is still built for a cleanup with no handler in the function, which unwinds to the caller; otherwise it is left unreachable. Linux is untouched.

Tests

🤖 Generated with Claude Code

, #456)

A try whose scope cleans up on the way out - an owning `let` (its release), or a `using` - lost
its exception on Windows when the function also allocated on the stack dynamically, as a
two-argument `print` does: `let t = s; print("in try", t.v); throw 1;` terminated (0x80000003)
instead of running the catch, in every model, JIT and AOT. So did a `let` whose initializer
throws (#456): it is the same cleanup.

Win32ExceptionPass turned the cleanup's exit into `cleanupret` to a new catch-all funclet that
rethrew (`_CxxThrowException(null, null)`), and that funclet unwound to the real handler. With a
dynamic alloca in the function the rethrow was never caught by the handler. Hand-editing the IR to
`cleanupret unwind label <handler>` - clang's shape - fixed it; keeping the rethrow and making it
an invoke did not.

Now a cleanup with a handler to go on to unwinds straight to it, and the invokes inside the
cleanup unwind there too. The catch-all funclet is still built, for a cleanup with no handler in
the function (which unwinds to the caller), and is left unreachable otherwise. Linux is untouched.

Test: 00try_cleanup_then_catch (compile and JIT, and in the rc/none corpus): an owning let and a
two-argument print in the try, a two-argument print in the catch, a using with a two-argument
print in the catch, a let whose initializer throws and one whose cast throws (#456), with a const
as the control. It terminated before the change.

Fixes #457.
Fixes #456.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 239c004 into main Oct 3, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-try-class-let branch October 3, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant