4

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?

Community
  • 1
  • 1
naasking
  • 2,514
  • 1
  • 27
  • 32

2 Answers2

10

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();
}
Simon
  • 33,714
  • 21
  • 133
  • 202
  • So it's basically the same process as described in that post, just passing ExceptionHandlerType.Finally into new ExceptionHandler(). And if you want a try/catch/finally, you're creating two "ExceptionHandler" instances and adding them to the method, one for the catch, one for the finally. If that's the case, the names are certainly confusing since you're not really creating new exception handlers, at least for 'finally'. Thanks for the pointers. – naasking Oct 09 '12 at 14:10
1

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.

Brian
  • 11
  • 1