5

I'm generating some code via Cecil. The code generates without error, but when I try to load the assembly I get:

An unhandled exception of type 'System.InvalidProgramException' occurred in DataSerializersTest.exe

Additional information: Common Language Runtime detected an invalid program.

Here is the generated IL:

.method public static 
    class Data.FooData Read (
        class [mscorlib]System.IO.BinaryReader input
    ) cil managed 
{
    // Method begins at RVA 0x3a58
    // Code size 60 (0x3c)
    .maxstack 3
    .locals (
        [0] class Data.FooData,
        [1] valuetype [System.Runtime]System.Nullable`1<int32>
    )

    IL_0000: newobj instance void Data.FooData::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: ldarg.0
    IL_0008: callvirt instance bool [System.IO]System.IO.BinaryReader::ReadBoolean()
    IL_000d: ldc.i4.0
    IL_000e: ceq
    IL_0010: brtrue.s IL_001f

    IL_0012: ldarg.0
    IL_0013: callvirt instance int32 [System.IO]System.IO.BinaryReader::ReadInt32()
    IL_0018: newobj instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
    IL_001d: br.s IL_0028

    IL_001f: nop
    IL_0020: ldloca.s 1
    IL_0022: initobj valuetype [System.Runtime]System.Nullable`1<int32>

    IL_0028: nop
    IL_0029: callvirt instance void Data.FooData::set_I(valuetype [System.Runtime]System.Nullable`1<int32>)
    IL_002e: ldloc.0
    IL_002f: ldarg.0
    IL_0030: callvirt instance string [System.IO]System.IO.BinaryReader::ReadString()
    IL_0035: callvirt instance void Data.FooData::set_Name(string)
    IL_003a: ldloc.0
    IL_003b: ret
} // end of method FooDataReader::Read

The equivalent C# code looks like this:

public static FooData Read(BinaryReader input)
{
    var result = new FooData();

    if (input.ReadBoolean())
    {
        result.I = input.ReadInt32();
    }
    else
    {
        result.I = null;
    }

    result.Name = input.ReadString();

    return result;
}

I've worked through the IL several times and it makes sense to me. It's not the same as what the C# compiler produces for the above C# code, but I'd like to understand what's wrong with the IL I've generated. Can anyone tell me?

me--
  • 1,978
  • 1
  • 22
  • 42
  • Can you not also add the code that the compiler generates for the same, rather than forcing everyone reading the question from going through the same exercise? – Damien_The_Unbeliever Jan 02 '14 at 11:09
  • @Damien_The_Unbeliever: the C# compiler takes a rather different approach using several local variables instead of the stack - different enough for me to think it was distracting to my question of why my approach is invalid. However, I will post it next time I'm at my dev machine. – me-- Jan 02 '14 at 11:30
  • 2
    Run PEVerify on it. It will tell you the exact problem! – leppie Jan 02 '14 at 11:48
  • 1
    The IL was generated as though the `I` member is a static property of FooData. It sure doesn't look like one in the C# snippet. And the argument to set_I() is missing, stack imbalance there. You *really* want to use the C# compiler to get a good start on this code. – Hans Passant Jan 02 '14 at 12:01
  • @HansPassant - the `FooData` instance is placed on the stack at `IL_0006` and left alone until that final `set_I` call, is it not? – Damien_The_Unbeliever Jan 02 '14 at 13:04

1 Answers1

7

I think I've spotted it. initobj does not leave the newly initialized object on the evaluation stack - whereas newobj does. So the stacks are unbalanced when IL_0028 is reached - if we've gone the newobj route, then we have two items on the stack (FooData and Nullable<int>). If we've gone the initobj route, then all we have is the FooData object reference on the stack. You need to reload local 1:

IL_0022: initobj valuetype [System.Runtime]System.Nullable`1<int32>
         ldloc.1
IL_0028: nop
Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Thanks - that was indeed the problem. Interestingly, PEVerify didn't even flag it as an issue. However, PEVerify does complain about "initlocals must be set for verifiable methods with one or more local variables". Not sure what to make of that, but I can load and consume the code just fine now. – me-- Jan 03 '14 at 01:22