Два дня гуглил — ничего не нагуглил.
Берём примитивный тестовый код:
public static void Test()
{
unsafe
{
fixed (int* p = new int[1])
{
*p = -1;
}
}
}
Всё работает. Смотрим в ILDASM:
.method /*06000013*/ public hidebysig static void
Test() cil managed
{
.maxstack 2
.locals /*1100000D*/ init (
[0] int32* p,
[1] int32[] pinned V_1
)
IL_0000: nop
IL_0001: nop
IL_0002: ldc.i4.1
IL_0003: newarr [mscorlib/*23000001*/]System.Int32/*0100003F*/
IL_0008: dup
IL_0009: stloc.1 // V_1
IL_000a: brfalse.s IL_0011
IL_000c: ldloc.1 // V_1
IL_000d: ldlen
IL_000e: conv.i4
IL_000f: brtrue.s IL_0016
IL_0011: ldc.i4.0
IL_0012: conv.u
IL_0013: stloc.0 // p
IL_0014: br.s IL_001f
IL_0016: ldloc.1 // V_1
IL_0017: ldc.i4.0
IL_0018: ldelema [mscorlib/*23000001*/]System.Int32/*0100003F*/
IL_001d: conv.u
IL_001e: stloc.0 // p
IL_001f: nop
IL_0020: ldloc.0 // p
IL_0021: ldc.i4.m1
IL_0022: stind.i4
IL_0023: nop
IL_0024: ldnull
IL_0025: stloc.1 // V_1
IL_0026: nop
IL_0027: ret
} // end of method Program::Test
Ничего военного — основные приседания вокруг пиннинга массива и проверки, что он не null и длина больше нуля.
Пробуем воспроизвести:
private static void GenerateTestMSIL(ILGenerator ilg)
{
var p = ilg.DeclareLocal(typeof(int*));
var t = ilg.DeclareLocal(typeof(int[]), true);
var zeroPtr = ilg.DefineLabel();
var nonZeroPtr = ilg.DefineLabel();
var main = ilg.DefineLabel();
ilg.Emit(OpCodes.Ldc_I4_1);
ilg.Emit(OpCodes.Newarr, typeof(int));
//ilg.Emit(OpCodes.Dup);
ilg.Emit(OpCodes.Stloc, t);
//ilg.Emit(OpCodes.Brfalse, zeroPtr);//
//ilg.Emit(OpCodes.Ldloc, t);
//ilg.Emit(OpCodes.Ldlen);
//ilg.Emit(OpCodes.Conv_I4);
//ilg.Emit(OpCodes.Brtrue, nonZeroPtr);
//ilg.MarkLabel(zeroPtr);
//ilg.Emit(OpCodes.Ldc_I4_0);
//ilg.Emit(OpCodes.Stloc, p);
//ilg.Emit(OpCodes.Br_S, main);
ilg.MarkLabel(nonZeroPtr);
ilg.Emit(OpCodes.Ldloc, t);
ilg.Emit(OpCodes.Ldc_I4_0);
ilg.Emit(OpCodes.Ldelema, typeof(int));
ilg.Emit(OpCodes.Conv_U);
ilg.Emit(OpCodes.Stloc, p);
ilg.MarkLabel(main);
ilg.Emit(OpCodes.Ldloc, p);
ilg.Emit(OpCodes.Ldc_I4_M1);
ilg.Emit(OpCodes.Stind_I4);
//ilg.Emit(OpCodes.Ldnull);
//ilg.Emit(OpCodes.Stloc, t);
ilg.Emit(OpCodes.Ret);
}
(Закомментированы избыточные операции, которые C# вставляет из паранойи — их наличие, к сожалению, на работоспособность не влияет).
Вот теперь странности:
1. Если мы генерируем этот код в метод класса, созданного в рамках динамической сборки, то всё работает, как ожидается:
public static Action GenerateTestAssembly()
{
var ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("FixedTest"), AssemblyBuilderAccess.Run);
var tb = ab.DefineDynamicModule("FixedTest", "FixedTest.dll").DefineType("FixedTest", TypeAttributes.Class | TypeAttributes.Public);
var mtb = tb.DefineMethod("Test", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static);
GenerateTestMSIL(mtb.GetILGenerator());
Type type = tb.CreateType();
return (Action)type.GetMethod("Test").CreateDelegate(typeof(Action));
}
А вот если мы попробуем такой же MSIL сгенерировать в рамках DynamicMethod, то попытка вызвать его приведёт к InvalidOperationException "This operation could destabilize the runtime".
В чём может быть дело?
Понятное дело, что PEVerify ругается на порождённый таким образом код, точно так же, как и на оригинальный метод Test(). Поэтому найти, что именно там could destabilize the runtime, не представляется возможным.