Skip to content

Internal CLR error. (0x80131506) from CompileFast when a custom equality operator's returns non bool value #561

Closed

Description

@overefactor

CompileFast emtis a delegate for expression tree whose body BinaryExpression has a non-bool result for user-defined operator == returning ICustomObject. The delegate returns a reference that passes a null check but is currupt - invoking direct call on this member such as ToString produces NullReferenceException, invoking virtual member like Ping() terminates the process with a fatal CLR error (0x80131506). built-in Compile() handles the same tree correctly.

Because this is a memory safety failure, it cannot be caught with try/catch and takes down the whole process.

Repro

FastExpressionCompiler 5.4.1, .NET 10 (SDK 10.0.112), Windows 11 x64.
bug occurs in latest FCE 6.0.0 too

using System.Linq.Expressions;
using FastExpressionCompiler;

Expression<Func<ICustomObject>> lambda = () => 1 == new CustomObject();

Console.WriteLine($"lambda: {lambda}");

Console.WriteLine("Baseline path prints pong on following line");
lambda.Compile().Invoke().Ping();

var func = lambda.CompileFast(ifFastFailedReturnNull: false);

try
{
    var result = func();

    if (result is null)
    {
        Console.WriteLine($"the result is null while it should have been not null");
        return;
    }

    
    Console.WriteLine($"Calling ping on broken object.");
    
    // virtual call ends on fault
    result.Ping();
    
}
catch (Exception ex)
{
    Console.WriteLine($"Caught managed exception: {ex}");
}


internal interface ICustomObject
{
    void Ping();
}

internal class CustomObject : ICustomObject
{
    public static ICustomObject operator ==(double left, CustomObject right)
        => new CustomObject();

    public static ICustomObject operator !=(double left, CustomObject right)
        => new CustomObject();

    public void Ping()
    {
        Console.WriteLine("pong");
    }
}

Output

lambda: () => (1 == new CustomObject())
Baseline path prints pong on following line
pong
Calling ping on broken object.
Fatal error.
Internal CLR error. (0x80131506)

Expected

Same behavior as Expression.Compile(): prints pong and exists normally

Activity

  1. jogibear9988 commented on Oct 22, 2026

    @jogibear9988
    Contributor

    Verified against current dadhi/master (3aefb5e5): I reproduced the reported fatal CLR error (0x80131506) using the x64 .NET 10.0.9 host. The emitted IL uses ceq and returns its integer result as ICustomObject; it never calls the interface-returning op_Equality. With reversed operands it can instead emit an invalid object.Equals call.

    Draft PR #558 already fixes the reported case, tested at its current head ae4d9a55. It honors BinaryExpression.Method before primitive comparison handling and preserves the method's actual return type. The relevant existing change is e37ba300.

    I added a local regression covering ==, !=, both operand orders, and Default / NoInvocationLambdaInlining / EnableDelegateDebugInfo. It fails against current master and passes against #558 for both Standard and LightExpression on .NET 8/10, ARM64/x64. The isolated original repro also crashes against master in the 64-bit Framework process and succeeds with #558. The modern regression checks the selected operator call before invocation so a corrupt delegate cannot terminate the normal test runner.

    These checks used Windows 11 ARM64 with the installed x64 .NET host under emulation, SDK 10.0.301 and runtime 10.0.9, rather than the reporter's exact SDK. Strict CompileFast(true) was used to ensure passing results came from FEC rather than System fallback. This is a valid expression, so it should be fixed upstream; Expression.Compile() remains a working workaround while #558 is pending.

  2. jogibear9988 commented on Oct 22, 2026

    @jogibear9988
    Contributor

    Created a separate, focused fix for this issue: draft PR #562 — #562.

    It is based directly on current master (3aefb5e5) and does not depend on the broader #558 changes. The fix honors the selected non-lifted comparison method and its actual return type, with regression coverage for the reported interface-returning operators, both operand orders, evaluation order, ignored results, and explicit comparison methods. Production changes are limited to 36 added lines, without context-layout changes or a broad refactoring.

    The targeted checks pass for Standard and LightExpression on .NET 8/10 with ARM64 and x64 hosts, and the isolated original repro passes on .NET Framework. Full .NET 10 ARM64, .NET 8 x64, and Framework suites also pass. The full .NET 8 ARM64 suite still encounters an unrelated float post-increment crash, reproduced with identical IL on clean master and the fix; this is documented in the PR.

  3. added a commit that references this issue on Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions