dadhi / FastExpressionCompiler

Fast Compiler for C# Expression Trees and the lightweight LightExpression alternative. Diagnostic and code generation tools for the expressions.
MIT License
1.14k stars 81 forks source link

[bug] InvalidProgramException when having TryCatch + Default in Catch #422

Open kicsiede opened 3 weeks ago

kicsiede commented 3 weeks ago

Hello,

I have an expression where using a deep accessor i try to determine whether a certain member is accessible (or it has a null value in the chain), however CompileFast compiles an invalid program. (running 32/64bit .net framework 4.7+) Here is a simplified version of the expression, I wasn't able to narrow down the root cause anymore:

var pEntity = Expression.Parameter(typeof(Tuple<object>));
var lastInstanceAccessor = Expression.PropertyOrField(pEntity, "Item1");

var expr = Expression.Lambda<Func<Tuple<object>, bool>>(Expression.Equal(Expression.Constant(null),
    Expression.TryCatch(lastInstanceAccessor, [Expression.Catch(typeof(NullReferenceException), Expression.Default(lastInstanceAccessor.Type))])), pEntity);

var shouldReturnTrue = expr.Compile().Invoke(null); // Works

var shouldReturnTrueButInvalidProgram = expr.CompileFast().Invoke(null); // Throws
dadhi commented 3 weeks ago

Thanks for finding. I will check.

kicsiede commented 2 weeks ago

FYI I tried working around the problem by abandoning the try-catch approach, and testing each member against null, and early returning in an Expression.IfThen + Expression.Return, but I get InvalidProgramException again for this totally different structure, so I would guess the problem may lie in returning values from block-like expressions, or how these expressions interact with the outer expression. (for example if you remove the outer Equal in the example above, it seems to work - I mean it's neither Equal nor TryCatch causing the problem alone, but when they are composed) Just a hint though, I haven't tracked down this second error.

kicsiede commented 6 days ago

I want to add that I've run into a situation, where there is no InvalidProgramException thrown, but the compiled program is simply wrong. Although this is also related to the above scenario but with more nesting. I am setting the (sub)properties of an object with deep accessors, where I check for null values with the technique above, and ignore those properties that i cannot reach. Maybe the extra depth makes the return point valid, but still incorrect.

dadhi commented 6 days ago

@kicsiede Coming back to it. Let's see.