Skip to content

JIT: Assertion failed 'firstHndBlock->bbRefs == 1' during 'Remove empty try-catch-fault 2' with a loop in the finally handler #133101

Closed

Description

@AndyAyersMS

Description

A try region whose body cannot throw, paired with a finally whose first block is the target of a back edge from within the handler, trips a ref-count assert in Compiler::fgRemoveEmptyTryCatchOrTryFault.

Assertion failed 'firstHndBlock->bbRefs == 1'
  in 'Runtime_XXXXXX:LoopInTryAndFinally(long):long'
  during 'Remove empty try-catch-fault 2'
  (IL size 33; hash 0xf561a59d; FullOpts)

The phase removes the handler blocks of an empty try, and assumes the handler's first block carries exactly the single artificial reference the phase itself installed:

// (3) Remove any handler blocks.
// The first handler block has an artificial ref count
//
assert(firstHndBlock->bbRefs == 1);
firstHndBlock->bbRefs = 0;

// (3) Remove any handler blocks.
// The first handler block has an artificial ref count
//
assert(firstHndBlock->bbRefs == 1);
firstHndBlock->bbRefs = 0;

When the handler entry is also the target of a back edge from inside the handler, bbRefs is 2 and the assert fires.

Repro

using System;
using System.Runtime.CompilerServices;

public static class Repro
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static long LoopInTryAndFinally(long a)
    {
        long x = a;
        try
        {
        L1:
            if ((x & 8) != 0) { x += 4; goto L1; }
        }
        finally
        {
        L2:
            if ((x & 1) != 0) { x += 1; goto L2; }
        }
        return x;
    }

    public static int Main()
    {
        Console.WriteLine(LoopInTryAndFinally(3));
        return 100;
    }
}

Requires optimized codegen (the phase does not run at Tier0), so DOTNET_TieredCompilation=0 on a checked JIT.

Configurations

Reproduces under crossgen2 with an assert-enabled JIT for all four target architectures — x64, arm64, x86, arm — under FullOpts with no stress modes enabled.

Isolating controls

The failure needs both an empty-able try and a self-edge on the finally entry, and is specific to finally:

variant shape result
as above self-loop in try and in finally assert (x64, arm64, x86, arm)
control self-loop in try only, straight-line finally clean
control straight-line try, self-loop in finally only clean
control catch instead of finally clean

Severity

Assert-only as far as I have been able to demonstrate. Running the same method over a ∈ [-40, 80] against a while-loop reference implementation on a release runtime (both tiered and with DOTNET_TieredCompilation=0) produces matching results in every case. In release builds the assert is compiled out and bbRefs is unconditionally set to 0 immediately afterwards.

So this may be a stale-assert / bookkeeping issue rather than a latent bad-codegen bug — but the invariant the phase documents ("The first handler block has an artificial ref count") does not hold, and I did not attempt to prove that no downstream consumer depends on it.

Related

Several other asserts have been reported in this phase, all distinct from this one and all closed: #130248, #115122, #110958, #110954, #114985.

Found by

A compile-only crossgen2 fuzzing leg generating goto-webs with duplicated statement tails inside EH regions. Reduced automatically from 756 bytes to the 12-line repro above.

Note

This issue was AI-generated by Copilot, and reviewed by @AndyAyersMS.

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 2, 2026
  2. dotnet-policy-service commented on Sep 2, 2026

    @dotnet-policy-service
    Contributor

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

  3. self-assigned this
    on Sep 2, 2026
  4. removed
    untriagedNew issue has not been triaged by the area owner
    on Sep 2, 2026
  5. locked and limited conversation to collaborators on Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions