dadhi/FastExpressionCompiler

Value can not be null(parametr 'meth')

EgoPingvina opened this issue · 17 comments

This is a very strange error that is happening before my eyes for the second time (the first time I was unable to save any information).
Occurs on a method call:

internal static Func<TIn, TOut> AsFunc(Expression<Func<TIn, TOut>> expression)
       => Cache.GetOrAdd(expression, x => x.CompileFast());

where Cache is private static readonly ConcurrentDictionary<Expression<Func<TIn, TOut>>, Func<TIn, TOut>>.

Callstack from output:

System.ArgumentNullException: Value cannot be null. (Parameter 'meth')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at System.Reflection.Emit.DynamicILGenerator.Emit(OpCode opcode, MethodInfo meth)
   at FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmitConvert(UnaryExpression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent) in /_/src/FastExpressionCompiler/FastExpressionCompiler.cs:line 2892
   at FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmit(Expression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent, Int32 byRefIndex) in /_/src/FastExpressionCompiler/FastExpressionCompiler.cs:line 1847
   at FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmitComparison(Expression left, Expression right, ExpressionType nodeType, Type exprType, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent) in /_/src/FastExpressionCompiler/FastExpressionCompiler.cs:line 4784
   at FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmit(Expression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent, Int32 byRefIndex) in /_/src/FastExpressionCompiler/FastExpressionCompiler.cs:line 1900
   at FastExpressionCompiler.ExpressionCompiler.TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList`1 paramExprs, Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags) in /_/src/FastExpressionCompiler/FastExpressionCompiler.cs:line 503
   at FastExpressionCompiler.ExpressionCompiler.CompileFast[T1,R](Expression`1 lambdaExpr, Boolean ifFastFailedReturnNull, CompilerFlags flags) in /_/src/FastExpressionCompiler/FastExpressionCompiler.cs:line 203
   at Test.Extensions.ExpressionEx.CompiledExpressions`2.<>c.<AsFunc>b__1_0(Expression`1 x) in C:\dev\Test\Extensions\ExpressionEx.cs:line 85

Moreover, whether last time or this time, if you call the same section of code again without restarting the solution, then everything works successfully.

Is this some kind of floating bug in the library? Or do you have any ideas about what mistake I am making that can lead to this behaviour?

dadhi commented

Hi @EgoPingvina

  • What version of FEC are you using; on what target?
  • What are the example expressions where the exception occurs, e.g. you may use expr.ToExpressionString() to see the final expression.

For now, given that the ex happened in TryEmitConvert with the meth method being used there.
It is either the conversion method itself or the Nullable methods, like GetValueOrDefault, HasValue, get_Value.
So I need to know more about kind of operands in the convert for your case.

Hi @EgoPingvina

  • What version of FEC are you using; on what target?
  • What are the example expressions where the exception occurs, e.g. you may use expr.ToExpressionString() to see the final expression.

For now, given that the ex happened in TryEmitConvert with the meth method being used there. It is either the conversion method itself or the Nullable methods, like GetValueOrDefault, HasValue, get_Value. So I need to know more about kind of operands in the convert for your case.

  • FastExpressionCompiler version is 4.0.1
  • the final expression from expression.ToExpressionString():
var p = new ParameterExpression[1]; // the parameter expressions
var e = new Expression[6]; // the unique expressions
var expr = Lambda<System.Func<Test.Message, bool>>(
  e[0]=MakeBinary(ExpressionType.NotEqual,
    e[1]=Convert(
      e[2]=Property(
        p[0]=Parameter(typeof(Test.Message), "x"),
        typeof(Test.Message).GetTypeInfo().GetDeclaredProperty("UserType")),
      typeof(int?)),
    e[3]=Convert(
      e[4]=Field(
        e[5]=Constant(default(Test.Logic.MessageSpec.c__DisplayClass0_0)/*Please provide the non-default value for the constant!*/),
        typeof(Test.Logic.MessageSpec.c__DisplayClass0_0).GetTypeInfo().GetDeclaredField("type")),
      typeof(int?))),
  p[0 // (Test.Message x)
    ]);

property public UserType? UserType { get; set; } contains the value of the enum of the same name

Perhaps this is still a problem with enums, which was solved in the latest updates:

  1. #374
  2. #378
dadhi commented

Ok, so if you convert to ToCSharpString() it will be something like this?

(Test.Message x) => (int?)x.UserType != (int?)(UserType.FooBar)

First, not relevant to the issue - just a note to myself. But the constant probably should be used as literal constant and not put into a closure.
Second, it seems not related to the HasFlag, but rather to the nullables in general... they are big PITA.

I will create the test case for that and see what I find.

yes, x => x.UserType != type, where is UserType type and Test.Message x.

dadhi commented

Like this:

(Test.Message x) => (int?)x.UserType != (int?)(FooBar.type)

Just wondering, why is there lowercase .type ?

Because of type is the name of the method parameter. It's not a value of enum :) sorry for the confusion

dadhi commented

Ehhh, I don't understand. It seems like a Field to me:

      e[4]=Field(
        e[5]=Constant(default(Test.Logic.MessageSpec.c__DisplayClass0_0)/*Please provide the non-default value for the constant!*/),
        typeof(Test.Logic.MessageSpec.c__DisplayClass0_0).GetTypeInfo().GetDeclaredField("type")),
      typeof(int?))),

Could you actually run the ToCSharpString() on this thing and paste the output?

(Func<Message, bool>)((Message x) =>
    ((int?)x.UserType) != ((int?)default(MessageSpec.c__DisplayClass0_0)/*Please provide the non-default value for the constant!*/.type));
dadhi commented

@EgoPingvina Again about the type. Is it really of the int? type internally, or there are cases when it is not (just a plain int)?

It's always the UserType enum member

dadhi commented

@EgoPingvina
I have added the multiple tests (see the linked commits) with the multiple combination of nullable and not for UserType.
Everything is passing.
So maybe you will have an idea what is missing?

So far, the only thing I have discovered is a method for guaranteed reproduction: I need to restart the project and simultaneously call the method from two places. Only a clean run and multiple simultaneous invokes guarantee an exception throw. I haven't delved deep into your library's source code and I could be wrong, but perhaps non-thread-safe caches are being used somewhere or something?

dadhi commented

@EgoPingvina Hey, yeah. This is very helpful!
There are some caches involved. Will check the problems there.

@dadhi Hello! Did you manage to fix this bug?

dadhi commented

@EgoPingvina The v4.1.0 with the fix is out on NuGet

Thanks a lot for the notice!