Skip to content

PersistedAssemblyBuilder generates invalid IL #114097

Closed
@osen1

Description

@osen1

Description

PersistedAssemblyBuilder generates an invalid DLL under special circumstances. Replace PersistedAssemblyBuilder by Lokad.ILPack.AssemblyGenerator, and it works. I tried to reduce the code example to the minimum (see reproduction steps), but I'm still not sure what causes the problem.

I'm using an ILGenerator to write something that looks like that:

public static void Test()
{
if (false)
{
    while (Util.CCC())
    {
        int num = Util.NNN();
        if (3 == num)
        {
            Util.OOO();
        }
    }
}
TestClass b = TestClass.New();
Util.EEE(b);
}

public class TestClass : object
{
    public static TestClass New()
    {
        return new TestClass();
    }
}


public static class Util
{
    public static bool CCC()
    {
        return false;
    }

    public static void EEE(TestClass b)
    {
    }

    public static int NNN()
    {
        return 3;
    }

    public static void OOO()
    {
    }
}

Reproduction Steps

using System;
using System.Reflection;
using System.Reflection.Emit;

PersistedAssemblyBuilder persistedAssemblyBuilder = new PersistedAssemblyBuilder(new AssemblyName("Test"), typeof(object).Assembly);
Emit(persistedAssemblyBuilder);
persistedAssemblyBuilder.Save("Test.dll");

AssemblyBuilder assemblyBuilder2 = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.RunAndCollect);
Emit(assemblyBuilder2);
Lokad.ILPack.AssemblyGenerator ag = new Lokad.ILPack.AssemblyGenerator();
ag.GenerateAssembly(assemblyBuilder2, "Test_ILPack.dll");


static void Emit(AssemblyBuilder assemblyBuilder)
{
  ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Test");
  TypeBuilder typeBuilder = moduleBuilder.DefineType("Class1", TypeAttributes.Class | TypeAttributes.Public);
  MethodBuilder methb = typeBuilder.DefineMethod("Test", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), []);
  ILGenerator ilGenerator = methb.GetILGenerator();
  Label label0 = ilGenerator.DefineLabel();
  ilGenerator.Emit(OpCodes.Ldc_I4_0);
  ilGenerator.Emit(OpCodes.Ldc_I4_0);
  ilGenerator.Emit(OpCodes.Beq, label0);
  ilGenerator.BeginScope();
  Label label1 = ilGenerator.DefineLabel();
  Label label2 = ilGenerator.DefineLabel();
  ilGenerator.MarkLabel(label1);
  MethodInfo methodInfo = typeof(Util).GetMethod(nameof(Util.CCC), new Type[] { });
  ilGenerator.Emit(OpCodes.Call, methodInfo);
  ilGenerator.Emit(OpCodes.Ldc_I4, 0);
  ilGenerator.Emit(OpCodes.Beq, label2);
  methodInfo = typeof(Util).GetMethod(nameof(Util.NNN), new Type[] { });
  ilGenerator.Emit(OpCodes.Call, methodInfo);
  LocalBuilder lb1 = ilGenerator.DeclareLocal(typeof(System.Int32));
  Label label3 = ilGenerator.DefineLabel();
  EmitStLoc(ilGenerator, lb1.LocalIndex);
  ilGenerator.Emit(OpCodes.Ldc_I4_3);
  EmitLdLoc(ilGenerator, lb1.LocalIndex);
  ilGenerator.Emit(OpCodes.Ceq);
  ilGenerator.Emit(OpCodes.Ldc_I4_0);
  ilGenerator.Emit(OpCodes.Beq, label3);
  methodInfo = typeof(Util).GetMethod(nameof(Util.OOO), new Type[] { });
  ilGenerator.Emit(OpCodes.Call, methodInfo);
  ilGenerator.Emit(OpCodes.Br, label3);
  ilGenerator.MarkLabel(label3);
  ilGenerator.Emit(OpCodes.Br, label1);
  ilGenerator.MarkLabel(label2);
  ilGenerator.EndScope();
  ilGenerator.Emit(OpCodes.Br, label0);
  ilGenerator.MarkLabel(label0);
  methodInfo = typeof(TestClass).GetMethod(nameof(TestClass.New), new Type[] { });
  ilGenerator.Emit(OpCodes.Call, methodInfo);
  LocalBuilder lb2 = ilGenerator.DeclareLocal(typeof(TestClass));
  EmitStLoc(ilGenerator, lb2.LocalIndex);
  EmitLdLoc(ilGenerator, lb2.LocalIndex);
  methodInfo = typeof(Util).GetMethod(nameof(Util.EEE), new Type[] { typeof(TestClass) });
  ilGenerator.Emit(OpCodes.Call, methodInfo);
  ilGenerator.Emit(OpCodes.Ret);

  typeBuilder.CreateType();
}

static void EmitLdLoc(ILGenerator ilGenerator, int variableOffset)
{
  if (variableOffset < 255)
  {
    switch (variableOffset)
    {
      case 0:
        ilGenerator.Emit(OpCodes.Ldloc_0);
        break;
      case 1:
        ilGenerator.Emit(OpCodes.Ldloc_1);
        break;
      case 2:
        ilGenerator.Emit(OpCodes.Ldloc_2);
        break;
      case 3:
        ilGenerator.Emit(OpCodes.Ldloc_3);
        break;
      default:
        ilGenerator.Emit(OpCodes.Ldloc_S, (byte)variableOffset);
        break;
    }
  }
  else
  {
    ilGenerator.Emit(OpCodes.Ldloc, variableOffset);
  }
}

static void EmitStLoc(ILGenerator ilGenerator, int variableOffset)
{
  if (variableOffset < 255)
  {
    switch (variableOffset)
    {
      case 0:
        ilGenerator.Emit(OpCodes.Stloc_0);
        break;
      case 1:
        ilGenerator.Emit(OpCodes.Stloc_1);
        break;
      case 2:
        ilGenerator.Emit(OpCodes.Stloc_2);
        break;
      case 3:
        ilGenerator.Emit(OpCodes.Stloc_3);
        break;
      default:
        ilGenerator.Emit(OpCodes.Stloc_S, (byte)variableOffset);
        break;
    }
  }
  else
  {
    ilGenerator.Emit(OpCodes.Stloc, variableOffset);
  }
}

Expected behavior

No error

Actual behavior

My Test() method, decompiled with ILSpy, looks like that:

public static void Test()
{
    //IL_003c: Expected I4, but got O
    //IL_0047: Expected O, but got I4
    //IL_0018: Expected O, but got I4
    if (false)
    {
	while (Util.CCC())
	{
		TestClass testClass = (TestClass)Util.NNN();
		if (3 == (nint)testClass)
		{
			Util.OOO();
		}
	}
    }
    int num = (int)TestClass.New();
    Util.EEE((TestClass)num);
}

Obviously, running it will crash : "System.InvalidProgramException: Common Language Runtime detected an invalid program.".

Regression?

I believe it has never worked since PersistedAssemblyBuilder was introduced, but I haven't tried it before 9.0.2
However, it has worked correctly with .Net Framework 4.8 and AssemblyBuilderAccess.Save

Known Workarounds

https://github.com/Lokad/ILPack

Configuration

I have seen this in .Net 9.0.2 and 9.0.3
It's probably irrelevant, but I'm using Windows 11 and x64

Other information

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions