|
// 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
}
}
|