Description
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