-
-
Notifications
You must be signed in to change notification settings - Fork 93
Description
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:
- Detect
Return(label, Assign(...))inside TryCatch as error 1007 - Throw
NotSupportedExpressionExceptionor returnnull - Allow graceful fallback to
Expression.Compile()
Actual Behavior
CompileFast():
- Does NOT detect the pattern as unsupported
- Returns a non-null delegate with invalid IL
- Fallback mechanism never triggers (because delegate is non-null and no exception thrown)
- Runtime throws
InvalidProgramExceptionwhen 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 TryCatchReturn(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.