Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mono.Cecil: injecting try/finally?

Good overview of implementing try/catch in Mono.Cecil was answered here, but he stops just short of a full try/catch/finally. So how do you implement try/finally using Mono.Cecil?

like image 485
naasking Avatar asked Dec 20 '22 15:12

naasking


2 Answers

Here is how to inject a finally.

First you need to fix your return statements. You only want one.

Instruction FixReturns()
{
    if (Method.ReturnType == TypeSystem.Void)
    {
        var instructions = body.Instructions;
        var lastRet = Instruction.Create(OpCodes.Ret);
        instructions.Add(lastRet);

        for (var index = 0; index < instructions.Count - 1; index++)
        {
            var instruction = instructions[index];
            if (instruction.OpCode == OpCodes.Ret)
            {
                instructions[index] = Instruction.Create(OpCodes.Leave, lastRet);
            }
        }
        return lastRet;
    }
    else
    {
        var instructions = body.Instructions;
        var returnVariable = new VariableDefinition("methodTimerReturn", Method.ReturnType);
        body.Variables.Add(returnVariable);
        var lastLd = Instruction.Create(OpCodes.Ldloc, returnVariable);
        instructions.Add(lastLd);
        instructions.Add(Instruction.Create(OpCodes.Ret));

        for (var index = 0; index < instructions.Count - 2; index++)
        {
            var instruction = instructions[index];
            if (instruction.OpCode == OpCodes.Ret)
            {
                instructions[index] = Instruction.Create(OpCodes.Leave, lastLd);
                instructions.Insert(index, Instruction.Create(OpCodes.Stloc, returnVariable));
                index++;
            }
        }
        return lastLd;
    }
}

Then find the first instruction. You will need to skip 2 if it is an instance constructor.

Instruction FirstInstructionSkipCtor()
{
    if (Method.IsConstructor && !Method.IsStatic)
    {
        return body.Instructions.Skip(2).First();
    }
    return body.Instructions.First();
}

Then stitch it together

void InnerProcess()
{
    body = Method.Body;
    body.SimplifyMacros();
    ilProcessor = body.GetILProcessor();

    var returnInstruction = FixReturns();

    var firstInstruction = FirstInstructionSkipCtor();

    var beforeReturn = Instruction.Create(OpCodes.Nop);
    ilProcessor.InsertBefore(returnInstruction, beforeReturn);

    InjectIlForFinaly(returnInstruction);

    var handler = new ExceptionHandler(ExceptionHandlerType.Finally)
        {
            TryStart = firstInstruction,
            TryEnd = beforeReturn,
            HandlerStart = beforeReturn,
            HandlerEnd = returnInstruction,
        };

    body.ExceptionHandlers.Add(handler);
    body.InitLocals = true;
    body.OptimizeMacros();
}
like image 133
Simon Avatar answered Dec 23 '22 05:12

Simon


Found the checked example quite informative and useful. Did run into a problem, though, in more complex conditions. In FixReturns(), both void and non-void returning scopes, the method of changing ret -> leave through creation of new Instructions orphans the originals. This can leave other instructions with the orphan as operand (e.g., branches to the original ret). The resulting code then ends up being invalid.

We simply updated the existing ret instructions' opcode/operand pair vs. creating new ones and all appears well.

Cheers.

like image 45
Brian Avatar answered Dec 23 '22 05:12

Brian