Search code examples
c#visual-studio-2010visual-studio-2012compiler-bug

Overriding generic iterator results in BadImageFormatException when compiled with Visual Studio 2010


tl;dr:

  • Overriding a generic iterator method in a constructed derived class results in a BadImageFormatException being thrown when compiled with Visual Studio 2010 (VS2010), regardless of .NET version (2.0, 3.0, 3.5 or 4), platform or configuration. The problem is not reproducible in Visual Studio 2012 (VS2012) and above.
  • The contents of the base method (provided the source compiles) is irrelevant as it is not executed.

How can this be avoided?


Description of problem

When stepping into the in in Main in the code in the MVCE below (which would normally move the execution to the iterator method), a BadImageFormatException is thrown when the code is compiled in Visual Studio 2010:

BadImageFormatException in VS2010

but not in Visual Studio 2012 and above:

BadImageFormatException in VS2012


MCVE

public class Program
{
    public static void Main(string[] args)
    {
        foreach ( var item in new ScrappyDoo().GetIEnumerableItems() )
            Console.WriteLine(item.ToString());
    }
}

public class ScoobyDoo<T>
    where T : new()
{
    public virtual IEnumerable<T> GetIEnumerableItems()
    {
        yield return new T();
    }
}

public class ScrappyDoo : ScoobyDoo<object>
{
    public override IEnumerable<object> GetIEnumerableItems()
    {
        foreach ( var item in base.GetIEnumerableItems() )
            yield return item;
    }
}

Things of note

  • When inspecting the code with ILSpy, the compiled IL for ScrappyDoo.GetIEnumerableItems was the same for both the VS2010 and VS2012 binaries:

    .method public hidebysig virtual 
        instance class [mscorlib]System.Collections.Generic.IEnumerable`1<object> GetIEnumerableItems () cil managed 
    {
        // Method begins at RVA 0x244c
        // Code size 21 (0x15)
        .maxstack 2
        .locals init (
            [0] class MysteryMachine.ScrappyDoo/'<GetIEnumerableItems>d__0',
            [1] class [mscorlib]System.Collections.Generic.IEnumerable`1<object>
        )
    
        IL_0000: ldc.i4.s -2
        IL_0002: newobj instance void MysteryMachine.ScrappyDoo/'<GetIEnumerableItems>d__0'::.ctor(int32)
        IL_0007: stloc.0
        IL_0008: ldloc.0
        IL_0009: ldarg.0
        IL_000a: stfld class MysteryMachine.ScrappyDoo MysteryMachine.ScrappyDoo/'<GetIEnumerableItems>d__0'::'<>4__this'
        IL_000f: ldloc.0
        IL_0010: stloc.1
        IL_0011: br.s IL_0013
    
        IL_0013: ldloc.1
        IL_0014: ret
    } // end of method ScrappyDoo::GetIEnumerableItems
    
  • Likewise, the IL for the Main method is the same for both VS2010 and VS2012 binaries:

    .method public hidebysig static 
        void Main (
            string[] args
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 69 (0x45)
        .maxstack 2
        .entrypoint
        .locals init (
            [0] object item,
            [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<object> CS$5$0000,
            [2] bool CS$4$0001
        )
    
        IL_0000: nop
        IL_0001: nop
        IL_0002: newobj instance void MysteryMachine.ScrappyDoo::.ctor()
        IL_0007: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerable`1<!0> class MysteryMachine.ScoobyDoo`1<object>::get_GetIEnumerableItems()
        IL_000c: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<object>::GetEnumerator()
        IL_0011: stloc.1
        .try
        {
            IL_0012: br.s IL_0027
            // loop start (head: IL_0027)
                IL_0014: ldloc.1
                IL_0015: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<object>::get_Current()
                IL_001a: stloc.0
                IL_001b: ldloc.0
                IL_001c: callvirt instance string [mscorlib]System.Object::ToString()
                IL_0021: call void [mscorlib]System.Console::WriteLine(string)
                IL_0026: nop
    
                IL_0027: ldloc.1
                IL_0028: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
                IL_002d: stloc.2
                IL_002e: ldloc.2
                IL_002f: brtrue.s IL_0014
            // end loop
    
            IL_0031: leave.s IL_0043
        } // end .try
        finally
        {
            IL_0033: ldloc.1
            IL_0034: ldnull
            IL_0035: ceq
            IL_0037: stloc.2
            IL_0038: ldloc.2
            IL_0039: brtrue.s IL_0042
    
            IL_003b: ldloc.1
            IL_003c: callvirt instance void [mscorlib]System.IDisposable::Dispose()
            IL_0041: nop
    
            IL_0042: endfinally
        } // end handler
    
        IL_0043: nop
        IL_0044: ret
    } // end of method Program::Main
    
  • In the binaries compiled by VS2012, there is a method, <>n__FabricatedMethod4, which doesn't appear in VS2010:

    VS2012:

    FabricatedMethod in Visual Studio 2012

    VS2010:

    FabricatedMethod in Visual Studio 2010

    ILSpy is unable to inspect the IL for the 'broken' method in the VS2010 binaries, and encounters the following exception:

    System.NullReferenceException: Object reference not set to an instance of an object.
       at ICSharpCode.Decompiler.Disassembler.DisassemblerHelpers.WriteTo(TypeReference type, ITextOutput writer, ILNameSyntax syntax)
       at ICSharpCode.Decompiler.Disassembler.DisassemblerHelpers.WriteTo(TypeReference type, ITextOutput writer, ILNameSyntax syntax)
       at ICSharpCode.Decompiler.Disassembler.ReflectionDisassembler.DisassembleMethodInternal(MethodDefinition method)
       at ICSharpCode.ILSpy.TextView.DecompilerTextView.DecompileNodes(DecompilationContext context, ITextOutput textOutput)
       at ICSharpCode.ILSpy.TextView.DecompilerTextView.<>c__DisplayClass31_0.<DecompileAsync>b__0()
    

    Likewise, it is unable to view the contents of the ScrappyDoo.GetIEnumerableItems method as C# and shows a similar exception:

    ICSharpCode.Decompiler.DecompilerException: Error decompiling System.Collections.Generic.IEnumerable`1<System.Object> MysteryMachine.ScrappyDoo::GetIEnumerableItems()
     ---> System.NullReferenceException: Object reference not set to an instance of an object.
       // stack trace elided
    
  • When inspecting the binaries with DotPeek, the decompiled code for the VS2010- and VS2012-compiled code differs in the expression of the foreach statement:

    VS2010:

    // ISSUE: reference to a compiler-generated method
    foreach (object obj in (IEnumerable<object>) this.<>n__FabricatedMethod4())
      yield return obj;
    

    VS2012 (note that the decompiled C# is the same as the source, as expected):

    foreach (object obj in base.GetIEnumerableItems())
      yield return obj;
    
  • The problem is not resolved by changing the method to a property, or by adding more logic into either the base or the override.

  • Changing the base method to return IEnumerable<object> instead of IEnumerable<T> fixes the problem (in this contrived case), but this is not an acceptable solution.

  • The problem occurs when targeting .NET 2.0, .NET 3.0, .NET 3.5, and .NET 4 in VS2010. When compiled with VS2012 and above, the target framework version is irrelevant and the code behaves as expected.

  • I'm aware that Visual Studio doesn't compile code - it just invokes MSBuild (or Roslyn), but this problem is still an issue on a machine with VS2010 and VS2012 installed: when running the code in VS2010 the problem persists, and when running in VS2012 it doesn't. Upon setting the build output verbosity to Diagnostic, I found that both VS2010 and VS2012 are using the same MSBuild binaries at

    C:\Windows\Microsoft.NET\Framework\v4.0.30319
    
  • The problem does not appear in VS2015 (using Roslyn to compile) - the IL is different, but I guess that's to be expected.

  • I need to use Visual Studio 2010 as, where I work, we do some development on Windows XP which only supports 2010 and below.

  • PEVerify gives the following output for the VS2010-compiled code:

    > peverify MysteryMachine2010.exe
    
    Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.0
    Copyright (c) Microsoft Corporation.  All rights reserved.
    
    [IL]: Error: [MysteryMachine2010.exe : MysteryMachine.ScrappyDoo::<>n__FabricatedMethod4]  [HRESULT 0x8007000B] - An attempt was made to load a program with an incorrect format.
    
    [IL]: Error: [MysteryMachine2010.exe : MysteryMachine.ScrappyDoo+<getIEnumerableItems>d__0::MoveNext]  [HRESULT 0x8007000B] - An attempt was made to load a program with an incorrect format.
    
    2 Error(s) Verifying MysteryMachine2010.exe
    

    whereas for binaries compiled through VS2012 and above, the result is, as expected:

    > peverify "MysteryMachine2012.exe"
    
    Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.0
    Copyright (c) Microsoft Corporation.  All rights reserved.
    
    All Classes and Methods in MysteryMachine2012.exe Verified.
    
  • When running the VS2010-compiled code from the command prompt results in the following output:

    > MysteryMachine2010.exe
    
    Unhandled Exception: System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)
       at MysteryMachine.ScrappyDoo.<getIEnumerableItems>d__0.MoveNext()
       at MysteryMachine.Program.Main(String[] args) in MysteryMachine\Program.cs:line 11
    

My actual question

Does anybody know why this is, and how it can be avoided? For my actual use case, the iterator in the base has no items in it so I made the base method abstract and made all the derived classes override it, but that could change at any point, rendering the hack fix useless.


Solution

  • Three suggestions for working around this that don't require abandoning iterators altogether, all relying on getting VS to see past the "discrepancy" of the base and derived return types, which seems to be the source of trouble.

    Move the iterator implementation to a method that isn't virtual/overridden

    public override IEnumerable<object> GetIEnumerableItems()
    {
        return getIEnumerableItems();
    }
    
    IEnumerable<object> getIEnumerableItems() 
    {
        foreach ( var item in base.GetIEnumerableItems() )
            yield return item;
    }
    

    Move the base invocation out of the iterator, obvious way

    public override IEnumerable<object> GetIEnumerableItems()
    {
        foreach ( var item in baseItems() )
        {
            yield return item;
        }
    }
    
    IEnumerable<object> baseItems() 
    {
        return base.GetIEnumerableItems();
    }
    

    This could potentially be thwarted by inlining, but I don't think the compiler will bother (traditionally such things are left to the IL level).

    Move the base invocation out of the iterator, involved way

    public override IEnumerable<object> GetIEnumerableItems()
    {
        return getIEnumerableItems(base.GetIEnumerableItems());
    }
    
    IEnumerable<object> getIEnumerableItems(IEnumerable<object> baseItems) 
    {
        foreach ( var item in baseItems )
            yield return item;
    }
    

    Disclaimer: none of this is tested, for lack of a VS 2010 installation.