Here's the code that does the method cloning:

private static AssemblyDefinition CloneTargetMethod(ModuleDefinition
originalModule, MethodDefinition targetMethod)
        {
            var assemblyNameDefinition = new
AssemblyNameDefinition("TestAssembly", new Version(1, 0));
            var testAssembly =
AssemblyDefinition.CreateAssembly(assemblyNameDefinition,
originalModule.Name, ModuleKind.Dll);
            var name = targetMethod.FullName;
            var module = testAssembly.MainModule;

            var returnType = module.Import(targetMethod.ReturnType);

            var globalType = module.Types[0];

            var attributes = MethodAttributes.Public |
MethodAttributes.Static;
            var clonedMethod = new MethodDefinition(targetMethod.Name,
attributes, returnType);
            globalType.Methods.Add(clonedMethod);
            foreach (var parameter in targetMethod.Parameters)
            {
                clonedMethod.Parameters.Add(new
ParameterDefinition(parameter.ParameterType));
            }

            var methodStubParameter = new
ParameterDefinition(module.Import(typeof(IMethodStub)));
            clonedMethod.Parameters.Add(methodStubParameter);

            // Add the IMethodStub parameter so that we can stub out
            // all of the method calls
            var body = clonedMethod.Body;
            body.InitLocals = true;
            body.MaxStackSize = 8;

            var getMethodFromHandle =
module.ImportMethod<MethodBase>("GetMethodFromHandle",
typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle));

            var stackCtor =
module.ImportConstructor<Stack<object>>(new Type[0]);
            var pushMethod =
module.ImportMethod<Stack<object>>("Push");
            var toArray =
module.ImportMethod<Stack<object>>("ToArray");
            var execute = module.ImportMethod<IMethodStub>("Execute");

            var currentArguments =
clonedMethod.AddLocal<Stack<object>>("__arguments");
            var currentArgument =
clonedMethod.AddLocal<object>("__currentArgument");
            var currentMethod =
clonedMethod.AddLocal<MethodBase>("__currentMethod");
            var target = clonedMethod.AddLocal<object>("__target");

            var processor = body.GetILProcessor();

            // Create the shim for calling the IMethodStub interface
            var methodBody = targetMethod.Body;
            var originalInstructions = new
List<Instruction>(methodBody.Instructions.ToArray());
            body.Instructions.Clear();

            // Create the stack that will hold the method
arguments
            foreach (var instruction in originalInstructions)
            {
                var opCode = instruction.OpCode;
                if (opCode.FlowControl == FlowControl.Branch)
                {
                    Console.WriteLine();
                }

                if (opCode != OpCodes.Callvirt && opCode !=
OpCodes.Call)
                {
                    processor.Append(instruction);
                    continue;
                }

                processor.Emit(OpCodes.Nop);

                processor.Emit(OpCodes.Newobj, stackCtor);
                processor.Emit(OpCodes.Stloc, currentArguments);

                var operand = instruction.Operand;
                var method = (MethodReference)operand;

                foreach (var param in method.Parameters)
                {
                    // Save the current argument
                    var parameterType = param.ParameterType;
                    if (parameterType.IsValueType || parameterType is
GenericParameter)
                        processor.Emit(OpCodes.Box, parameterType);

                    processor.Emit(OpCodes.Stloc, currentArgument);
                    processor.Emit(OpCodes.Ldloc, currentArguments);
                    processor.Emit(OpCodes.Ldloc, currentArgument);
                    processor.Emit(OpCodes.Callvirt,
pushMethod);
                }

                // Save the target if necessary
                if (method.HasThis)
                    processor.Emit(OpCodes.Stloc, target);

                var declaringType = method.DeclaringType;


                // Instantiate the generic type before determining
                // the current method
                if (declaringType.GenericParameters.Count > 0)
                {
                    var genericType = new
GenericInstanceType(declaringType);
                    foreach (var parameter in
declaringType.GenericParameters)
                    {
                        genericType.GenericArguments.Add(parameter);
                    }

                    declaringType = genericType;
                }

                processor.Emit(OpCodes.Ldtoken, method);
                processor.Emit(OpCodes.Ldtoken, declaringType);

                processor.Emit(OpCodes.Call,
getMethodFromHandle);
                processor.Emit(OpCodes.Ldarg,
methodStubParameter);

                // The stack should be balanced at this point
                // but for some reason I get the 'branch out of
method' error
                // even though I haven't changed any branch
instructions

                processor.Emit(OpCodes.Pop);
                processor.Emit(OpCodes.Pop);

                //processor.Emit(OpCodes.Ldloc, currentMethod);
                //processor.Emit(OpCodes.Ldloc, target);

                //processor.Emit(OpCodes.Ldloc, currentArguments);
                //processor.Emit(OpCodes.Callvirt, toArray);
                //processor.Emit(OpCodes.Callvirt, execute);
                //processor.PackageReturnValue(returnType);

            }

            return testAssembly;
        }

Here's the PEVerify output:

Microsoft (R) .NET Framework PE Verifier.  Version  4.0.30319.1
Copyright (c) Microsoft Corporation.  All rights reserved.

[IL]: Error: [H:\Development\MethodCloning\MethodCloning\bin\Debug
\output.dll : <Module>::Print][offset 0x0000004D] Branch out of the
method.
1 Error(s) Verifying output.dll

...and here's the dump of the Print method:

.method public static void  Print(int32 A_0,
                                  class
[MethodCloning]MethodCloning.IMethodStub A_1) cil managed
{
  // Code size       214 (0xd6)
  .maxstack  2
  .locals init (class
[System]System.Collections.Generic.Stack`1<object> V_0,
           object V_1,
           class [mscorlib]System.Reflection.MethodBase V_2,
           object V_3)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  ldc.i4.3
  IL_0003:  rem
  IL_0004:  brtrue.s   IL_0011
  IL_0006:  ldarg.1
  IL_0007:  ldc.i4.5
  IL_0008:  rem
  IL_0009:  ldc.i4.0
  IL_000a:  ceq
  IL_000c:  ldc.i4.0
  IL_000d:  ceq
  IL_000f:  br.s       IL_0012
  IL_0011:  ldc.i4.1
  IL_0012:  stloc.0
  IL_0013:  ldloc.0
  IL_0014:  brtrue.s   IL_004f
  IL_0016:  nop
  IL_0017:  ldstr      "FizzBuzz"
  IL_001c:  nop
  IL_001d:  newobj     instance void class
[System]System.Collections.Generic.Stack`1<object>::.ctor()
  IL_0022:  stloc      V_0
  IL_0026:  stloc      V_1
  IL_002a:  ldloc      V_0
  IL_002e:  ldloc      V_1
  IL_0032:  callvirt   instance void class
[System]System.Collections.Generic.Stack`1<object>::Push(!0)
  IL_0037:  ldtoken    method void
[mscorlib]System.Console::WriteLine(string)
  IL_003c:  ldtoken    [mscorlib]System.Console
  IL_0041:  call       class [mscorlib]System.Reflection.MethodBase
[mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype
[mscorlib]System.RuntimeMethodHandle,
 
valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0046:  ldarg      A_1
  IL_004a:  pop
  IL_004b:  pop
  IL_004c:  nop
  IL_004d:  br.s       IL_ffffffd5
  IL_004f:  ldarg.1
  IL_0050:  ldc.i4.3
  IL_0051:  rem
  IL_0052:  ldc.i4.0
  IL_0053:  ceq
  IL_0055:  ldc.i4.0
  IL_0056:  ceq
  IL_0058:  stloc.0
  IL_0059:  ldloc.0
  IL_005a:  brtrue.s   IL_0092
  IL_005c:  ldstr      "Fizz"
  IL_0061:  nop
  IL_0062:  newobj     instance void class
[System]System.Collections.Generic.Stack`1<object>::.ctor()
  IL_0067:  stloc      V_0
  IL_006b:  stloc      V_1
  IL_006f:  ldloc      V_0
  IL_0073:  ldloc      V_1
  IL_0077:  callvirt   instance void class
[System]System.Collections.Generic.Stack`1<object>::Push(!0)
  IL_007c:  ldtoken    method void
[mscorlib]System.Console::WriteLine(string)
  IL_0081:  ldtoken    [mscorlib]System.Console
  IL_0086:  call       class [mscorlib]System.Reflection.MethodBase
[mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype
[mscorlib]System.RuntimeMethodHandle,
 
valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_008b:  ldarg      A_1
  IL_008f:  pop
  IL_0090:  pop
  IL_0091:  nop
  IL_0092:  ldarg.1
  IL_0093:  ldc.i4.5
  IL_0094:  rem
  IL_0095:  ldc.i4.0
  IL_0096:  ceq
  IL_0098:  ldc.i4.0
  IL_0099:  ceq
  IL_009b:  stloc.0
  IL_009c:  ldloc.0
  IL_009d:  brtrue.s   IL_00d5
  IL_009f:  ldstr      "Buzz"
  IL_00a4:  nop
  IL_00a5:  newobj     instance void class
[System]System.Collections.Generic.Stack`1<object>::.ctor()
  IL_00aa:  stloc      V_0
  IL_00ae:  stloc      V_1
  IL_00b2:  ldloc      V_0
  IL_00b6:  ldloc      V_1
  IL_00ba:  callvirt   instance void class
[System]System.Collections.Generic.Stack`1<object>::Push(!0)
  IL_00bf:  ldtoken    method void
[mscorlib]System.Console::WriteLine(string)
  IL_00c4:  ldtoken    [mscorlib]System.Console
  IL_00c9:  call       class [mscorlib]System.Reflection.MethodBase
[mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype
[mscorlib]System.RuntimeMethodHandle,
 
valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_00ce:  ldarg      A_1
  IL_00d2:  pop
  IL_00d3:  pop
  IL_00d4:  nop
  IL_00d5:  ret
} // end of method 'Global Functions'::Print

Now for some reason, Cecil tries to branch out of the method with the
following instruction:

  IL_004d:  br.s       IL_ffffffd5

When I step through the code with the debugger and look at the
original branch instructions, none of those branch instructions ever
try to jump out of the method. Am I doing something wrong here, or is
this a bug in Cecil?


-- 
--
mono-cecil

Reply via email to