dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.04k stars 4.03k forks source link

Calling a generic local function with a dynamic parameter produces a BadImageFormatException #21317

Closed j2jensen closed 7 years ago

j2jensen commented 7 years ago

Version Used: Roslyn 2.0

Steps to Reproduce:

  1. Write the following lines of code in a method
    GenericLocal((dynamic)"");
    void GenericLocal<T>(T val) {}
  2. Invoke the method

Expected Behavior:

GenericLocal should be invoked, with string as T.

Actual Behavior:

BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)


I described this on a StackOverflow post and the general consensus was that I'd found a bug. Note that the stacktrace shows the exception being thrown inside the method with this code, but no code actually gets run before the exception occurs.

jcouv commented 7 years ago

FYI @agocke

khyperia commented 7 years ago

Tried fixing this bug this morning, then just realized it's far more complex than what might be guessed. Here was my description for my original fix for this issue, because it took a good chunk of my sanity to debug through, and I don't want anyone else to do the same:

diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
index a5a3121d29..792e30ec2f 100644
--- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
@@ -2801,7 +2801,8 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind
             EffectiveParameters effectiveParameters;
             if (member.Kind == SymbolKind.Method && (method = (MethodSymbol)(Symbol)member).Arity > 0)
             {
-                if (typeArgumentsBuilder.Count == 0 && arguments.HasDynamicArgument && !inferWithDynamic)
+                var isLocalFunction = ((MethodSymbol)(Symbol)member).MethodKind == MethodKind.LocalFunction;
+                if (typeArgumentsBuilder.Count == 0 && arguments.HasDynamicArgument && !inferWithDynamic && !isLocalFunction)
                 {
                     // Spec 7.5.4: Compile-time checking of dynamic overload resolution:
                     // * First, if F is a generic method and type arguments were provided,

However, there is a large issue with this solution: the generic type inferred is object, not the type of the object at runtime.

private static void Method<T>(T x) => Console.WriteLine(typeof(T).Name);
private static void M1()
{
    Method((dynamic)2); // prints "Int32"
}
private static void M2()
{
    void LocFunc<T>(T x) => Console.WriteLine(typeof(T).Name);
    LocFunc((dynamic)2); // with this fix, prints "Object". Should print "Int32".
}

I don't know if this is possible to fix for local functions - I'm not sure how dynamic generics get constructed, and if it's possible to do with local functions.

julealgon commented 5 years ago

Are there any plans to make this work in the near future?

Just disabling the stuff doesn't sound like the cleanest or most orthogonal solution...

I wanted to call a generic local function today using a dynamic parameter to reduce the amount of code (I could get rid of a private generic method that way) and was greeted with the compilation error.