|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Mono.Linker
{
#pragma warning disable RS0030
public sealed class LinkerILProcessor
{
readonly ILProcessor _ilProcessor;
Collections.Generic.Collection<Instruction> Instructions => _ilProcessor.Body.Instructions;
internal LinkerILProcessor (MethodBody body)
{
_ilProcessor = body.GetILProcessor ();
}
public void Emit (OpCode opcode) => Append (_ilProcessor.Create (opcode));
public void Emit (OpCode opcode, TypeReference type) => Append (_ilProcessor.Create (opcode, type));
public void Emit (OpCode opcode, MethodReference method) => Append (_ilProcessor.Create (opcode, method));
public void Emit (OpCode opcode, VariableDefinition variable) => Append (_ilProcessor.Create (opcode, variable));
public void Emit (OpCode opcode, string value) => Append (_ilProcessor.Create (opcode, value));
public void InsertBefore (Instruction target, Instruction instruction)
{
// When inserting before, all pointers have to be updated to the new "before" instruction.
RedirectScopeStart (target, instruction);
ReplaceInstructionReference (target, instruction);
_ilProcessor.InsertBefore (target, instruction);
}
// When inserting after, no redirection is necessary since it will naturally be "appended" to the same blocks
// as the target instruction.
public void InsertAfter (Instruction target, Instruction instruction)
{
RedirectScopeEnd (target, instruction);
_ilProcessor.InsertAfter (target, instruction);
}
public void Append (Instruction instruction)
{
Instruction? lastInstruction = Instructions.Count == 0 ? null : Instructions[Instructions.Count - 1];
RedirectScopeEnd (lastInstruction, instruction);
_ilProcessor.Append (instruction);
}
public void Replace (Instruction target, Instruction instruction)
{
RedirectScopeStart (target, instruction);
RedirectScopeEnd (target, instruction);
ReplaceInstructionReference (target, instruction);
_ilProcessor.Replace (target, instruction);
}
public void Replace (int index, Instruction instruction) => Replace (_ilProcessor.Body.Instructions[index], instruction);
public void Remove (Instruction instruction)
{
int index = _ilProcessor.Body.Instructions.IndexOf (instruction);
if (index == -1)
throw new ArgumentOutOfRangeException (nameof (instruction));
Instruction? nextInstruction = Instructions.Count == index + 1 ? null : Instructions[index + 1];
Instruction? previousInstruction = index == 0 ? null : Instructions[index - 1];
RedirectScopeStart (instruction, nextInstruction);
RedirectScopeEnd (instruction, previousInstruction);
ReplaceInstructionReference (instruction, nextInstruction);
_ilProcessor.Remove (instruction);
}
public void RemoveAt (int index) => Remove (Instructions[index]);
void RedirectScopeStart (Instruction oldTarget, Instruction? newTarget)
{
// In Cecil "start" pointers point to the first instruction in a given scope
// and the "end" pointers point to the first instruction after the given block
// so they need to effectively point to the start of the next scope.
// That's why both start and end are handled in the RedirectScopeStart.
foreach (var handler in _ilProcessor.Body.ExceptionHandlers) {
if (handler.TryStart == oldTarget)
handler.TryStart = newTarget;
if (handler.TryEnd == oldTarget)
handler.TryEnd = newTarget;
if (handler.HandlerStart == oldTarget)
handler.HandlerStart = newTarget;
if (handler.HandlerEnd == oldTarget)
handler.HandlerEnd = newTarget;
if (handler.FilterStart == oldTarget)
handler.FilterStart = newTarget;
}
}
#pragma warning disable IDE0060 // Remove unused parameter
static void RedirectScopeEnd (Instruction? oldTarget, Instruction? newTarget)
#pragma warning restore IDE0060 // Remove unused parameter
{
// Currently Cecil treats all block boundaries as "starts"
// so nothing to do here.
}
void ReplaceInstructionReference (Instruction oldTarget, Instruction? newTarget)
{
foreach (var instr in Instructions) {
switch (instr.OpCode.FlowControl) {
case FlowControl.Cond_Branch:
if (instr.Operand is Instruction?[] instructions) {
for (int i = 0; i < instructions.Length; ++i) {
if (instructions[i] == oldTarget)
instructions[i] = newTarget;
}
break;
}
goto case FlowControl.Branch;
case FlowControl.Branch:
if (instr.Operand == oldTarget)
instr.Operand = newTarget;
break;
}
}
}
#pragma warning disable RS0030
}
}
|