Skip to content

Incomplete pattern detection for error 1007: Return goto from TryCatch with Assign generates invalid IL instead of failing gracefully #495

@bfarmer67

Description

@bfarmer67

I found an issue with switch expressions that I wanted to report.

Problem

FastExpressionCompiler has incomplete pattern detection for a documented unsupported pattern: Return gotos from TryCatch blocks (error 1007: NotSupported_Try_GotoReturnToTheFollowupLabel).

When the Return value contains an assignment expression like Return(label, Assign(...)), FEC fails to detect it as unsupported and generates invalid IL instead of triggering graceful fallback to System compiler.

Impact: CompileFast() returns a delegate with corrupted IL that throws InvalidProgramException at runtime. The documented fallback mechanism (CompileFast(ifFastFailedReturnNull: true) ?? lambda.Compile()) doesn't work because FEC doesn't detect the pattern and never returns null or throws NotSupportedExpressionException.

Context

FEC intentionally does not support Return gotos from TryCatch blocks and has documented error code 1007 for this limitation: "Goto of the Return kind from the TryCatch is not supported".

The detection logic correctly rejects simple cases like Return(label, variable) inside TryCatch, but fails to detect variants where the Return value is a compound expression containing assignments:

  • Return(label, Assign(variable, value))
  • Return(label, Block(Assign(...), value))
  • Return(label, Coalesce(value, Assign(...)))
  • etc.

Real-world impact: This pattern appears in expression tree transformation libraries that perform lowering/rewriting, particularly for async state machine generation. During lowering, Return expressions are commonly rewritten to assign a result variable before jumping to the return label (e.g., transforming Return(label, value) into Return(label, Assign(_result, value))). Libraries performing such transformations cannot reliably use FEC because the fallback mechanism fails - they get InvalidProgramException at runtime instead of gracefully falling back to System compiler.

Reproduction

using System;
using System.Linq.Expressions;
using FastExpressionCompiler;
using NUnit.Framework;
using static System.Linq.Expressions.Expression;

[Test]
public void ReturnGotoFromTryCatchWithAssign_ShouldBeDetectedAsError1007()
{
    // Arrange: Build expression with Return(label, Assign(...)) inside TryCatch
    var variable = Variable(typeof(object), "var");
    var finalResult = Variable(typeof(object), "finalResult");
    var returnLabel = Label(typeof(object), "return");
    var exceptionParam = Parameter(typeof(Exception), "ex");

    var block = Block(
        new[] { variable, finalResult },
        TryCatch(
            Block(
                typeof(void),
                Assign(variable, Constant("hello", typeof(object))),
                IfThen(
                    NotEqual(variable, Constant(null, typeof(object))),
                    // FEC should detect this as error 1007 and reject it
                    Return(returnLabel, Assign(finalResult, variable), typeof(object))
                ),
                Assign(finalResult, Constant("default", typeof(object))),
                Label(returnLabel, Constant("fallback", typeof(object)))
            ),
            Catch(exceptionParam, Empty())
        ),
        finalResult
    );

    var lambda = Lambda<Func<object>>(block);

    // Act: CompileFast should throw NotSupportedExpressionException or return null
    var compiled = lambda.CompileFast(ifFastFailedReturnNull: true);

    // Expected: compiled should be null (pattern detected as unsupported)
    // Actual: compiled is non-null with invalid IL
    Assert.IsNotNull(compiled); // This passes but shouldn't - FEC doesn't detect the pattern

    // Invoking the delegate throws InvalidProgramException
    Assert.Throws<InvalidProgramException>(() => compiled());
}

Expected Behavior

CompileFast() should:

  1. Detect Return(label, Assign(...)) inside TryCatch as error 1007
  2. Throw NotSupportedExpressionException or return null
  3. Allow graceful fallback to Expression.Compile()

Actual Behavior

CompileFast():

  1. Does NOT detect the pattern as unsupported
  2. Returns a non-null delegate with invalid IL
  3. Fallback mechanism never triggers (because delegate is non-null and no exception thrown)
  4. Runtime throws InvalidProgramException when delegate is invoked

This breaks the documented fallback strategy - code using CompileFast(ifFastFailedReturnNull: true) ?? lambda.Compile() still fails because FEC returns a non-null delegate with bad IL.

Environment

  • FastExpressionCompiler: 5.3.0
  • .NET: 8.0, 9.0, 10.0
  • OS: Windows 11 Pro 10.0.26200

Related Patterns

These variations of the same pattern also fail:

  • Return(label, Block(Assign(var, value), value)) inside TryCatch
  • Return(label, Call(MethodThatAssigns, ref var, value)) inside TryCatch (ref parameters)
  • Return(label, Coalesce(value, Assign(var, default))) inside TryCatch

All should be detected as error 1007 (NotSupported_Try_GotoReturnToTheFollowupLabel) and either throw NotSupportedExpressionException or return null to trigger fallback to System compiler. Without that, they generate invalid IL that throws InvalidProgramException at runtime, preventing the fallback mechanism from working.

As always - thank you for FEC. We are really appreciative of all your work.

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