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; /// /// 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. /// public static class MonoCecilExtensions { /// /// Represents an information container for updating Mono.Cecil definitions. /// public class UpdateInfo { /// /// A collection of CustomAttribute objects that have been updated. /// public readonly Collection updatedAttributes = new Collection(); /// /// A collection of InterfaceImplementation objects that have been updated. /// public readonly Collection updatedInterfaces = new Collection(); /// /// A collection of FieldDefinition objects that have been updated. /// public readonly Collection updatedFields = new Collection(); /// /// A collection of PropertyDefinition objects that have been updated. /// public readonly Collection updatedProperties = new Collection(); /// /// A collection of MethodDefinition objects that have been updated. /// public readonly Collection updatedMethods = new Collection(); /// /// A collection of source TypeDefinition objects that are being merged. /// public readonly Collection srcTypes = new Collection(); /// /// A collection of destination TypeDefinition objects where source objects are merged into. /// public readonly Collection destTypes = new Collection(); }; /// /// A dictionary mapping from AssemblyDefinition objects to their corresponding UpdateInfo objects. /// Used to keep track of the updates made to each assembly. /// public static readonly Dictionary assemblyUpdateInfo = new Dictionary (); /// /// Additional search directories for resolving assembly types. /// public static readonly Collection additionalSearchDirectories = new Collection(); // Basic extension methods for loading assemblies, adding elements to collections, and finding types, fields, and methods in Mono.Cecil objects. #region Base /// /// This extension method loads an assembly from a given location. /// /// The location of the assembly to be loaded. /// A boolean value to determine if the assembly is read-only or writable. /// The AssemblyDefinition object of the loaded assembly if successful 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, }); } /// /// This extension method finds a method of a given type in an assembly. /// /// The assembly where the type and method are located. /// The full or simple name of the type. /// The full or simple name of the method. /// The MethodDefinition object of the found method. Null if not found. 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); } /// /// This extension method finds a type in an assembly using its full name or simple name. /// /// The assembly where the type is located. /// The full or simple name of the type. /// The TypeDefinition object of the found type. Null if not found. 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); } /// /// This extension method finds a type in an assembly using its full name or simple name. /// /// The assembly where the type is located. /// The type to locate. /// The TypeDefinition object of the found type. Null if not found. 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); } /// /// This extension method finds a field in a type. /// /// The type where the field is located. /// The full or simple name of the field. /// The FieldDefinition object of the found field. Null if not found. 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); } /// /// This extension method finds a method in a type. /// /// The type where the method is located. /// The full or simple name of the method. /// The MethodDefinition object of the found method. Null if not found. 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); } /// /// This extension method finds all methods in a type that match a given method signature. /// /// The type where the methods are located. /// The full or simple name of the methods. /// A collection of MethodDefinition objects for the found methods. Empty collection if none found. public static Collection FindMethods(this TypeDefinition type, string methodSignature) { var collection = new Collection(); // 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 /// /// 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. /// /// The assembly to which the type will be added. /// The source type that will be added to the assembly. /// Avoid name conflicts by adding a '_' suffix to the copied class name. 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 /// /// 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. /// /// The destination type definition where fields, properties, and methods from source will be added. /// The source type definition whose fields, properties, and methods will be cloned and added to the destination. 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(); 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(); 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(); 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(); 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(); 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(); // 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 /// /// 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. /// /// 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. /// Avoid signature conflicts by changing original method parameters to be base object types for duplicate methods 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 /// /// Clones a CustomAttribute. /// /// The attribute to be cloned. /// A clone of the original attribute. 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; } /// /// Clones a InterfaceImplementation. /// /// The interface to be cloned. /// A clone of the original interface. 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; } /// /// Clones a FieldDefinition. /// /// The field to be cloned. /// A clone of the original field. 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; } /// /// Clones a PropertyDefinition. /// /// The property to be cloned. /// A clone of the original property. 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; } /// /// Clones a ParameterDefinition. /// /// The parameter to be cloned. /// A clone of the original parameter. 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; } /// /// Clones a VariableDefinition. /// /// The variable to be cloned. /// A clone of the original variable. 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); } /// /// Clones an Instruction. /// /// The instruction to be cloned. /// A clone of the original instruction. 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; } /// /// Clones a MethodDefinition. /// /// The method to be cloned. /// A clone of the original method. 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(); // 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(); // 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(); // 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 /// /// Updates the InterfaceType of the given InterfaceImplementation, if it matches the source type, to the destination type. /// /// InterfaceImplementation that may have its InterfaceType updated. /// The source type which could be replaced. /// The destination type which could replace the source type. 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; } /// /// Updates the FieldType of the given FieldDefinition, if it matches the source type, to the destination type. /// /// FieldDefinition that may have its FieldType updated. /// The source type which could be replaced. /// The destination type which could replace the source type. 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; } /// /// 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. /// /// FieldReference that may have its FieldType, and DeclaringType updated. /// The source type which could be replaced. /// The destination type which could replace the source type. /// A FieldReference with updated types, or the original FieldReference if no updates were made. 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; } /// /// Updates the PropertyType of the given PropertyDefinition, if it matches the source type, to the destination type. /// /// PropertyDefinition that may have its PropertyType updated. /// The source type which could be replaced. /// The destination type which could replace the source type. 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; } /// /// Updates the ParameterType of the given ParameterDefinition, if it matches the source type, to the destination type. /// /// ParameterDefinition that may have its ParameterType updated. /// The source type which could be replaced. /// The destination type which could replace the source type. 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; } /// /// Updates the VariableType of the given VariableDefinition, if it matches the source type, to the destination type. /// /// VariableDefinition that may have its VariableType updated. /// The source type which could be replaced. /// The destination type which could replace the source type. 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; } /// /// 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. /// /// MethodDefinition that may have its ReturnType, ParameterTypes, and VariableTypes updated. /// The source type which could be replaced. /// The destination type which could replace the source type. 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); } /// /// 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. /// /// MethodReference that may have its ReturnType, DeclaringType and ParameterTypes updated. /// The source type which could be replaced. /// The destination type which could replace the source type. /// A MethodReference with updated types, or the original MethodReference if no updates were made. 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; } /// /// Updates the ReturnType and Parameters of the CallSite to the destination type, if they match the source type, to the destination type. /// /// CallSite that needs its return type and parameters updated. /// The original type which is being replaced. /// The new type which is replacing the original type. 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 /// /// 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. /// /// Instruction that needs its operand updated. /// The original type which is being replaced. /// The new type which is replacing the original type. 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 } /// /// Updates all instructions in the method's body. /// /// Method whose instructions are to be updated. /// The original type which is being replaced. /// The new type which is replacing the original type. 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 /// /// 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. /// /// PropertyDefinition whose getter and setter need to be updated. /// The original type which is being replaced. /// The new type which is replacing the original type. 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 /// /// Imports the constructor reference for a given attribute into a module. /// /// The custom attribute whose constructor reference needs to be imported. /// The module type into whose module the reference should be imported. public static void ImportReferences(this CustomAttribute attribute, ModuleDefinition module) { // Import the constructor reference into the module attribute.Constructor = module.ImportReference(attribute.Constructor); } /// /// Imports the interface type and custom attributes references of an interface into a module. /// /// The interface whose references need to be imported. /// The module type into whose module the references should be imported. 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); } /// /// Imports the field type and custom attributes references of a field into a module. /// /// The field whose references need to be imported. /// The module type into whose module the references should be imported. 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(); } /// /// Imports the property type and custom attributes references of a property into a module. /// /// The property whose references need to be imported. /// The module type into whose module the references should be imported. 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(); } /// /// Imports the parameter type and custom attributes references of a parameter into a module. /// /// The parameter whose references need to be imported. /// The module type into whose module the references should be imported. 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); } /// /// Imports the variable type references of a variable into a module. /// /// The variable whose type references need to be imported. /// The module type into whose module the references should be imported. public static void ImportReferences(this VariableDefinition variable, ModuleDefinition module) { // Import the variable type reference into the module variable.VariableType = module.ImportReference(variable.VariableType); } /// /// Imports the method type references and the custom attributes of a method into a module. /// /// The method whose references need to be imported. /// The module type into whose module the references should be imported. 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); } } /// /// Imports the return type references of a CallSite into a module. /// /// The CallSite whose return type references need to be imported. /// The module type into whose module the references should be imported. 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); } /// /// Imports the operand type references of an instruction into a module. /// /// The instruction whose operand references need to be imported. /// The module type into whose module the references should be imported. 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 /// /// Swaps the method references within the provided instruction between two given methods. /// /// The instruction to modify. /// The first method to swap. /// The second method to swap. 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; } } /// /// Swaps the method references within the provided collection of instructions between two given methods. /// /// The collection of instructions to modify. /// The first method to swap. /// The second method to swap. public static void SwapMethodReferences(this Collection instructions, MethodDefinition leftMethod, MethodDefinition rightMethod) { // Swap method references for each instruction in the collection foreach (var instruction in instructions) instruction.SwapMethodReferences(leftMethod, rightMethod); } /// /// Swaps the method references within the body of the provided method between two given methods. /// /// The method to modify. /// The first method to swap. /// The second method to swap. 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); } /// /// Swaps the attributes, parameters, custom attributes, and generic parameters between two given methods. /// /// The first method to swap. /// The second method to swap. 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(leftMethod.Parameters); var leftCustomAttributes = new Collection(leftMethod.CustomAttributes); var leftGenericParameters = new Collection(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); } /// /// Finds and swaps methods with the same full name within the given type. /// /// The type to modify. /// Avoid signature conflicts by changing original method parameters to be base object types 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(); // 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 /// /// 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. /// /// The instruction to be checked for optimization. /// The method definition that contains the instruction. /// Returns true if the instruction can be optimized out. Otherwise, returns false. /// /// 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. /// 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 /// /// Optimizes a given method by removing any instructions /// that can be optimized out. /// /// /// 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. /// 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 }