File: Linker\LinkerILProcessor.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// 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
	}
}