jbevain / mono.reflection

Some useful reflection helpers, including an IL disassembler.
151 stars 51 forks source link

"Operation could destabilize the runtime." when using IL to create a DynamicMethod #14

Closed sjoerd222888 closed 8 years ago

sjoerd222888 commented 8 years ago

Maybe I completely misunderstand some basics, but I thought it would be possible to do the following:

 Action<string> myAction = s =>
{
    Console.WriteLine("Hello " + s);
};
IList<Instruction> instructions = myAction.GetMethodInfo().GetInstructions();
DynamicMethod dynamicCallback = new DynamicMethod("myAction", typeof(void), new Type[] { typeof(string) }, true);
ILGenerator il = dynamicCallback.GetILGenerator();
foreach (Instruction instruction in instructions)
{
    il.Emit(instruction.OpCode);
}
dynamicCallback.Invoke(null, new object[] { "World" });

But doing so will end up with a TargetInvocationException with the message: "Operation could destabilize the runtime.".

According to my understanding the il code generated with your extension library should be valid to be used to create a DynamicMethod, isn't it? So I suppose there is an issue with the generated IL code. Please forgive me when I wrong and I misunderstood some basics about IL generation.

jbevain commented 8 years ago

That's going to be a bit more complicated than that :)

Here's what's needed to make your test work:

Action<string> myAction = s =>
{
    Console.WriteLine("Hello " + s);
};
IList<Instruction> instructions = myAction.Method.GetInstructions();
DynamicMethod dynamicCallback = new DynamicMethod("myAction", typeof(void), new Type[] { typeof(string) }, true);
ILGenerator il = dynamicCallback.GetILGenerator();
foreach (Instruction instruction in instructions)
{
    switch (instruction.OpCode.OperandType)
    {
        case OperandType.InlineNone:
            if (instruction.OpCode == OpCodes.Ldarg_1)
                il.Emit(OpCodes.Ldarg_0);
            else
                il.Emit(instruction.OpCode);
            break;
        case OperandType.InlineMethod:
            il.Emit(instruction.OpCode, (MethodInfo)instruction.Operand);
            break;
        case OperandType.InlineString:
            il.Emit(instruction.OpCode, (string)instruction.Operand);
            break;
        default:
            throw new NotImplementedException();
    }

}
dynamicCallback.Invoke(null, new object[] { "World" });

You can't blindly take instructions from a method and copy them into DynamicMethod.

The first thing to note here is that some instructions have operands. Your method body looks like:

nop
ldstr "Hello"
ldarg.1
call System.String::Concat(System.String,System.String)
call System.Console::WriteLine(System.String)
nop
ret

I've only implemented what's needed to work here. ldstr takes a literal string, call takes a MethodInfo. There's a bunch of other instructions that take operands that you'll need to implement.

The second thing is that in:

 Action<string> myAction = s =>
{
    Console.WriteLine("Hello " + s);
};

The lambda is compiled into a class, and the code of the lambda is compiled into an instance method, whereas DynamicMethods are static methods by definition. It means that you need to adjust the index of the arguments, as instance methods have the this argument at index 0.

The exception you saw was because you emitted a ldarg.1 while there's no argument at index 1 in the DynamicMethod, the string argument will be at index 0.

Good luck!

sjoerd222888 commented 8 years ago

Many thanks for the detailed explanation.