.netcililasm

MSIL assembly: Unexpected OutOfMemoryException in class constructor


I'm writing a compiler that outputs .NET assemblies (using Mono.Cecil, although I don't believe Cecil is relevant to this problem). One of the compiler features requires that a class will have a compiler-generated nested class with some support methods; the outer class has a static field so each class effectively has a singleton referencing an object of the nested class. To initialise this, any such class has a class constructor to create an instance of the nested class and store it in the field.

Problem: When my outer class is a generic class, I make the nested class generic too (since it needs to create objects of the outer class). The generated IL passes through peverify fine, and looks fine to my eye, but the class constructor creating an instance of the nested class throws OutOfMemoryException at runtime.

I've disassembled the assembly with ildasm, slimmed it down to a minimal reproduction (unfortunately still ~180 lines of IL), and verifying that compiling the IL with ilasm produces an exe that still exhibits the problem.

Debugging with Visual Studio or MDbg hasn't enlightened me - I just get OutOfMemoryException without indication why. I'm willing to believe my IL is invalid somehow, but peverify doesn't indicate a problem. Can anyone suggest what the issue is?

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly extern System
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly Repro1
{
  .ver 0:0:0:0
}
.module Repro1
// MVID: {7DA983B6-F5EA-4ACB-8443-C29F25ADDCD4}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x016E0000

.class public abstract auto ansi sealed Repro1
       extends [mscorlib]System.Object
{
  .method assembly static void  '<NemeaProgram>'() cil managed
  {
    .entrypoint
    // Code size       6 (0x6)
    .maxstack  0
    IL_0000:  call       void Rep2::Go()
    IL_0005:  ret
  } // end of method Repro1::'<NemeaProgram>'

} // end of class Repro1

.class public abstract auto ansi sealed Rep1
       extends [mscorlib]System.Object
{
  .class auto ansi nested public TRep
         extends [mscorlib]System.Object
  {
    .class auto ansi nested public '__%NemeaVType'
           extends [mscorlib]System.Object
    {
      .method public hidebysig specialname rtspecialname 
              instance void  .ctor() cil managed
      {
        // Code size       7 (0x7)
        .maxstack  8
        IL_0000:  ldarg.0
        IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
        IL_0006:  ret
      } // end of method '__%NemeaVType'::.ctor

      .method public newslot virtual instance class Rep1/TRep 
              Create(string Foo) cil managed
      {
        // Code size       10 (0xa)
        .maxstack  8
        IL_0000:  ldarg      Foo
        IL_0004:  newobj     instance void Rep1/TRep::.ctor(string)
        IL_0009:  ret
      } // end of method '__%NemeaVType'::Create

    } // end of class '__%NemeaVType'

    .field famorassem string FData
    .field public static class Rep1/TRep/'__%NemeaVType' '__%NemeaVTypeI'

    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(string Foo) cil managed
    {
      // Code size       17 (0x11)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  ldarg.0
      IL_0007:  ldarg      Foo
      IL_000b:  stfld      string Rep1/TRep::FData
      IL_0010:  ret
    } // end of method TRep::.ctor

    .method privatescope specialname rtspecialname static 
            void  '.cctor$PST0600004C'() cil managed
    {
      // Code size       11 (0xb)
      .maxstack  8
      IL_0000:  newobj     instance void Rep1/TRep/'__%NemeaVType'::.ctor()
      IL_0005:  stsfld     class Rep1/TRep/'__%NemeaVType' Rep1/TRep::'__%NemeaVTypeI'
      IL_000a:  ret
    } // end of method TRep::.cctor

  } // end of class TRep

  .class auto ansi nested public TItem
         extends [mscorlib]System.Object
  {
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor() cil managed
    {
      // Code size       7 (0x7)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  ret
    } // end of method TItem::.ctor

  } // end of class TItem

} // end of class Rep1

.class public abstract auto ansi sealed Rep2
       extends [mscorlib]System.Object
{
  .class auto ansi nested public TRep<(Rep1/TItem) T>
         extends Rep1/TRep
  {
    .class auto ansi nested public '__%NemeaVType'<(Rep1/TItem) T_vt>
           extends Rep1/TRep/'__%NemeaVType'
    {
      .method public hidebysig specialname rtspecialname 
              instance void  .ctor() cil managed
      {
        // Code size       7 (0x7)
        .maxstack  8
        IL_0000:  ldarg.0
        IL_0001:  call       instance void Rep1/TRep/'__%NemeaVType'::.ctor()
        IL_0006:  ret
      } // end of method '__%NemeaVType'::.ctor

      .method public virtual instance class Rep1/TRep 
              Create(string Foo) cil managed
      {
        // Code size       10 (0xa)
        .maxstack  8
        IL_0000:  ldarg      Foo
        IL_0004:  newobj     instance void class Rep2/TRep<!T_vt>::.ctor(string)
        IL_0009:  ret
      } // end of method '__%NemeaVType'::Create

    } // end of class '__%NemeaVType'

    .field public static class Rep2/TRep/'__%NemeaVType'<!T> '__%NemeaVTypeI'
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(string Foo) cil managed
    {
      // Code size       22 (0x16)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldarg      Foo
      IL_0005:  call       instance void Rep1/TRep::.ctor(string)
      IL_0015:  ret
    } // end of method TRep::.ctor

    .method privatescope specialname rtspecialname static 
            void  '.cctor$PST06000055'() cil managed
    {
      // Code size       11 (0xb)
      .maxstack  8
      IL_0000:  newobj     instance void class Rep2/TRep/'__%NemeaVType'<!T>::.ctor()
      IL_0005:  stsfld     class Rep2/TRep/'__%NemeaVType'<!T> Rep2/TRep::'__%NemeaVTypeI'
      IL_000a:  ret
    } // end of method TRep::.cctor

  } // end of class TRep

  .method public static void  Go() cil managed
  {
    // Code size       29 (0x1d)
    .maxstack  1
    .locals init ([0] class Rep1/TRep R)
    IL_0000:  ldstr      "Oi"
    IL_0005:  newobj     instance void class Rep2/TRep<class Rep1/TItem>::.ctor(string)
    IL_000a:  stloc      R
    IL_001c:  ret
  } // end of method Rep2::Go

} // end of class Rep2

Solution

  • So it turns out the problem isn't anything to do with the generic declarations - the declaration of the nested class is fine. The name of the generic parameter doesn't match the generic parameter of the outer class, but this is just a convention that the C# compiler (understandably) adheres to when it's propagating generic parameters to nested classes.

    The problem is simply the

     IL_0005:  stsfld     class Rep2/TRep/'__%NemeaVType'<!T> Rep2/TRep::'__%NemeaVTypeI'
    

    line - it's not valid, because it's trying to access a field on the Rep2/TRep class, which is generic, without providing any type arguments. Changing this to

     IL_0005:  stsfld     class Rep2/TRep/'__%NemeaVType'<!0> Rep2/TRep<!T>::'__%NemeaVTypeI'
    

    resolves all the issues.

    I still hold that peverify could have highlighted this, because it's not really valid - it couldn't possibly execute correctly since it's not a valid field reference. It's also a little annoying that you get an OutOfMemoryException when executing the code, rather than anything with more details.