Skip to content

JIT: (bug) assert phiFound || ... in SSA for a filter whose mutually-protecting sibling catch contains a try/finally #134457

Description

@EgorBo

BasicBlock::VisitEHEnclosedHandlerSecondPassSuccs enumerates a bogus EH successor edge from a filter region to a finally nested inside a sibling (mutually protecting) catch handler. Compiler::BlockPredsWithEH does not create the matching edge, so phi placement and phi-arg insertion disagree and the JIT asserts during SSA: insert phis.

Minimal Repro

No stress knobs needed; the failure happens while JITting Test at full opts.

using System;
using System.Runtime.CompilerServices;

public static class Program
{
    public static int Sink;

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Throw() => throw new InvalidOperationException();

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void Work(int v) => Sink = v;

    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
    public static int Test(int arg)
    {
        int x = 0;
        try
        {
            Throw();
        }
        catch (ArgumentException)
        {
            try { Work(arg); }          // nested try/finally inside the FIRST catch handler
            finally { Sink = x; }       // 'x' is live-in to this finally
        }
        catch (Exception) when ((x = arg + 1) > 0) // mutually-protecting filter defines 'x'
        {
            Sink = x;
        }
        return x;
    }

    public static int Main()
    {
        Console.WriteLine("Test(5) = " + Test(5));
        return 100;
    }
}

Expected

Test(5) = 6

exit code 100 (this is what .NET 10.0.12 prints).

Actual

Assert failure(PID 48836 [0x0000bec4], Thread: 67488 [0x107a0]): Assertion failed
'phiFound || ((ehDsc != nullptr) && !m_compiler->m_dfsTree->Contains(ehDsc->ebdTryBeg))'
in 'Program:Test(int):int' during 'SSA: insert phis' (IL size 64; hash 0x5b844c46; FullOpts)

    File: src\coreclr\jit\ssabuilder.cpp:685

Process exit code 0xC0000602.

Notes

  • Root cause: BasicBlock::VisitEHEnclosedHandlerSecondPassSuccs (compiler.hpp ~483-516) reuses one inTry out-param across hops of mixed kinds. The EH table has EH#0 (finally) nested in the handler of EH#1, and EH#2 mutually protecting EH#1. Walking up from the inner finally goes EH#0 -> (handler link, inTry=false) EH#1 -> (mutual-protect try link, inTry=true) EH#2; only the last inTry is tested, so the inner finally is misclassified as an enclosed try/finally of the filter's try region.
  • Semantically that edge cannot exist: second-pass unwind for catch (B) never enters a finally that only exists inside sibling handler A.
  • Consumers disagree: SsaBuilder::AddDefToEHSuccessorPhis uses VisitEHSuccs, while Compiler::BlockPredsWithEH (block.cpp ~229-253) never adds that edge.
  • Debug-only assert, but the bogus successor edge exists in Release codegen too. Target-independent.
  • main @ 498a04c34eae9db1a1b11e5de378d139d032f66f.

Activity

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

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions