Search code examples
c#monocilmono.cecil

Mono.Cecil: create local variable and change return statement


I'm trying to rewrite the get method of an property from:

 get
 {
    return dataString;
 }

to:

 get
 {
    string temp = dataString;
    PropertyLogging.Get("DataString", ref temp);
    return temp;
 }

So far I've tried differnt things:

//the static method I#m trying to insert
var getMethod = att.AttributeType.Resolve().Methods.FirstOrDefault(x => x.Name == _getMethodName);
if (getMethod != null)
{
    // ilProcessor.InsertBefore(returnInstruction, ilProcessor.Create(OpCodes.Starg, 1));

    ilProcessor.InsertBefore(returnInstruction, ilProcessor.CreateLoadInstruction(property.Name));

    // ilProcessor.InsertBefore(returnInstruction, ilProcessor.Create(OpCodes.Ldloc, 0));
    ilProcessor.InsertBefore(returnInstruction, ilProcessor.Create(OpCodes.Ldloca_S,  0));

    ilProcessor.InsertBefore(returnInstruction, ilProcessor.Create(OpCodes.Call, currentMethod.Module.ImportReference(getMethod)));

}

But it always ends in code like (decompiled with ilspy):

get
{
    string text = this.dataString;
    string arg_17_0 = text;
    string text2;
    PropertyLogging.Get("DataString", ref text2);
    return arg_17_0;
}

The IL code i currently have is:

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       UserQuery.dataString
IL_0007:  stloc.0     // text
IL_0008:  ldloc.0     // text
IL_0009:  stloc.1     // arg_17_0
IL_000A:  ldnull      
IL_000B:  stloc.2     // text2
IL_000C:  ldstr       "DataString"
IL_0011:  ldloca.s    02 // text2
IL_0013:  call        UserQuery+PropertyLogging.Get
IL_0018:  nop         
IL_0019:  ldloc.1     // arg_17_0
IL_001A:  stloc.3     
IL_001B:  br.s        IL_001D
IL_001D:  ldloc.3     
IL_001E:  ret    

But what I need is:

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       UserQuery.dataString
IL_0007:  stloc.0     // temp
IL_0008:  ldstr       "DataString"
IL_000D:  ldloca.s    00 // temp
IL_000F:  call        UserQuery+PropertyLogging.Get
IL_0014:  nop         
IL_0015:  ldloc.0     // temp
IL_0016:  stloc.1     
IL_0017:  br.s        IL_0019
IL_0019:  ldloc.1     
IL_001A:  ret 

I've also tried ti create a new variable in the method body, but I don't have a clue how to use it.

Does anyone know how to correctly rewrite this get method?

thanks in advance


Solution

  • C# code before:

    public string DataString
    {
       get { return _dataString; }
    }
    

    IL code before:

    .method public hidebysig specialname 
    instance string get_DataString () cil managed 
    {
    // Method begins at RVA 0x2050
    // Code size 12 (0xc)
    .maxstack 1
    .locals init (
        [0] string
    )
    
        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldfld string ClassLibrary1.Class1::_dataString
        IL_0007: stloc.0
        IL_0008: br.s IL_000a
        IL_000a: ldloc.0
        IL_000b: ret
    } // end of method Class1::get_DataString
    

    Rewrite:

    public void InterceptPropertyGetter()
    {
        // get the module where the property exist
        var module = ModuleDefinition.ReadModule(@"C:\temp\ClassLibrary1.dll");
    
        // get the property get method
        TypeDefinition myType = module.Types.First(type => type.Name == "Class1");
        var property = 
            myType.Properties.First(prop => prop.Name == "DataString").GetMethod;
    
        // get the _dataString field
        FieldDefinition dataStringDef = 
            myType.Fields.First(field => field.Name == "_dataString");
        FieldReference dataStringRef = module.Import(dataStringDef);
    
        // get the PropertyLogging static method
        MethodDefinition propertyLoggingDef = 
            myType.Methods.First(method => method.Name == "PropertyLogging");
        MethodReference propertyLoggingRef = module.Import(propertyLoggingDef);
    
        // clear the method (variables and instructions )
        property.Body.Variables.Clear();
        property.Body.Instructions.Clear();
    
        // define and init the locals
        property.Body.InitLocals = true;
        var tempVar = new VariableDefinition("temp", module.TypeSystem.String);
        property.Body.Variables.Add(tempVar);
    
        // write the IL
        var processor = property.Body.GetILProcessor();
        processor.Emit(OpCodes.Ldarg_0);
        processor.Emit(OpCodes.Ldfld, dataStringRef);
        processor.Emit(OpCodes.Stloc_0);
        processor.Emit(OpCodes.Ldstr, "DataString");
        processor.Emit(OpCodes.Ldloca_S, tempVar);
        processor.Emit(OpCodes.Call, propertyLoggingRef);
        processor.Emit(OpCodes.Ldloca_S, tempVar);
        processor.Emit(OpCodes.Ret);
        processor.Body.OptimizeMacros();
    
        // save the new module
        module.Write(@"C:\temp\ClassLibrary1_new.dll");
    }
    

    C# code after:

    public string DataString
    {
        get
        {
            string dataString = this._dataString;
            Class1.PropertyLogging("DataString", ref dataString);
            return dataString;
        }
    }
    

    IL code after:

    .method public hidebysig specialname 
    instance string get_DataString () cil managed 
    {
      // Method begins at RVA 0x2050
      // Code size 22 (0x16)
      .maxstack 2
      .locals init (
       [0] string
      )
        IL_0000: ldarg.0
        IL_0001: ldfld string ClassLibrary1.Class1::_dataString
        IL_0006: stloc.0
        IL_0007: ldstr "DataString"
        IL_000c: ldloca.s 0
        IL_000e: call void ClassLibrary1.Class1::PropertyLogging(string, string&)
        IL_0013: ldloca.s 0
        IL_0015: ret
    } // end of method Class1::get_DataString
    

    Decompile made with ILSpy.

    This is not the only way (for example you don't must to clear all and you can add nop instructions and use unshort version of the instructions) but I think it will help you to understand how to do that.