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?
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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With