Skip to content

[Interpreter] Install the resume-after-catch handler in ThrowControlForThread - #135533

Draft
pcshrosbree wants to merge 2 commits into
dotnet:mainfrom
pcshrosbree:interp-abort-throw-control
Draft

pcshrosbree wants to merge 2 commits into
dotnet:mainfrom
pcshrosbree:interp-abort-throw-control

Conversation

@pcshrosbree

@pcshrosbree pcshrosbree commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR: With DOTNET_InterpMode=1 (only some methods interpreted), a ControlledExecution action that cancels its own token terminates the process or hits an assert. This PR fixes that and adds a regression scenario. It also enables an existing self-cancellation test again, except on browser CoreCLR, where #135914 still applies. It is rebased onto main and stays a draft until CI has run on its two commits.

Fixes #135532.

Rebased onto main now that #135939 is in. The first commit is the fix. The second skips CancelItselfOutsideOfTryCatchFinally on browser CoreCLR. Since #135915 removed the class-level multithreading requirement from ControlledExecutionTests, this test would otherwise also run on single-threaded browser-wasm, where #135914 still applies. The PR stays a draft until CI has run on the rebased commits.

When compiled code raises a thread abort again at the end of a catch handler, it does so through ThrowControlForThread. Before this change, when an interpreted frame further up caught the abort, the ResumeAfterCatchException used to resume that frame encountered the managed frames present when the thread was redirected. Now ThrowControlForThread installs the resume-after-catch handler, like IL_Throw and HandleManagedFault.

On Windows x64 with shadow stacks, the handler's rethrow searched for a "precise" SSP on the assumption that the slot below the SSP holds a return address in the context's frame. A redirected thread's frame was interrupted rather than stopped at a call, and its recorded SSP is exact already, so the search moved it past the correct position. The new INSTALL_RESUME_AFTER_CATCH_HANDLER_WITH_INTERRUPTED_CONTEXT marks the context as interrupted, so that RethrowResumeAfterCatchExceptionSkipManagedFrames uses the recorded SSP without searching for another one. The same mismatch caused #135530, fixed by #135531.

Tests: a CancelItself scenario in src/tests/Regressions/coreclr/GitHub_135463 (the action cancels its own token), and ControlledExecutionTests.CancelItselfOutsideOfTryCatchFinally enabled again, except on browser CoreCLR.

The new scenario was tested before the rebase, with this PR stacked on #135939's head (Checked builds; linux-x64, osx-arm64, windows-x64 with CET enabled). The rebase applied without conflicts and did not change the first commit's diff.

#135939 alone #135939 + this PR
JIT pass pass
DOTNET_InterpMode=1, with and without TieredCompilation=0 linux/osx: terminate with ResumeAfterCatchException; windows: assert pass
DOTNET_InterpMode 2 and 3 pass pass

Additional validation, on the same builds:

  • Under the JIT, the new scenario also passes with TieredCompilation=0, JITMinOpts, JitStress 1 and 2, ReadyToRun=0, and GCStress 0x3 and 0xC on all three platforms. Its TestEnclosingTry is skipped under the JIT, as in [Interpreter] Raise a pending thread abort again at the resume point after a catch #135939.
  • With this PR, all 10 ControlledExecutionTests pass when run as a class in one process, and CancelItselfOutsideOfTryCatchFinally also passes when run alone in its own process. Both runs pass on all three platforms under the JIT and DOTNET_InterpMode 1, 2 and 3.
  • With [Interpreter] Raise a pending thread abort again at the resume point after a catch #135939 alone, CancelItselfOutsideOfTryCatchFinally fails under mode 1: it terminates with ResumeAfterCatchException on linux-x64 and osx-arm64, and fails a Checked consistency check on windows-x64.
  • With the handler but without the interrupted-context marking, windows-x64 asserts *(ULONG64*)targetSSP == pContext->Rip in ExecuteFunctionBelowContext.

Note

This PR was prepared with AI assistance (Anthropic Claude, checked by OpenAI Codex) at my request. I reviewed it before posting it.

@github-actions github-actions Bot added the area-ExceptionHandling-coreclr only use for closed issues label Oct 9, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 9, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @BrzVlad, @janvorli
See info in area-owners.md if you want to be subscribed.

When compiled code raises a thread abort again at the end of a catch
handler and an interpreted frame catches it, the ResumeAfterCatchException
that resumes that frame ran into the managed frames ThrowControlForThread
was redirected from. Install the handler there, as IL_Throw and
HandleManagedFault do.

On Windows x64 with shadow stacks, the frame of a redirected thread was
interrupted rather than stopped at a call and its recorded SSP is exact,
so tell the rethrow not to search for a more precise one.

Enable ControlledExecutionTests.CancelItselfOutsideOfTryCatchFinally again
and add the scenario to the GitHub_135463 regression test.

Fixes dotnet#135532
The previous commit enables this test again. Since dotnet#135915 removed the
class-level IsMultithreadingSupported condition from
ControlledExecutionTests, the test now also runs on single-threaded
browser-wasm CoreCLR. There, with the default ReadyToRun CoreLib, an
action that cancels itself still leaves the abort pending at the end of
CancellationTokenSource's ReadyToRun catch (dotnet#135914). Checked runtimes
assert there. Skip the test on browser CoreCLR until dotnet#135914 is fixed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-Interpreter-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Interpreter] ResumeAfterCatchException escapes ThrowControlForThread when compiled code raises a thread abort again and an interpreted frame catches it

2 participants