Можно ли генерировать Unsafe код в DynamicMethod?
От: Sinclair Россия https://github.com/evilguest/
Дата: 21.06.18 08:43
Оценка:
Два дня гуглил — ничего не нагуглил.
Берём примитивный тестовый код:
        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, не представляется возможным.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.