Skip to content

Mono: give EH clauses nested in an earlier-listed handler their own region - #135645

Closed
pcshrosbree wants to merge 1 commit into
dotnet:mainfrom
pcshrosbree:mono/eh-nested-handler-regions
Closed

pcshrosbree wants to merge 1 commit into
dotnet:mainfrom
pcshrosbree:mono/eh-nested-handler-regions

Conversation

@pcshrosbree

Copy link
Copy Markdown
Contributor

Fixes #135641.

ECMA-335 I.12.4.2.5 orders nested try blocks only. A clause nested in the handler of another clause may be listed after that clause. Mono's JIT assumed it could not be:

  • compute_bb_regions (method-to-ir.c) marks regions in EH-table order, and mark_bb_in_region keeps the first handler region a block receives. When the enclosing clause is listed first, the blocks of the nested filter or catch handler stay in the enclosing handler's region. MONO_VERBOSE_METHOD=FilterInEarlierFinally on main shows the nested filter (IL_0016) and its handler (IL_001b) in region 0x112, the finally of clause 0.
  • mono_find_block_region_notry (mini.c), ip_get_finally_clause_index and the CEE_RETHROW handler lookup (method-to-ir.c) return the first matching clause. The comment on ip_get_finally_clause_index says "The clauses are ordered from inner to outer, so the first match is the innermost one".

JIT/Regression/JitBlue/Runtime_135592, added in #135601, has valid IL in this shape, and Mono failed it in that PR's CI (build 1631268): the mono windows-x64 work item crashed with 0xC0000005, the mono linux-arm64 work item hung until the watchdog stopped it, and the llvmaot leg's AOT compile step failed with * Assertion at src/mono/mono/mini/mini-llvm.c:12710, condition 'lhs' not met (OP_ENDFINALLY, from CatchInEarlierFinally).

Change

  • mark_bb_in_region: when a block already belongs to a handler or filter region, a handler or filter region that is strictly nested in it replaces it. Otherwise the block keeps its region, as before.
  • mono_find_block_region_notry, ip_get_finally_clause_index and the CEE_RETHROW lookup: choose the innermost (shortest) enclosing handler or filter instead of the first match. Handler and filter ranges are disjoint or nested, so the shortest one is the innermost.
  • The CEE_RETHROW lookup also now considers only catch and filter handlers, whose exception variable holds the exception to rethrow. It used to accept a fault handler, which has no exception variable: a rethrow in a fault nested in a catch crashed when the fault clause was listed first, and choosing the innermost handler alone would have made it crash in either order.

When nested clauses are listed first, every block gets the same region as before. With the change, the nested filter and handler above get their own regions (0x281 and 0x221).

Validation

Own Mono builds of main 523a36f9e98 and of this change on main 29d644d27d0. The PR is on main 0d21247817e; the three commits since 523a36f9e98 change src/coreclr/jit/helperexpansion.cpp, the cDAC and System.Net.NetworkInformation, and nothing under src/mono. Runtime_135592 assembled from #135601's IL (at 319d70bb424, before the Mono exclusion was added), run under corerun from the Mono Core_Root, one process per run, 30 s timeout. "Whole test" runs Main; the scenarios are invoked one per process through a small reflection driver.

platform mode main this PR
windows-x64 JIT whole test and FilterInEarlierFinally: 0xC0000005, 20 of 20 each; the other three scenarios pass, 20 of 20 each all pass, 20 of 20 each
windows-x64 interpreter all pass, 20 of 20 each all pass, 20 of 20 each
linux-x64 JIT, interpreter all pass, 20 of 20 each (the JIT's region dump shows the wrong regions above) all pass, 20 of 20 each
linux-x64 LLVM AOT (as the llvmaot leg) AOT compile asserts lhs at mini-llvm.c:12710 compiles; with the AOT image, all pass, 20 of 20 each
osx-arm64 JIT whole test and FilterInEarlierFinally: timeout, 20 of 20 each; the other three scenarios pass, 20 of 20 each all pass, 20 of 20 each
osx-arm64 interpreter all pass, 20 of 20 each all pass, 20 of 20 each

rethrow in a handler nested in a catch handler, both clause orders (an ad hoc probe, not part of this PR), JIT, 5 runs per cell on each of windows-x64, linux-x64 and osx-arm64:

rethrow in clause listed first main this PR
a fault nested in a catch the fault the process crashes (0xC0000005 on windows-x64; SIGSEGV, then abort, on linux-x64; abort on osx-arm64) rethrows the catch's exception
a fault nested in a catch the catch rethrows the catch's exception rethrows the catch's exception
a catch nested in a catch the inner catch rethrows the inner exception rethrows the inner exception
a catch nested in a catch the outer catch rethrows the outer exception rethrows the inner exception

EH-related tests (JIT/Methodical/eh, JIT/Directed/ExcepFilters, JIT/Regression/Dev11, baseservices/exceptions and two JIT/Regression/JitBlue tests; priority 1, standalone; 335 tests), main vs this PR:

platform mode main this PR
linux-x64 JIT 331 of 335 pass 331 of 335 pass, same tests
linux-x64 interpreter 331 of 335 pass 331 of 335 pass, same tests
linux-x64 LLVM AOT compile 333 of 335 compile 334 of 335 compile (Test_HndIndex_10_Reordered now compiles)
linux-x64 run after LLVM AOT compilation 331 of 335 pass 330 of 335 pass; the difference is ParallelCrashTester, which failed 3 of 10 JIT reruns on main and 2 of 10 with this PR
windows-x64 JIT 328 of 335 pass 328 of 335 pass, same tests
windows-x64 interpreter 327 of 335 pass 327 of 335 pass, same tests

Runs after LLVM AOT compilation allow JIT fallback, including for assemblies whose AOT compilation failed, so they do not show that every method ran AOT-compiled. On windows-x64, the test IL was built on linux-x64 and each test was run directly under corerun and checked for its expected exit code, instead of through a generated Windows script.

Tests that fail both before and after
  • linux-x64 JIT, and runs after LLVM AOT compilation: Test_HndIndex_10_Plain, Test_HndIndex_10_Reordered, b473131_struct, OOMException01.
  • linux-x64 interpreter: rethrowinfinallyinsidecatch_d, rethrowinfinallyinsidecatch_r, Test_HndIndex_10_Plain, Test_HndIndex_10_Reordered.
  • windows-x64 JIT: the linux-x64 JIT set, plus CSharpPart_132534, ParallelCrashTester and unhandledTester.
  • windows-x64 interpreter: the linux-x64 interpreter set, plus throwinfilter_d, throwinfilter_r, ParallelCrashTester and unhandledTester.

Test_HndIndex_10_Reordered stays disabled on Mono. It now LLVM-AOT-compiles, but under JIT, under the interpreter and after LLVM AOT compilation, before and after this change, it still reports "The double ctor call worked without generating an exception". On main its AOT compilation fails, so that run used JIT fallback.

The interpreter is not changed. Its initialize_clause_bblocks (interp/transform.c) also keeps, for each IL offset, the first handler in table order. The four Runtime_135592 scenarios pass under the interpreter before and after, but in the rethrow probe above the interpreter rethrows the outer exception when the outer catch is listed first, and throws NullReferenceException for the fault listed first, on main and with this PR.

Runtime_135592 is disabled on Mono in #135601 (ActiveIssue for #135641 and MonoAotIncompatible). Once #135601 merges, that exclusion can be lifted, either here by rebasing onto it or in a follow-up.

Shipped Mono

Mono 8.0.28 runtime packs, running the compiled Runtime_135592 through a self-contained reflection host, one process per run, 30 s timeout:

platform FilterInEarlierFinally the other three scenarios
windows-x64 0xC0000005, 20 of 20 pass, 20 of 20 each
linux-arm64 (WSL2) timeout, 20 of 20 pass, 20 of 20 each
osx-arm64 timeout, 20 of 20 pass, 20 of 20 each
linux-x64 passes, 20 of 20 pass, 20 of 20 each

Note

This PR was prepared with AI assistance (Anthropic Claude and OpenAI Codex), under my direction. I reviewed it before posting.

…egion

ECMA-335 I.12.4.2.5 orders nested try blocks only. A clause nested in the
handler of another clause may be listed after that clause.
compute_bb_regions marked regions in table order and kept the first
handler region a block received. The blocks of such a nested filter or
catch handler therefore stayed in the enclosing handler's region.

Runtime_135592 hits this. Its FilterInEarlierFinally scenario, with
shipped Mono 8.0.28, fails with an access violation on windows-x64 and
hangs on linux-arm64 and osx-arm64. In CI the test crashed the
windows-x64 work item and hung the linux-arm64 one. Its
CatchInEarlierFinally scenario makes the LLVM AOT compiler assert in
OP_ENDFINALLY (mini-llvm.c, `lhs' not met).

mono_find_block_region_notry, ip_get_finally_clause_index and the
rethrow handler lookup assume the same order. Each returns the first
matching clause.

In mark_bb_in_region, let a strictly nested handler or filter region
replace the enclosing one. In the three lookups, choose the innermost
(shortest) enclosing handler. Handler and filter ranges are disjoint or
nested, so the shortest one is the innermost. If nested clauses are
listed first, every block gets the same region as before.

In addition, the rethrow lookup now considers only catch and filter
handlers, whose exception variable holds the exception to rethrow. It
used to accept a fault handler, which has no exception variable: a
rethrow in a fault nested in a catch crashed when the fault clause was
listed first, and choosing the innermost handler would have made it
crash in either order. It now rethrows the catch's exception in both
orders.
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 12, 2026
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Oct 12, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 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, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@jkotas

jkotas commented Oct 12, 2026

Copy link
Copy Markdown
Member

We are not interested in fixing corner case bugs in Mono runtime. Mono runtime is going to be deleted in near future.

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

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

2 participants