Files
7d2dXG/XG-Rebirth/Mods/0A-KFCommonUtilityLib/Scripts/Utilities/MonoCecilExtensions.cs
2025-05-30 00:19:27 +09:30

1724 lines
91 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using MethodBody = Mono.Cecil.Cil.MethodBody;
/// <summary>
/// https://github.com/snaphat/MonoCecilExtensions
/// Provides extension methods for classes from the Mono.Cecil library, a library for reading and writing Intermediate Language (IL) code.
/// These extensions facilitate manipulation of IL code, providing functionality to clone, merge, and update types in collections, methods, fields, and other components of a .NET assembly.
/// </summary>
public static class MonoCecilExtensions
{
/// <summary>
/// Represents an information container for updating Mono.Cecil definitions.
/// </summary>
public class UpdateInfo
{
/// <summary>
/// A collection of CustomAttribute objects that have been updated.
/// </summary>
public readonly Collection<CustomAttribute> updatedAttributes = new Collection<CustomAttribute>();
/// <summary>
/// A collection of InterfaceImplementation objects that have been updated.
/// </summary>
public readonly Collection<InterfaceImplementation> updatedInterfaces = new Collection<InterfaceImplementation>();
/// <summary>
/// A collection of FieldDefinition objects that have been updated.
/// </summary>
public readonly Collection<FieldDefinition> updatedFields = new Collection<FieldDefinition>();
/// <summary>
/// A collection of PropertyDefinition objects that have been updated.
/// </summary>
public readonly Collection<PropertyDefinition> updatedProperties = new Collection<PropertyDefinition>();
/// <summary>
/// A collection of MethodDefinition objects that have been updated.
/// </summary>
public readonly Collection<MethodDefinition> updatedMethods = new Collection<MethodDefinition>();
/// <summary>
/// A collection of source TypeDefinition objects that are being merged.
/// </summary>
public readonly Collection<TypeDefinition> srcTypes = new Collection<TypeDefinition>();
/// <summary>
/// A collection of destination TypeDefinition objects where source objects are merged into.
/// </summary>
public readonly Collection<TypeDefinition> destTypes = new Collection<TypeDefinition>();
};
/// <summary>
/// A dictionary mapping from AssemblyDefinition objects to their corresponding UpdateInfo objects.
/// Used to keep track of the updates made to each assembly.
/// </summary>
public static readonly Dictionary<AssemblyDefinition, UpdateInfo> assemblyUpdateInfo = new Dictionary<AssemblyDefinition, UpdateInfo> ();
/// <summary>
/// Additional search directories for resolving assembly types.
/// </summary>
public static readonly Collection<string> additionalSearchDirectories = new Collection<string>();
// Basic extension methods for loading assemblies, adding elements to collections, and finding types, fields, and methods in Mono.Cecil objects.
#region Base
/// <summary>
/// This extension method loads an assembly from a given location.
/// </summary>
/// <param name="location">The location of the assembly to be loaded.</param>
/// <param name="readWrite">A boolean value to determine if the assembly is read-only or writable.</param>
/// <returns>The AssemblyDefinition object of the loaded assembly if successful</returns>
public static AssemblyDefinition LoadAssembly(this string location, bool readWrite = false)
{
// Create a new instance of the DefaultAssemblyResolver.
var resolver = new DefaultAssemblyResolver();
// Add search directories to the resolver.
foreach (var directory in additionalSearchDirectories)
resolver.AddSearchDirectory(directory);
resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(int).Assembly.Location));
resolver.AddSearchDirectory(Path.Combine(Path.GetDirectoryName(typeof(int).Assembly.Location), "Facades"));
// Read and return the assembly using the provided location and reader parameters.
return AssemblyDefinition.ReadAssembly(location, new ReaderParameters()
{
AssemblyResolver = resolver,
ReadWrite = readWrite,
});
}
/// <summary>
/// This extension method finds a method of a given type in an assembly.
/// </summary>
/// <param name="assembly">The assembly where the type and method are located.</param>
/// <param name="typeSignature">The full or simple name of the type.</param>
/// <param name="methodSignature">The full or simple name of the method.</param>
/// <returns>The MethodDefinition object of the found method. Null if not found.</returns>
public static MethodDefinition FindMethodOfType(this AssemblyDefinition assembly, string typeSignature, string methodSignature)
{
// Find and return the method of the given type in the assembly.
return assembly.FindType(typeSignature)?.FindMethod(methodSignature);
}
/// <summary>
/// This extension method finds a type in an assembly using its full name or simple name.
/// </summary>
/// <param name="assembly">The assembly where the type is located.</param>
/// <param name="typeSignature">The full or simple name of the type.</param>
/// <returns>The TypeDefinition object of the found type. Null if not found.</returns>
public static TypeDefinition FindType(this AssemblyDefinition assembly, string typeSignature)
{
// Return the first type that matches the provided type signature.
return assembly.MainModule.Types.FirstOrDefault(type => type.FullName == typeSignature || type.Name == typeSignature);
}
/// <summary>
/// This extension method finds a type in an assembly using its full name or simple name.
/// </summary>
/// <param name="assembly">The assembly where the type is located.</param>
/// <param name="type">The type to locate.</param>
/// <returns>The TypeDefinition object of the found type. Null if not found.</returns>
public static TypeDefinition FindType(this AssemblyDefinition assembly, Type type)
{
// Return the first type that matches the provided type signature.
return assembly.MainModule.Types.FirstOrDefault(_type => _type.FullName == type.FullName || _type.Name == type.Name);
}
/// <summary>
/// This extension method finds a field in a type.
/// </summary>
/// <param name="type">The type where the field is located.</param>
/// <param name="fieldSignature">The full or simple name of the field.</param>
/// <returns>The FieldDefinition object of the found field. Null if not found.</returns>
public static FieldDefinition FindField(this TypeDefinition type, string fieldSignature)
{
// Return the first field that matches the provided field signature.
return type.Fields.FirstOrDefault(m => m.FullName == fieldSignature || m.Name == fieldSignature);
}
/// <summary>
/// This extension method finds a method in a type.
/// </summary>
/// <param name="type">The type where the method is located.</param>
/// <param name="methodSignature">The full or simple name of the method.</param>
/// <returns>The MethodDefinition object of the found method. Null if not found.</returns>
public static MethodDefinition FindMethod(this TypeDefinition type, string methodSignature)
{
// The function checks each method in the type's Methods collection,
// and returns the first method whose full name or simple name matches the provided method signature.
return type.Methods.FirstOrDefault(m => m.FullName == methodSignature || m.Name == methodSignature);
}
/// <summary>
/// This extension method finds all methods in a type that match a given method signature.
/// </summary>
/// <param name="type">The type where the methods are located.</param>
/// <param name="methodSignature">The full or simple name of the methods.</param>
/// <returns>A collection of MethodDefinition objects for the found methods. Empty collection if none found.</returns>
public static Collection<MethodDefinition> FindMethods(this TypeDefinition type, string methodSignature)
{
var collection = new Collection<MethodDefinition>();
// This function checks each method in the type's Methods collection,
// and adds those methods to the collection whose full name or simple name matches the provided method signature.
foreach (var item in type.Methods.Where(m => m.FullName == methodSignature || m.Name == methodSignature))
collection.Add(item);
return collection;
}
#endregion Base
// Extension method that handles adding types to an assembly.
#region AddType
/// <summary>
/// Adds a type to an assembly. This includes adding the type's fields, properties, and methods.
/// If the source type is nested, it will be added as a nested type within the parent type in the destination assembly.
/// </summary>
/// <param name="assembly">The assembly to which the type will be added.</param>
/// <param name="src">The source type that will be added to the assembly.</param>
/// <param name="avoidSignatureConflicts">Avoid name conflicts by adding a '_' suffix to the copied class name.</param>
public static void AddType(this AssemblyDefinition assembly, TypeDefinition src, bool avoidSignatureConflicts = false)
{
// Check for signature conflict avoidance
var srcName = src.Name;
if (avoidSignatureConflicts) src.Name += "_";
// Create a new TypeDefinition with the same properties as the source type
var dest = new TypeDefinition(src.Namespace, src.Name, src.Attributes);
// If the source type isn't nested, add the new type directly to the assembly's types
// Otherwise, find the declaring type in the assembly and add the new type as a nested type
if (!src.IsNested)
assembly.MainModule.Types.Add(dest);
else
assembly.FindType(src.DeclaringType.FullName).NestedTypes.Add(dest);
// Set the base type of the new type to match the base type of the source type
dest.BaseType = src.BaseType;
// Add the fields, properties, and methods from the source type to the new type
dest.AddFieldsPropertiesAndMethods(src);
// Restore name
if (avoidSignatureConflicts) src.Name = srcName;
}
#endregion AddType
// Extension method that handles the addition of fields, properties, and methods from a source type to a destination type.
// This is a key part of merging two types, ensuring the destination type includes all necessary components from the source type.
#region AddFieldsPropertiesAndMethods
/// <summary>
/// Merges the source type into the destination type by cloning the fields, properties, and methods of the source, updating their types and adding them to the destination.
/// </summary>
/// <param name="dest">The destination type definition where fields, properties, and methods from source will be added.</param>
/// <param name="src">The source type definition whose fields, properties, and methods will be cloned and added to the destination.</param>
public static void AddFieldsPropertiesAndMethods(this TypeDefinition dest, TypeDefinition src)
{
// Add nested types to the module
foreach (var subtype in src.NestedTypes)
dest.Module.Assembly.AddType(subtype);
// Clone attributes from the source and add to the destination
var clonedAttributes = new Collection<CustomAttribute>();
foreach (var attribute in src.CustomAttributes)
{
var clonedAttribute = attribute.Clone();
dest.CustomAttributes.Add(clonedAttribute);
clonedAttributes.Add(clonedAttribute);
}
// Clone interfaces from the source and add to the destination
var clonedInterfaces = new Collection<InterfaceImplementation>();
foreach (var @interface in src.Interfaces)
{
var clonedInterface = @interface.Clone();
dest.Interfaces.Add(clonedInterface);
clonedInterfaces.Add(clonedInterface);
}
// Clone fields from the source and add to the destination
var clonedFields = new Collection<FieldDefinition>();
foreach (var field in src.Fields)
{
var clonedField = field.Clone();
clonedFields.Add(clonedField);
dest.Fields.Add(clonedField);
}
// Clone properties from the source and add to the destination
var clonedProperties = new Collection<PropertyDefinition>();
foreach (var property in src.Properties)
{
var clonedProperty = property.Clone();
clonedProperties.Add(clonedProperty);
dest.Properties.Add(clonedProperty);
}
// Clone methods from the source (don't add to the destination yet)
var clonedMethods = new Collection<MethodDefinition>();
foreach (var method in src.Methods)
{
var clonedMethod = method.Clone();
clonedMethods.Add(clonedMethod);
}
// List for keeping track of methods that need further processing
var updatedMethods = new Collection<MethodDefinition>();
// Process each method
foreach (var clonedMethod in clonedMethods.ToList())
{
// Special handling for constructors
if (clonedMethod.Name is ".ctor" || clonedMethod.Name is ".cctor" || clonedMethod.Name is "Finalize")
{
// Temporarily set the declaring type of the cloned method to the destination type
// This is required to get the correct full name of the method for the FindMethod call
clonedMethod.DeclaringType = dest;
// Find an existing method in the destination type that matches the full name of the cloned method
// Note that the full name of a method includes the name of its declaring type
var destMethod = dest.FindMethod(clonedMethod.FullName);
// Reset the declaring type of the cloned method to null
// This is done because the cloned method hasn't been added to the destination type yet,
// and leaving the declaring type set will cause failures to add the method to the destination type
clonedMethod.DeclaringType = null;
// If destination already contains a constructor/destructor, merge the instructions
if (destMethod != null)
{
var clonedInstructions = clonedMethod.Body.Instructions;
var trimmedClonedInstructions = clonedInstructions.ToList();
// For constructors
if (clonedMethod.Name is ".ctor")
{
// Find the constructor call instruction and remove the instructions before it
// This is done to prevent calling the base class constructor twice when merging
var callIndex = trimmedClonedInstructions.FindIndex(x => x.OpCode == OpCodes.Call);
// Check if callIndex is within valid range
if (callIndex < 0 || callIndex >= trimmedClonedInstructions.Count)
throw new Exception("Invalid Call Instruction Index in cloned method.");
// Remove starting instructions
trimmedClonedInstructions.RemoveRange(0, callIndex + 1);
trimmedClonedInstructions.RemoveAt(trimmedClonedInstructions.Count - 1);
// Insert the trimmed instructions to the existing constructor, just before the last instruction (ret)
int insertIndex = destMethod.Body.Instructions.Count - 1;
foreach (var clonedInstruction in trimmedClonedInstructions)
{
destMethod.Body.Instructions.Insert(insertIndex, clonedInstruction);
insertIndex++;
}
}
// For static constructors
else if (clonedMethod.Name is ".cctor")
{
// Remove the last instruction (ret)
trimmedClonedInstructions.RemoveAt(trimmedClonedInstructions.Count - 1);
// Insert the trimmed instructions to the existing static constructor, just before the last instruction (ret)
int insertIndex = destMethod.Body.Instructions.Count - 1;
foreach (var clonedInstruction in trimmedClonedInstructions)
{
destMethod.Body.Instructions.Insert(insertIndex, clonedInstruction);
insertIndex++;
}
}
// For destructors
else if (clonedMethod.Name is "Finalize")
{
// Find the leave.s instruction and remove the instructions after it.
// This is done to prevent calling the base class destructor twice when merging.
var trimIndex = trimmedClonedInstructions.FindIndex(x => x.OpCode == OpCodes.Leave_S);
// Check if trimIndex is within valid range
if (trimIndex < 0 || trimIndex >= trimmedClonedInstructions.Count)
throw new Exception("Invalid trim index in cloned method.");
// Remove instructions after leave.s (inclusive)
trimmedClonedInstructions.RemoveRange(trimIndex, trimmedClonedInstructions.Count - trimIndex);
// Insert the trimmed instructions to the existing destructor, at the beginning
int insertionIndex = 0;
foreach (var clonedInstruction in trimmedClonedInstructions)
{
destMethod.Body.Instructions.Insert(insertionIndex, clonedInstruction);
insertionIndex++;
}
}
// Remove the cloned constructor or destructor from the list of methods to add to the destination type
_ = clonedMethods.Remove(clonedMethod);
// Add the method to the list of methods to update since it has been modified
updatedMethods.Add(destMethod);
}
else
{
// Add the cloned constructor to the destination type
updatedMethods.Add(clonedMethod);
}
}
else
{
// For non-constructor/non-destructor methods
updatedMethods.Add(clonedMethod);
}
}
// Add updated methods to the destination type
foreach (var method in clonedMethods) dest.Methods.Add(method);
// Add updated attributes, interfaces, fields, properties and methods to the update info
if (!assemblyUpdateInfo.TryGetValue(dest.Module.Assembly, out var updateInfo))
updateInfo = assemblyUpdateInfo[dest.Module.Assembly] = new UpdateInfo();
foreach (var attribute in clonedAttributes) updateInfo.updatedAttributes.Add(attribute);
foreach (var @interface in clonedInterfaces) updateInfo.updatedInterfaces.Add(@interface);
foreach (var field in clonedFields) updateInfo.updatedFields.Add(field);
foreach (var property in clonedProperties) updateInfo.updatedProperties.Add(property);
foreach (var method in updatedMethods) updateInfo.updatedMethods.Add(method);
// Add source and destination types to the update info
updateInfo.srcTypes.Add(src);
updateInfo.destTypes.Add(dest);
}
#endregion AddFieldsPropertiesAndMethods
// Extension methods that handle the updating of fields, properties, and methods within a destination type after they have been cloned from a source type.
// These methods ensure that the newly added components in the destination type correctly reference the destination type, rather than the original source type.
#region UpdateFieldsPropertiesAndMethods
/// <summary>
/// Updates the types of attributes, interfaces, fields, properties, and methods within a given assembly.
/// This includes updating the types in interfaces, fields, properties, and methods. It also updates the getter and setter methods for properties,
/// updates the instruction types for methods, imports references for attributes, interfaces, fields, properties, and methods,
/// imports base types of each destination type, and swaps any duplicate methods in the destination types.
/// </summary>
/// <param name="assembly">The assembly to be updated. This assembly's types are matched against the source types and replaced with the corresponding destination types, based on previously registered update information.</param>
/// <param name="avoidSignatureConflicts">Avoid signature conflicts by changing original method parameters to be base object types for duplicate methods</param>
public static void UpdateFieldsPropertiesAndMethods(this AssemblyDefinition assembly, bool avoidSignatureConflicts = false)
{
// Check if update information exists for the assembly
if (assemblyUpdateInfo.TryGetValue(assembly, out var updateInfo))
{
// Update types in interfaces, fields, properties, and methods
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var @interface in updateInfo.updatedInterfaces) @interface.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var field in updateInfo.updatedFields) field.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var property in updateInfo.updatedProperties) property.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var method in updateInfo.updatedMethods) method.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
// Update getter and setter methods for properties
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var property in updateInfo.updatedProperties) property.UpdateGettersAndSetters(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
// Update instruction types for methods
for (int i = 0; i < updateInfo.destTypes.Count; ++i)
foreach (var method in updateInfo.updatedMethods) method.UpdateInstructionTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
// Check for optimization opportunities for methods
foreach (var method in updateInfo.updatedMethods) method.OptimizeInstructions();
// Import references for attributes, interfaces, fields, properties, and methods
foreach (var attribute in updateInfo.updatedAttributes) attribute.ImportReferences(assembly.MainModule);
foreach (var @interface in updateInfo.updatedInterfaces) @interface.ImportReferences(assembly.MainModule);
foreach (var field in updateInfo.updatedFields) field.ImportReferences(assembly.MainModule);
foreach (var property in updateInfo.updatedProperties) property.ImportReferences(assembly.MainModule);
foreach (var method in updateInfo.updatedMethods) method.ImportReferences(assembly.MainModule);
// Import base type of each dest type
foreach (var type in updateInfo.destTypes) type.BaseType = assembly.MainModule.ImportReference(type.BaseType);
// Swap any duplicate methods in the destination types
foreach (var type in updateInfo.destTypes) type.SwapDuplicateMethods(avoidSignatureConflicts);
// Remove the assembly from the update information collection
_ = assemblyUpdateInfo.Remove(assembly);
}
}
#endregion UpdateFieldsPropertiesAndMethods
// Extension methods for cloning various Mono.Cecil objects.
#region Clone
/// <summary>
/// Clones a CustomAttribute.
/// </summary>
/// <param name="attribute">The attribute to be cloned.</param>
/// <returns>A clone of the original attribute.</returns>
public static CustomAttribute Clone(this CustomAttribute attribute)
{
// Create a new CustomAttribute with the constructor of the original attribute.
var clonedAttribute = new CustomAttribute(attribute.Constructor);
// Add all constructor arguments from the original attribute to the cloned attribute.
foreach (var argument in attribute.ConstructorArguments) clonedAttribute.ConstructorArguments.Add(argument);
// Add all properties from the original attribute to the cloned attribute.
foreach (var property in attribute.Properties) clonedAttribute.Properties.Add(property);
// Add all fields from the original attribute to the cloned attribute.
foreach (var field in attribute.Fields) clonedAttribute.Fields.Add(field);
// Return the cloned attribute.
return clonedAttribute;
}
/// <summary>
/// Clones a InterfaceImplementation.
/// </summary>
/// <param name="interface">The interface to be cloned.</param>
/// <returns>A clone of the original interface.</returns>
public static InterfaceImplementation Clone(this InterfaceImplementation @interface)
{
// Create a new InterfaceImplementation with the type the original interface.
var clonedInterface = new InterfaceImplementation(@interface.InterfaceType);
// Copy all custom attributes from the original interface to the cloned interface.
foreach (var attribute in @interface.CustomAttributes) clonedInterface.CustomAttributes.Add(attribute.Clone());
// Return the cloned interface.
return clonedInterface;
}
/// <summary>
/// Clones a FieldDefinition.
/// </summary>
/// <param name="field">The field to be cloned.</param>
/// <returns>A clone of the original field.</returns>
public static FieldDefinition Clone(this FieldDefinition field)
{
// Create a new FieldDefinition with the same properties as the original field.
var clonedField = new FieldDefinition(field.Name, field.Attributes, field.FieldType);
// Copy all custom attributes from the original field to the cloned field.
foreach (var attribute in field.CustomAttributes) clonedField.CustomAttributes.Add(attribute.Clone());
// Copy the MarshalInfo if it exists.
clonedField.MarshalInfo = field.MarshalInfo != null ? new MarshalInfo(field.MarshalInfo.NativeType) : null;
// Copy the initial value of the field.
clonedField.InitialValue = field.InitialValue;
// Return the cloned field.
return clonedField;
}
/// <summary>
/// Clones a PropertyDefinition.
/// </summary>
/// <param name="property">The property to be cloned.</param>
/// <returns>A clone of the original property.</returns>
public static PropertyDefinition Clone(this PropertyDefinition property)
{
// Create a new PropertyDefinition with the same properties as the original property.
var clonedProperty = new PropertyDefinition(property.Name, property.Attributes, property.PropertyType);
// Copy all custom attributes from the original property to the cloned property.
foreach (var attribute in property.CustomAttributes) clonedProperty.CustomAttributes.Add(attribute.Clone());
// Clone the get and set methods if they exist.
clonedProperty.GetMethod = property.GetMethod?.Clone();
clonedProperty.SetMethod = property.SetMethod?.Clone();
// Return the cloned property.
return clonedProperty;
}
/// <summary>
/// Clones a ParameterDefinition.
/// </summary>
/// <param name="parameter">The parameter to be cloned.</param>
/// <returns>A clone of the original parameter.</returns>
public static ParameterDefinition Clone(this ParameterDefinition parameter)
{
// Create a new ParameterDefinition with the same properties as the original parameter.
var clonedParameter = new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType);
// Copy all custom attributes from the original parameter to the cloned parameter.
foreach (var attribute in parameter.CustomAttributes) clonedParameter.CustomAttributes.Add(attribute.Clone());
// Return the cloned parameter.
return clonedParameter;
}
/// <summary>
/// Clones a VariableDefinition.
/// </summary>
/// <param name="variable">The variable to be cloned.</param>
/// <returns>A clone of the original variable.</returns>
public static VariableDefinition Clone(this VariableDefinition variable)
{
// Create and return a new VariableDefinition with the same type as the original variable.
return new VariableDefinition(variable.VariableType);
}
/// <summary>
/// Clones an Instruction.
/// </summary>
/// <param name="instruction">The instruction to be cloned.</param>
/// <returns>A clone of the original instruction.</returns>
public static Instruction Clone(this Instruction instruction)
{
// Create a new Instruction with a default opcode.
var clonedInstruction = Instruction.Create(OpCodes.Nop);
// Copy the opcode and operand from the original instruction to the cloned instruction.
clonedInstruction.OpCode = instruction.OpCode;
clonedInstruction.Operand = instruction.Operand;
// Return the cloned instruction.
return clonedInstruction;
}
/// <summary>
/// Clones a MethodDefinition.
/// </summary>
/// <param name="method">The method to be cloned.</param>
/// <returns>A clone of the original method.</returns>
public static MethodDefinition Clone(this MethodDefinition method)
{
// Create a new MethodDefinition with the same properties as the original method.
var clonedMethod = new MethodDefinition(method.Name, method.Attributes, method.ReturnType)
{
ImplAttributes = method.ImplAttributes,
SemanticsAttributes = method.SemanticsAttributes
};
// Add all overides from the original method to the cloned method (references).
foreach (var @override in method.Overrides) clonedMethod.Overrides.Add(@override);
// Copy all custom attributes from the original method to the cloned method.
foreach (var attribute in method.CustomAttributes) clonedMethod.CustomAttributes.Add(attribute.Clone());
// Clone all parameters and add them to the cloned method.
foreach (var parameter in method.Parameters) clonedMethod.Parameters.Add(parameter.Clone());
// Create a new method body for the cloned method.
clonedMethod.Body = new MethodBody(clonedMethod);
// If the original method has a body, copy the relevant properties to the cloned method's body.
if (method.HasBody)
{
clonedMethod.Body.MaxStackSize = method.Body.MaxStackSize;
clonedMethod.Body.InitLocals = method.Body.InitLocals;
// Clone all variables and add them to the cloned method's body.
foreach (var variable in method.Body.Variables) clonedMethod.Body.Variables.Add(variable.Clone());
// Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
var instructionMapping = new Dictionary<Instruction, Instruction>();
// Clone all the instructions and create the mapping.
foreach (var instruction in method.Body.Instructions)
{
var clonedInstruction = instruction.Clone();
instructionMapping[instruction] = clonedInstruction;
clonedMethod.Body.Instructions.Add(clonedInstruction);
}
// Now fix up the branch targets.
foreach (var instruction in clonedMethod.Body.Instructions)
{
// If the instruction is a branch instruction, fix up its target.
if (instruction.OpCode.FlowControl == FlowControl.Branch ||
instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
{
instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
}
// If the instruction is a switch instruction, fix up its targets.
if (instruction.OpCode == OpCodes.Switch)
{
var oldTargets = (Instruction[])instruction.Operand;
var newTargets = new Instruction[oldTargets.Length];
for (int i = 0; i < oldTargets.Length; ++i)
{
newTargets[i] = instructionMapping[oldTargets[i]];
}
instruction.Operand = newTargets;
}
}
}
// Return the cloned method.
return clonedMethod;
}
public static MethodDefinition CloneToModuleAsStatic(this MethodDefinition method, TypeReference originalType, ModuleDefinition module)
{
// Create a new MethodDefinition with the same properties as the original method.
var clonedMethod = new MethodDefinition(method.Name, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, module.ImportReference(method.ReturnType))
{
ImplAttributes = method.ImplAttributes,
SemanticsAttributes = method.SemanticsAttributes
};
// Copy all custom attributes from the original method to the cloned method.
foreach (var attribute in method.CustomAttributes)
clonedMethod.CustomAttributes.Add(attribute.CloneToModule(module));
if (!method.Attributes.HasFlag(MethodAttributes.Static))
clonedMethod.Parameters.Add(new ParameterDefinition("self", ParameterAttributes.None, originalType));
// Clone all parameters and add them to the cloned method.
foreach (var parameter in method.Parameters)
clonedMethod.Parameters.Add(parameter.CloneToModule(module));
// Create a new method body for the cloned method.
clonedMethod.Body = new MethodBody(clonedMethod);
// If the original method has a body, copy the relevant properties to the cloned method's body.
if (method.HasBody)
{
clonedMethod.Body.MaxStackSize = method.Body.MaxStackSize;
clonedMethod.Body.InitLocals = method.Body.InitLocals;
// Clone all variables and add them to the cloned method's body.
foreach (var variable in method.Body.Variables) clonedMethod.Body.Variables.Add(variable.CloneToModule(module));
// Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
var instructionMapping = new Dictionary<Instruction, Instruction>();
// Clone all the instructions and create the mapping.
foreach (var instruction in method.Body.Instructions)
{
var clonedInstruction = instruction.Clone();
clonedInstruction.Resolve(clonedMethod, method, module);
instructionMapping[instruction] = clonedInstruction;
clonedMethod.Body.Instructions.Add(clonedInstruction);
}
// Now fix up the branch targets.
foreach (var instruction in clonedMethod.Body.Instructions)
{
// If the instruction is a branch instruction, fix up its target.
if (instruction.OpCode.FlowControl == FlowControl.Branch ||
instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
{
instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
}
// If the instruction is a switch instruction, fix up its targets.
if (instruction.OpCode == OpCodes.Switch)
{
var oldTargets = (Instruction[])instruction.Operand;
var newTargets = new Instruction[oldTargets.Length];
for (int i = 0; i < oldTargets.Length; ++i)
{
newTargets[i] = instructionMapping[oldTargets[i]];
}
instruction.Operand = newTargets;
}
}
// copy the exception handler blocks
foreach (ExceptionHandler eh in method.Body.ExceptionHandlers)
{
ExceptionHandler neh = new ExceptionHandler(eh.HandlerType);
neh.CatchType = module.ImportReference(eh.CatchType);
// we need to setup neh.Start and End; these are instructions; we need to locate it in the source by index
if (eh.TryStart != null)
{
int idx = method.Body.Instructions.IndexOf(eh.TryStart);
neh.TryStart = clonedMethod.Body.Instructions[idx];
}
if (eh.TryEnd != null)
{
int idx = method.Body.Instructions.IndexOf(eh.TryEnd);
neh.TryEnd = clonedMethod.Body.Instructions[idx];
}
clonedMethod.Body.ExceptionHandlers.Add(neh);
}
}
// Return the cloned method.
return clonedMethod;
}
public static MethodDefinition CloneToModuleAsStatic(this MethodDefinition method, MethodBody body, TypeReference originalType, ModuleDefinition module)
{
// Create a new MethodDefinition with the same properties as the original method.
var clonedMethod = new MethodDefinition(method.Name, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, module.ImportReference(method.ReturnType))
{
ImplAttributes = method.ImplAttributes,
SemanticsAttributes = method.SemanticsAttributes
};
// Copy all custom attributes from the original method to the cloned method.
foreach (var attribute in method.CustomAttributes)
clonedMethod.CustomAttributes.Add(attribute.CloneToModule(module));
if (!method.Attributes.HasFlag(MethodAttributes.Static))
clonedMethod.Parameters.Add(new ParameterDefinition("self", ParameterAttributes.None, originalType));
// Clone all parameters and add them to the cloned method.
foreach (var parameter in method.Parameters)
clonedMethod.Parameters.Add(parameter.CloneToModule(module));
// Create a new method body for the cloned method.
clonedMethod.Body = new MethodBody(clonedMethod);
// If the original method has a body, copy the relevant properties to the cloned method's body.
clonedMethod.Body.MaxStackSize = body.MaxStackSize;
clonedMethod.Body.InitLocals = body.InitLocals;
// Clone all variables and add them to the cloned method's body.
foreach (var variable in body.Variables) clonedMethod.Body.Variables.Add(variable.CloneToModule(module));
// Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
var instructionMapping = new Dictionary<Instruction, Instruction>();
// Clone all the instructions and create the mapping.
foreach (var instruction in body.Instructions)
{
var clonedInstruction = instruction.Clone();
clonedInstruction.Resolve(clonedMethod, method, module);
instructionMapping[instruction] = clonedInstruction;
clonedMethod.Body.Instructions.Add(clonedInstruction);
}
// Now fix up the branch targets.
foreach (var instruction in clonedMethod.Body.Instructions)
{
// If the instruction is a branch instruction, fix up its target.
if (instruction.OpCode.FlowControl == FlowControl.Branch ||
instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
{
instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
}
// If the instruction is a switch instruction, fix up its targets.
if (instruction.OpCode == OpCodes.Switch)
{
var oldTargets = (Instruction[])instruction.Operand;
var newTargets = new Instruction[oldTargets.Length];
for (int i = 0; i < oldTargets.Length; ++i)
{
newTargets[i] = instructionMapping[oldTargets[i]];
}
instruction.Operand = newTargets;
}
}
// copy the exception handler blocks
foreach (ExceptionHandler eh in body.ExceptionHandlers)
{
ExceptionHandler neh = new ExceptionHandler(eh.HandlerType);
neh.CatchType = module.ImportReference(eh.CatchType);
// we need to setup neh.Start and End; these are instructions; we need to locate it in the source by index
if (eh.TryStart != null)
{
int idx = body.Instructions.IndexOf(eh.TryStart);
neh.TryStart = clonedMethod.Body.Instructions[idx];
}
if (eh.TryEnd != null)
{
int idx = body.Instructions.IndexOf(eh.TryEnd);
neh.TryEnd = clonedMethod.Body.Instructions[idx];
}
clonedMethod.Body.ExceptionHandlers.Add(neh);
}
// Return the cloned method.
return clonedMethod;
}
public static VariableDefinition CloneToModule(this VariableDefinition variable, ModuleDefinition module)
{
// Create and return a new VariableDefinition with the same type as the original variable.
return new VariableDefinition(module.ImportReference(variable.VariableType));
}
public static ParameterDefinition CloneToModule(this ParameterDefinition parameter, ModuleDefinition module)
{
// Create a new ParameterDefinition with the same properties as the original parameter.
var clonedParameter = new ParameterDefinition(parameter.Name, parameter.Attributes, module.ImportReference(parameter.ParameterType));
// Copy all custom attributes from the original parameter to the cloned parameter.
foreach (var attribute in parameter.CustomAttributes)
{
module.ImportReference(attribute.GetType());
clonedParameter.CustomAttributes.Add(attribute.CloneToModule(module));
}
// Return the cloned parameter.
return clonedParameter;
}
public static CustomAttribute CloneToModule(this CustomAttribute attribute, ModuleDefinition module)
{
// Create a new CustomAttribute with the constructor of the original attribute.
var clonedAttribute = new CustomAttribute(module.ImportReference(attribute.Constructor));
// Add all constructor arguments from the original attribute to the cloned attribute.
foreach (var argument in attribute.ConstructorArguments)
{
module.ImportReference(argument.Type);
if (argument.Value != null)
{
module.ImportReference(argument.Value?.GetType());
}
clonedAttribute.ConstructorArguments.Add(argument);
}
// Add all properties from the original attribute to the cloned attribute.
foreach (var property in attribute.Properties)
{
module.ImportReference(property.Argument.Type);
if (property.Argument.Value != null)
{
module.ImportReference(property.Argument.Value?.GetType());
}
clonedAttribute.Properties.Add(property);
}
// Add all fields from the original attribute to the cloned attribute.
foreach (var field in attribute.Fields)
{
module.ImportReference(field.Argument.Type);
if (field.Argument.Value != null)
{
module.ImportReference(field.Argument.Value?.GetType());
}
clonedAttribute.Fields.Add(field);
}
// Return the cloned attribute.
return clonedAttribute;
}
private static void Resolve(this Instruction instruction, MethodDefinition method_new, MethodDefinition method_original, ModuleDefinition module)
{
var operand = instruction.Operand;
if (operand == null)
return;
if (operand is MethodDefinition opmtddef)
{
if (opmtddef.FullName.Equals(method_original.FullName))
instruction.Operand = method_new;
else
instruction.Operand = module.ImportReference(opmtddef);
return;
}
if (operand is MethodReference opmtdref)
{
instruction.Operand = module.ImportReference(opmtdref);
return;
}
if (operand is FieldReference opfldref)
{
instruction.Operand = module.ImportReference(opfldref);
return;
}
if (operand is TypeReference optyperef)
{
instruction.Operand = module.ImportReference(optyperef);
return;
}
}
#endregion Clone
// Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil objects.
// This is used to ensure that copied fields, properties, and methods reference copied types instead of the originals.
#region UpdateTypes
/// <summary>
/// Updates the InterfaceType of the given InterfaceImplementation, if it matches the source type, to the destination type.
/// </summary>
/// <param name="interface">InterfaceImplementation that may have its InterfaceType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this InterfaceImplementation @interface, TypeDefinition src, TypeDefinition dest)
{
// If the interface's type matches the source type, update it to the destination type
if (@interface.InterfaceType == src) @interface.InterfaceType = dest;
}
/// <summary>
/// Updates the FieldType of the given FieldDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="field">FieldDefinition that may have its FieldType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this FieldDefinition field, TypeDefinition src, TypeDefinition dest)
{
// If the field's type matches the source type, update it to the destination type
if (field.FieldType == src) field.FieldType = dest;
}
/// <summary>
/// Updates the FieldReference and DeclaringType of the given FieldReference, if they match the source type, to the destination type.
/// If a matching field definition is found in the destination type, a reference to it is returned.
/// Otherwise, the original field reference is returned.
/// </summary>
/// <param name="field">FieldReference that may have its FieldType, and DeclaringType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
/// <returns>A FieldReference with updated types, or the original FieldReference if no updates were made.</returns>
public static FieldReference UpdateTypes(this FieldReference field, TypeDefinition src, TypeDefinition dest)
{
// Check if the field's FieldType or DeclaringType matches the source type, and if so, replace them with the destination type
if (field.FieldType == src) field.FieldType = dest;
if (field.DeclaringType == src) field.DeclaringType = dest;
// Attempt to find a field in the destination type that matches the field's full name
// If a matching definition is found, return a reference to it otherwise return original reference
return dest.FindField(field.FullName) ?? field;
}
/// <summary>
/// Updates the PropertyType of the given PropertyDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="property">PropertyDefinition that may have its PropertyType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this PropertyDefinition property, TypeDefinition src, TypeDefinition dest)
{
// If the property's type matches the source type, update it to the destination type
if (property.PropertyType == src) property.PropertyType = dest;
}
/// <summary>
/// Updates the ParameterType of the given ParameterDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="parameter">ParameterDefinition that may have its ParameterType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this ParameterDefinition parameter, TypeDefinition src, TypeDefinition dest)
{
// If the parameter's type matches the source type, update it to the destination type
if (parameter.ParameterType == src) parameter.ParameterType = dest;
}
/// <summary>
/// Updates the VariableType of the given VariableDefinition, if it matches the source type, to the destination type.
/// </summary>
/// <param name="variable">VariableDefinition that may have its VariableType updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this VariableDefinition variable, TypeDefinition src, TypeDefinition dest)
{
// If the variable's type matches the source type, update it to the destination type
if (variable.VariableType == src) variable.VariableType = dest;
}
/// <summary>
/// Updates the ReturnType of the given MethodDefinition, if it matches the source type, to the destination type.
/// Also updates ParameterTypes and VariableTypes of the MethodDefinition using the same rule.
/// </summary>
/// <param name="method">MethodDefinition that may have its ReturnType, ParameterTypes, and VariableTypes updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
public static void UpdateTypes(this MethodDefinition method, TypeDefinition src, TypeDefinition dest)
{
// Update method overrides if they match the source type
for (int i = 0; i < method.Overrides.Count; i++) method.Overrides[i] = method.Overrides[i].UpdateTypes(src, dest);
// If the method's return type matches the source type, update it to the destination type
if (method.ReturnType == src) method.ReturnType = dest;
// Update method parameters and variables if they match the source type
foreach (var parameter in method.Parameters) parameter.UpdateTypes(src, dest);
if (method.HasBody) foreach (var variable in method.Body.Variables) variable.UpdateTypes(src, dest);
}
/// <summary>
/// Updates the ReturnType and DeclaringType of the given MethodReference, if they match the source type, to the destination type.
/// Also updates the ParameterTypes of the MethodReference using the same rule.
/// If a matching method definition is found in the destination type, a reference to it is returned.
/// Otherwise, the original method reference is returned.
/// </summary>
/// <param name="method">MethodReference that may have its ReturnType, DeclaringType and ParameterTypes updated.</param>
/// <param name="src">The source type which could be replaced.</param>
/// <param name="dest">The destination type which could replace the source type.</param>
/// <returns>A MethodReference with updated types, or the original MethodReference if no updates were made.</returns>
public static MethodReference UpdateTypes(this MethodReference method, TypeDefinition src, TypeDefinition dest)
{
// Update method parameters to destination type
foreach (var parameter in method.Parameters) parameter.UpdateTypes(src, dest);
// Check if the method's ReturnType or DeclaringType matches the source type, and if so, replace them with the destination type
if (method.ReturnType == src) method.ReturnType = dest;
if (method.DeclaringType == src) method.DeclaringType = dest;
// Attempt to find a method in the destination type that matches the method's full name
// If a matching definition is found, return a reference to it otherwise return original reference
return dest.FindMethod(method.FullName) ?? method;
}
/// <summary>
/// Updates the ReturnType and Parameters of the CallSite to the destination type, if they match the source type, to the destination type.
/// </summary>
/// <param name="callSite">CallSite that needs its return type and parameters updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateTypes(this CallSite callSite, TypeDefinition src, TypeDefinition dest)
{
// Update callsite parameters to destination type
foreach (var parameter in callSite.Parameters) parameter.UpdateTypes(src, dest);
// If the current return type is the source type, update it to destination type
if (callSite.ReturnType == src) callSite.ReturnType = dest;
}
#endregion UpdateTypes
// Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil.Instruction objects.
// This is crucial for ensuring that the instructions within methods correctly reference the fields, properties, and methods of the destination type after cloning from the source type.
#region UpdateInstructionTypes
/// <summary>
/// Updates the Operand of an instruction when merging classes.
/// The update strategy depends on the type of the operand.
/// If the operand is a ParameterDefinition, VariableDefinition, FieldReference, MethodReference, CallSite, or TypeReference, it's updated accordingly.
/// </summary>
/// <param name="instruction">Instruction that needs its operand updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateInstructionTypes(this Instruction instruction, TypeDefinition src, TypeDefinition dest)
{
// Check operand type and update accordingly
if (instruction.Operand is ParameterDefinition parameter)
parameter.UpdateTypes(src, dest); // Update types in ParameterDefinition
else if (instruction.Operand is VariableDefinition variable)
variable.UpdateTypes(src, dest); // Update types in VariableDefinition
else if (instruction.Operand is TypeReference type && type == src)
instruction.Operand = dest; // Update type in TypeReference
else if (instruction.Operand is FieldReference field)
instruction.Operand = field.UpdateTypes(src, dest); // Update types in FieldReference
else if (instruction.Operand is MethodReference method)
instruction.Operand = method.UpdateTypes(src, dest); // Update types in MethodReference
else if (instruction.Operand is CallSite callSite)
callSite.UpdateTypes(src, dest); // Update types in CallSite
}
/// <summary>
/// Updates all instructions in the method's body.
/// </summary>
/// <param name="method">Method whose instructions are to be updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateInstructionTypes(this MethodDefinition method, TypeDefinition src, TypeDefinition dest)
{
// Update instructions in the method body to the destination type
if (method.HasBody) foreach (var instruction in method.Body.Instructions) UpdateInstructionTypes(instruction, src, dest);
}
#endregion UpdateInstructionTypes
// Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil.Property getter and setter methods.
// This ensures that the properties of the destination type reference copied getters and setters instead of the originals.
#region UpdateGettersAndSetters
/// <summary>
/// Updates the getter and setter methods of a property to reference the destination type when merging classes.
/// This method does the following:
/// - Clones the existing getter/setter methods, so that any modifications do not affect the original methods
/// - Calls UpdateTypes to update all type references within the methods' bodies from src to dest
/// - Updates the declaring type of the methods to be dest
/// - Finds the equivalent methods in dest (if they exist), and updates the property's getter/setter methods to reference them
/// This ensures that the property correctly interacts with the destination type after merging.
/// </summary>
/// <param name="property">PropertyDefinition whose getter and setter need to be updated.</param>
/// <param name="src">The original type which is being replaced.</param>
/// <param name="dest">The new type which is replacing the original type.</param>
public static void UpdateGettersAndSetters(this PropertyDefinition property, TypeDefinition src, TypeDefinition dest)
{
// If the declaring type of the property is the destination type
if (property.DeclaringType == dest)
{
// If the property has a getter, clone and update it
if (property.GetMethod != null)
{
// Clone the getter
var clonedGetter = property.GetMethod.Clone();
// Update all type references within the getter from src to dest
clonedGetter.UpdateTypes(src, dest);
// Update the declaring type of the getter to be dest
clonedGetter.DeclaringType = dest;
// If an equivalent method exists in dest, update the property's getter to reference it
if (dest.FindMethod(clonedGetter.FullName) is MethodDefinition getMethod)
property.GetMethod = getMethod;
}
// If the property has a setter, clone and update it
if (property.SetMethod != null)
{
// Clone the setter
var clonedSetter = property.SetMethod.Clone();
// Update all type references within the setter from src to dest
clonedSetter.UpdateTypes(src, dest);
// Update the declaring type of the setter to be dest
clonedSetter.DeclaringType = dest;
// If an equivalent method exists in dest, update the property's setter to reference it
if (dest.FindMethod(clonedSetter.FullName) is MethodDefinition setMethod)
property.SetMethod = setMethod;
}
}
}
#endregion UpdateGettersAndSetters
// Extension methods to import references from one module to another.
// This is important when merging assemblies classes as it allows the destination to access types that may not have been referenced prior.
#region ImportReferences
/// <summary>
/// Imports the constructor reference for a given attribute into a module.
/// </summary>
/// <param name="attribute">The custom attribute whose constructor reference needs to be imported.</param>
/// <param name="module">The module type into whose module the reference should be imported.</param>
public static void ImportReferences(this CustomAttribute attribute, ModuleDefinition module)
{
// Import the constructor reference into the module
attribute.Constructor = module.ImportReference(attribute.Constructor);
}
/// <summary>
/// Imports the interface type and custom attributes references of an interface into a module.
/// </summary>
/// <param name="interface">The interface whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this InterfaceImplementation @interface, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in @interface.CustomAttributes) attribute.ImportReferences(module);
// Import the interface type reference into the module
@interface.InterfaceType = module.ImportReference(@interface.InterfaceType);
}
/// <summary>
/// Imports the field type and custom attributes references of a field into a module.
/// </summary>
/// <param name="field">The field whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this FieldDefinition field, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in field.CustomAttributes) attribute.ImportReferences(module);
// Import the field type reference into the module
field.FieldType = module.ImportReference(field.FieldType);
// Import the declaring type definition into the module
field.DeclaringType = module.ImportReference(field.DeclaringType).Resolve();
}
/// <summary>
/// Imports the property type and custom attributes references of a property into a module.
/// </summary>
/// <param name="property">The property whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this PropertyDefinition property, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in property.CustomAttributes) attribute.ImportReferences(module);
// Import the property type reference into the module
property.PropertyType = module.ImportReference(property.PropertyType);
// Import the declaring type definition into the module
property.DeclaringType = module.ImportReference(property.DeclaringType).Resolve();
}
/// <summary>
/// Imports the parameter type and custom attributes references of a parameter into a module.
/// </summary>
/// <param name="parameter">The parameter whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this ParameterDefinition parameter, ModuleDefinition module)
{
// Import the custom attributes references into the module
foreach (var attribute in parameter.CustomAttributes) attribute.ImportReferences(module);
// Import the parameter type reference into the module
parameter.ParameterType = module.ImportReference(parameter.ParameterType);
}
/// <summary>
/// Imports the variable type references of a variable into a module.
/// </summary>
/// <param name="variable">The variable whose type references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this VariableDefinition variable, ModuleDefinition module)
{
// Import the variable type reference into the module
variable.VariableType = module.ImportReference(variable.VariableType);
}
/// <summary>
/// Imports the method type references and the custom attributes of a method into a module.
/// </summary>
/// <param name="method">The method whose references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this MethodDefinition method, ModuleDefinition module)
{
// Import method overrides into the module
for (int i = 0; i < method.Overrides.Count; ++i) method.Overrides[i] = module.ImportReference(method.Overrides[i]);
// Import the custom attributes references into the module
foreach (var attribute in method.CustomAttributes) attribute.ImportReferences(module);
// Import the parameter type references into the module
foreach (var parameter in method.Parameters) parameter.ImportReferences(module);
// Import the return type reference into the module
method.ReturnType = module.ImportReference(method.ReturnType);
// Import the declaring type definition into the module
method.DeclaringType = module.ImportReference(method.DeclaringType).Resolve();
// If the method has a body, import references for each variable and instruction
if (method.HasBody)
{
// Import the variable type references in the method body into the module
foreach (var variable in method.Body.Variables) variable.ImportReferences(module);
// Import the instruction type references in the method body into the module
foreach (var instruction in method.Body.Instructions) instruction.ImportReferences(module);
}
}
/// <summary>
/// Imports the return type references of a CallSite into a module.
/// </summary>
/// <param name="callSite">The CallSite whose return type references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this CallSite callSite, ModuleDefinition module)
{
// Import the return type reference of the callSite into the module
callSite.ReturnType = module.ImportReference(callSite.ReturnType);
}
/// <summary>
/// Imports the operand type references of an instruction into a module.
/// </summary>
/// <param name="instruction">The instruction whose operand references need to be imported.</param>
/// <param name="module">The module type into whose module the references should be imported.</param>
public static void ImportReferences(this Instruction instruction, ModuleDefinition module)
{
// Import the operand references of the instruction into the module
if (instruction.Operand is ParameterDefinition parameter)
parameter.ImportReferences(module);
else if (instruction.Operand is VariableDefinition variable)
variable.ImportReferences(module);
else if (instruction.Operand is TypeReference type)
instruction.Operand = module.ImportReference(type);
else if (instruction.Operand is FieldReference field)
instruction.Operand = module.ImportReference(field);
else if (instruction.Operand is MethodReference method)
instruction.Operand = module.ImportReference(method);
else if (instruction.Operand is CallSite callSite)
callSite.ImportReferences(module);
}
#endregion ImportReferences
// Extension methods for swapping method implementations between different types.
// This can be used when wanting to replace method functionality in the destination type with the corresponding functionality from the source type.
#region SwapMethods
/// <summary>
/// Swaps the method references within the provided instruction between two given methods.
/// </summary>
/// <param name="instruction">The instruction to modify.</param>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethodReferences(this Instruction instruction, MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// If the instruction's operand is a method reference
if (instruction.Operand is MethodReference method)
{
// If the operand matches the left method, replace it with the right method
if (method == leftMethod)
instruction.Operand = rightMethod;
// If the operand matches the right method, replace it with the left method
else if (method == rightMethod)
instruction.Operand = leftMethod;
}
}
/// <summary>
/// Swaps the method references within the provided collection of instructions between two given methods.
/// </summary>
/// <param name="instructions">The collection of instructions to modify.</param>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethodReferences(this Collection<Instruction> instructions, MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// Swap method references for each instruction in the collection
foreach (var instruction in instructions)
instruction.SwapMethodReferences(leftMethod, rightMethod);
}
/// <summary>
/// Swaps the method references within the body of the provided method between two given methods.
/// </summary>
/// <param name="method">The method to modify.</param>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethodReferences(this MethodDefinition method, MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// Swap method references for each instruction in the method's body
if (method.HasBody) method.Body.Instructions.SwapMethodReferences(leftMethod, rightMethod);
}
/// <summary>
/// Swaps the attributes, parameters, custom attributes, and generic parameters between two given methods.
/// </summary>
/// <param name="leftMethod">The first method to swap.</param>
/// <param name="rightMethod">The second method to swap.</param>
public static void SwapMethods(this MethodDefinition leftMethod, MethodDefinition rightMethod)
{
// Save the left method's original details
var leftBody = leftMethod.Body;
var leftAttributes = leftMethod.Attributes;
var leftImplAttributes = leftMethod.ImplAttributes;
var leftSemanticsAttributes = leftMethod.SemanticsAttributes;
var leftParameters = new Collection<ParameterDefinition>(leftMethod.Parameters);
var leftCustomAttributes = new Collection<CustomAttribute>(leftMethod.CustomAttributes);
var leftGenericParameters = new Collection<GenericParameter>(leftMethod.GenericParameters);
// Swap the details from the right method to the left
leftMethod.Body = rightMethod.Body;
leftMethod.Body = rightMethod.Body;
leftMethod.Attributes = rightMethod.Attributes;
leftMethod.ImplAttributes = rightMethod.ImplAttributes;
leftMethod.SemanticsAttributes = rightMethod.SemanticsAttributes;
leftMethod.Parameters.Clear();
leftMethod.CustomAttributes.Clear();
leftMethod.GenericParameters.Clear();
foreach (var parameter in rightMethod.Parameters) leftMethod.Parameters.Add(parameter);
foreach (var attribute in rightMethod.CustomAttributes) leftMethod.CustomAttributes.Add(attribute);
foreach (var parameter in rightMethod.GenericParameters) leftMethod.GenericParameters.Add(parameter);
// Swap the details from the left method (which were saved) to the right
rightMethod.Body = leftBody;
rightMethod.Body = leftBody;
rightMethod.Attributes = leftAttributes;
rightMethod.ImplAttributes = leftImplAttributes;
rightMethod.SemanticsAttributes = leftSemanticsAttributes;
rightMethod.Parameters.Clear();
rightMethod.CustomAttributes.Clear();
rightMethod.GenericParameters.Clear();
foreach (var parameter in leftParameters) rightMethod.Parameters.Add(parameter);
foreach (var attribute in leftCustomAttributes) rightMethod.CustomAttributes.Add(attribute);
foreach (var parameter in leftGenericParameters) rightMethod.GenericParameters.Add(parameter);
// Swap method references within each method body
leftMethod.SwapMethodReferences(leftMethod, rightMethod);
rightMethod.SwapMethodReferences(rightMethod, leftMethod);
}
/// <summary>
/// Finds and swaps methods with the same full name within the given type.
/// </summary>
/// <param name="type">The type to modify.</param>
/// <param name="avoidSignatureConflicts">Avoid signature conflicts by changing original method parameters to be base object types</param>
public static void SwapDuplicateMethods(this TypeDefinition type, bool avoidSignatureConflicts = false)
{
// This HashSet is used for tracking the methods that have already been swapped.
var alreadySwapped = new HashSet<string>();
// Convert the method collection to list for efficient index-based access.
var methods = type.Methods.ToList();
// Iterate over each pair of methods in the type
for (int i = 0; i < methods.Count; i++)
{
for (int j = i + 1; j < methods.Count; j++)
{
var methodLeft = methods[i];
var methodRight = methods[j];
// If two methods have the same full name and haven't been swapped yet
if (methodLeft.FullName == methodRight.FullName && !alreadySwapped.Contains(methodLeft.FullName))
{
// Add the method full name to the set of already swapped methods
_ = alreadySwapped.Add(methodLeft.FullName);
// Swap the two methods
methodLeft.SwapMethods(methodRight);
// Change the original method types to be generic to avoid signature conflicts
if (avoidSignatureConflicts)
{
foreach (var parameter in methodRight.Parameters)
if (!parameter.ParameterType.IsValueType) parameter.ParameterType = type.Module.ImportReference(typeof(object));
}
}
}
}
}
#endregion SwapMethods
// Methods to do with instruction optimizations
#region InstructionOptimizations
#pragma warning disable RCS1003
/// <summary>
/// Determines if a given instruction within a method can be optimized out.
/// Specifically, this method looks for type conversion instructions (Isinst or Castclass)
/// that are unnecessary because the type of the value at the top of the stack is
/// already the target conversion type.
/// </summary>
/// <param name="instruction">The instruction to be checked for optimization.</param>
/// <param name="method">The method definition that contains the instruction.</param>
/// <returns>Returns true if the instruction can be optimized out. Otherwise, returns false.</returns>
/// <remarks>
/// This method works by examining the instructions before the given instruction in the method,
/// maintaining a conceptual "stack balance" and tracking the type of the value at the top of the stack.
/// The stack balance is a measure of the net effect of the instructions on the stack,
/// with a positive balance indicating more values have been pushed than popped,
/// and a negative balance indicating more values have been popped than pushed.
/// If the stack balance is zero and the type of the value at the top of the stack
/// matches the type conversion, the conversion is unnecessary and the method returns true.
/// </remarks>
public static bool CanBeOptimizedOut(this Instruction instruction, MethodDefinition method)
{
// Check if the instruction is a type conversion instruction (instance cast or class cast)
if (instruction.OpCode == OpCodes.Isinst || instruction.OpCode == OpCodes.Castclass)
{
// Get the type to which the conversion is being made
var typeConversionType = instruction.Operand as TypeReference;
// Initialize stack balance. This will help to determine the net stack effect of the instructions
int stackBalance = 0;
// Move to the previous instruction
instruction = instruction.Previous;
// Process previous instructions
while (instruction != null)
{
// Determine how the current instruction modifies the stack
var pushBehaviour = instruction.OpCode.StackBehaviourPush;
var popBehaviour = instruction.OpCode.StackBehaviourPop;
// Fullname of any type extracted from the instruction
string extractedFullName = null;
// This is an exhaustive check for control flow change instructions. These instructions will cause a jump
// in execution or a termination of the function, thus ending our analysis.
if (instruction.OpCode == OpCodes.Ret || // Return from the current method.
instruction.OpCode == OpCodes.Throw || // Throw an exception.
instruction.OpCode == OpCodes.Rethrow || // Rethrow the current exception.
instruction.OpCode == OpCodes.Endfilter || // End the filter clause of an exception block and branch to the exception handler.
instruction.OpCode == OpCodes.Endfinally || // Transfer control from the exception block of a try or catch block.
instruction.OpCode == OpCodes.Leave || instruction.OpCode == OpCodes.Leave_S || // Exit a protected region of code.
instruction.OpCode == OpCodes.Jmp || // Jump to the method pointed to by the method pointer loaded on the stack.
instruction.OpCode == OpCodes.Switch || // Switch control to one of several locations.
instruction.OpCode == OpCodes.Br || instruction.OpCode == OpCodes.Br_S || // Unconditional branch to target.
instruction.OpCode == OpCodes.Brfalse || instruction.OpCode == OpCodes.Brfalse_S || // Branch to target if value is zero (false).
instruction.OpCode == OpCodes.Brtrue || instruction.OpCode == OpCodes.Brtrue_S || // Branch to target if value is non-zero (true).
instruction.OpCode == OpCodes.Beq || instruction.OpCode == OpCodes.Beq_S || // Branch to target if two values are equal.
instruction.OpCode == OpCodes.Bne_Un || instruction.OpCode == OpCodes.Bne_Un_S || // Branch to target if two values are not equal.
instruction.OpCode == OpCodes.Bge || instruction.OpCode == OpCodes.Bge_S || instruction.OpCode == OpCodes.Bge_Un || instruction.OpCode == OpCodes.Bge_Un_S || // Branch to target if value1 >= value2 (unsigned or unordered).
instruction.OpCode == OpCodes.Bgt || instruction.OpCode == OpCodes.Bgt_S || instruction.OpCode == OpCodes.Bgt_Un || instruction.OpCode == OpCodes.Bgt_Un_S || // Branch to target if value1 > value2 (unsigned or unordered).
instruction.OpCode == OpCodes.Ble || instruction.OpCode == OpCodes.Ble_S || instruction.OpCode == OpCodes.Ble_Un || instruction.OpCode == OpCodes.Ble_Un_S || // Branch to target if value1 <= value2 (unsigned or unordered).
instruction.OpCode == OpCodes.Blt || instruction.OpCode == OpCodes.Blt_S || instruction.OpCode == OpCodes.Blt_Un || instruction.OpCode == OpCodes.Blt_Un_S) // Branch to target if value1 < value2 (unsigned or unordered).
return false; // Return from method
// Check if instruction is for loading a field onto the stack
// In this case, the type of the value is the type of the field.
else if (instruction.OpCode == OpCodes.Ldfld || // load field value onto stack
instruction.OpCode == OpCodes.Ldflda || // load field address onto stack
instruction.OpCode == OpCodes.Ldsfld || // load static field value onto stack
instruction.OpCode == OpCodes.Ldsflda) // load static field address onto stack
extractedFullName = ((FieldReference)instruction.Operand).FieldType.FullName;
// Check if instruction is for loading an argument onto the stack
// In this case, the type of the value is the type of the argument.
else if (instruction.OpCode == OpCodes.Ldarg || // load argument onto stack
instruction.OpCode == OpCodes.Ldarg_S) // short form for loading argument onto stack
extractedFullName = ((ParameterReference)instruction.Operand).ParameterType.FullName;
// Check for loading argument at index 0 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_0) // load argument at index 0 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[0].ParameterType : method.DeclaringType).FullName;
// Check for loading argument at index 1 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_1) // load argument at index 1 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[1].ParameterType : method.Parameters[0].ParameterType).FullName;
// Check for loading argument at index 2 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_2) // load argument at index 2 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[2].ParameterType : method.Parameters[1].ParameterType).FullName;
// Check for loading argument at index 3 onto stack
else if (instruction.OpCode == OpCodes.Ldarg_3) // load argument at index 3 onto stack
extractedFullName = (method.IsStatic ? method.Parameters[3].ParameterType : method.Parameters[2].ParameterType).FullName;
// Check for loading local variable onto stack
else if (instruction.OpCode == OpCodes.Ldloc || // load local variable onto stack
instruction.OpCode == OpCodes.Ldloc_S) // short form for loading local variable onto stack
extractedFullName = ((VariableReference)instruction.Operand).VariableType.FullName;
// Check for loading local variable at index 0 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_0) // load local variable at index 0 onto stack
extractedFullName = method.Body.Variables[0].VariableType.FullName;
// Check for loading local variable at index 1 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_1)// load local variable at index 1 onto stack
extractedFullName = method.Body.Variables[1].VariableType.FullName;
// Check for loading local variable at index 2 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_2)// load local variable at index 2 onto stack
extractedFullName = method.Body.Variables[2].VariableType.FullName;
// Check for loading local variable at index 3 onto stack
else if (instruction.OpCode == OpCodes.Ldloc_3)// load local variable at index 3 onto stack
extractedFullName = method.Body.Variables[3].VariableType.FullName;
// Check for calling a method and pushing return value onto the stack, loading function pointer onto the stack
else if (instruction.OpCode == OpCodes.Callvirt || // call method virtually and push return value onto stack
instruction.OpCode == OpCodes.Call || // call method and push return value onto stack
instruction.OpCode == OpCodes.Ldftn || // load method pointer onto stack
instruction.OpCode == OpCodes.Ldvirtftn) // load virtual method pointer onto stack
extractedFullName = ((MethodReference)instruction.Operand).ReturnType.FullName;
// Check for calling a method indicated on the stack with arguments, pushing return value onto stack
else if (instruction.OpCode == OpCodes.Calli) // call method indicated on the stack with arguments, pushing return value onto stack
extractedFullName = ((CallSite)instruction.Operand).ReturnType.FullName;
// Check for creating a new object and pushing object reference onto stack
else if (instruction.OpCode == OpCodes.Newobj) // create a new object and push object reference onto stack
extractedFullName = ((MethodReference)instruction.Operand).DeclaringType.FullName;
// Check for loading an object, array element, or pointer onto stack, creating a new array, or creating a typed reference
else if (instruction.OpCode == OpCodes.Ldobj || // load object onto stack
instruction.OpCode == OpCodes.Ldelem_Any || // load element of an object array onto stack
instruction.OpCode == OpCodes.Newarr || // create a new array and push reference onto stack
instruction.OpCode == OpCodes.Mkrefany) // push a typed reference onto stack
extractedFullName = ((TypeReference)instruction.Operand).FullName;
// Check for loading a string onto stack
else if (instruction.OpCode == OpCodes.Ldstr) // load a string onto stack
extractedFullName = typeof(string).FullName;
// If the type of the value currently at the top of the stack matches the type conversion
// and the stack is balanced, the conversion is unnecessary
if (stackBalance == 0 && extractedFullName == typeConversionType.FullName)
return true;
// Dup doesn't change the type of the top of the stack, so adjust stack balance to ignore it
if (instruction.OpCode == OpCodes.Dup)
stackBalance--;
// Adjust stack balance according to the current instruction's push behavior
//if (pushBehaviour == StackBehaviour.Push0)
if (pushBehaviour == StackBehaviour.Push1 || pushBehaviour == StackBehaviour.Pushi || pushBehaviour == StackBehaviour.Pushref ||
pushBehaviour == StackBehaviour.Pushi8 || pushBehaviour == StackBehaviour.Pushr4 || pushBehaviour == StackBehaviour.Pushr8 ||
pushBehaviour == StackBehaviour.Varpush)
stackBalance++;
else if (pushBehaviour == StackBehaviour.Push1_push1)
stackBalance += 2;
// Adjust stack balance according to the current instruction's pop behavior
//if (popBehaviour == StackBehaviour.Pop0)
if (popBehaviour == StackBehaviour.Pop1 || popBehaviour == StackBehaviour.Popi || popBehaviour == StackBehaviour.Popref ||
popBehaviour == StackBehaviour.Varpop)
stackBalance--;
else if (popBehaviour == StackBehaviour.Pop1_pop1 || popBehaviour == StackBehaviour.Popi_popi || popBehaviour == StackBehaviour.Popi_pop1 ||
popBehaviour == StackBehaviour.Popi_popi8 || popBehaviour == StackBehaviour.Popi_popr4 || popBehaviour == StackBehaviour.Popi_popr8 ||
popBehaviour == StackBehaviour.Popref_pop1 || popBehaviour == StackBehaviour.Popref_popi)
stackBalance -= 2;
else if (popBehaviour == StackBehaviour.Popi_popi_popi || popBehaviour == StackBehaviour.Popref_popi_popi || popBehaviour == StackBehaviour.Popref_popi_popi8 ||
popBehaviour == StackBehaviour.Popref_popi_popr4 || popBehaviour == StackBehaviour.Popref_popi_popr8 || popBehaviour == StackBehaviour.Popref_popi_popref)
stackBalance -= 3;
// Move to previous instruction
instruction = instruction.Previous;
}
}
// Return that the instruction cannot be optimized
return false;
}
#pragma warning restore RCS1003
/// <summary>
/// Optimizes a given method by removing any instructions
/// that can be optimized out.
/// </summary>
/// <param name="method">
/// The MethodDefinition object to be optimized. This method contains a list
/// of instructions that are to be checked and potentially removed if they can be optimized out.
/// </param>
public static void OptimizeInstructions(this MethodDefinition method)
{
// If the method doesn't have a body (i.e., it's an abstract or external method), then exit
if (!method.HasBody) return;
// Iterate over each instruction in the method body
for (int i = 0; i < method.Body.Instructions.Count - 1; ++i)
{
// If the current instruction can be optimized out according to the CanBeOptimizedOut method, remove it
if (method.Body.Instructions[i].CanBeOptimizedOut(method))
{
// Remove the current instruction from the method body
method.Body.Instructions.RemoveAt(i);
// Decrement the loop index to account for the removal. This ensures that the next iteration doesn't skip any instructions.
i--;
}
}
}
#endregion InstructionOptimizations
public static OpCode LoadRefAsValue(this TypeReference type, out bool isStruct)
{
isStruct = false;
if (!type.IsValueType)
{
return OpCodes.Ldind_Ref;
}
switch (type.MetadataType)
{
case MetadataType.Boolean:
case MetadataType.SByte:
return OpCodes.Ldind_I1;
case MetadataType.Byte:
return OpCodes.Ldind_U1;
case MetadataType.Int16:
return OpCodes.Ldind_I2;
case MetadataType.UInt16:
return OpCodes.Ldind_U2;
case MetadataType.Int32:
case MetadataType.UInt32:
return OpCodes.Ldind_I4;
case MetadataType.Int64:
case MetadataType.UInt64:
return OpCodes.Ldind_I8;
case MetadataType.Single:
return OpCodes.Ldind_R4;
case MetadataType.Double:
return OpCodes.Ldind_R8;
case MetadataType.IntPtr:
case MetadataType.UIntPtr:
return OpCodes.Ldind_I;
default:
isStruct = true;
return OpCodes.Ldobj;
}
}
}