Files
Fishery/Source/FisheryLib/Cecil/CecilExtensions.cs
T

581 lines
23 KiB
C#

// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Mono.Collections.Generic;
using Verse;
using CallSite = Mono.Cecil.CallSite;
namespace FisheryLib.Cecil;
public static class CecilExtensions
{
public static MethodReference ImportReference(this ModuleDefinition module, Delegate method)
=> module.ImportReference(method.Method);
public static MethodReference MakeGenericMethod<T>(this MethodReference genericMethodDefinition,
ICollection<T> genericArguments) where T : TypeReference
{
if (genericArguments.Count != genericMethodDefinition.GenericParameters.Count)
ThrowForInvalidGenericArgumentCount(genericMethodDefinition, genericArguments);
var genericInstanceMethod = new GenericInstanceMethod(genericMethodDefinition);
foreach (var genericArgument in genericArguments)
genericInstanceMethod.GenericArguments.Add(genericArgument);
return genericInstanceMethod;
}
private static void ThrowForInvalidGenericArgumentCount<T>(IGenericParameterProvider genericParameterProvider,
ICollection<T> genericArguments) where T : TypeReference
=> throw new ArgumentException(
$"Invalid generic argument count for {genericParameterProvider}. Received {
genericArguments.Count}, must be {genericParameterProvider.GenericParameters.Count}");
public static MethodReference MakeGenericMethod<T>(this MethodReference genericMethodDefinition,
params T[] genericArguments) where T : TypeReference
=> MakeGenericMethod(genericMethodDefinition, (ICollection<T>)genericArguments);
public static MethodReference ImportMethodWithGenericArguments<T>(this ModuleDefinition module, MethodInfo method,
ICollection<T> genericArguments) where T : TypeReference
=> module.ImportReference(method.IsGenericMethodDefinition ? method : method.GetGenericMethodDefinition())
.MakeGenericMethod(genericArguments);
public static MethodReference ImportMethodWithGenericArguments<T>(this ModuleDefinition module, MethodInfo method,
params T[] genericArguments) where T : TypeReference
=> module.ImportMethodWithGenericArguments(method, (ICollection<T>)genericArguments);
public static MethodReference ImportMethodWithGenericArguments<T>(this ModuleDefinition module, Delegate method,
ICollection<T> genericArguments) where T : TypeReference
=> module.ImportMethodWithGenericArguments(method.Method, genericArguments);
public static MethodReference ImportMethodWithGenericArguments<T>(this ModuleDefinition module, Delegate method,
params T[] genericArguments) where T : TypeReference
=> module.ImportMethodWithGenericArguments(method.Method, (ICollection<T>)genericArguments);
public static TypeReference MakeGenericType<T>(this TypeReference genericTypeDefinition,
ICollection<T> genericArguments) where T : TypeReference
{
if (genericArguments.Count != genericTypeDefinition.GenericParameters.Count)
ThrowForInvalidGenericArgumentCount(genericTypeDefinition, genericArguments);
var genericInstanceType = new GenericInstanceType(genericTypeDefinition);
foreach (var genericArgument in genericArguments)
genericInstanceType.GenericArguments.Add(genericArgument);
return genericInstanceType;
}
public static TypeReference MakeGenericType<T>(this TypeReference genericTypeDefinition,
params T[] genericArguments) where T : TypeReference
=> MakeGenericType(genericTypeDefinition, (ICollection<T>)genericArguments);
public static TypeReference ImportTypeWithGenericArguments<T>(this ModuleDefinition module, Type type,
ICollection<T> genericArguments) where T : TypeReference
=> module.ImportReference(type.IsGenericTypeDefinition ? type : type.GetElementType())
.MakeGenericType(genericArguments);
public static TypeReference ImportTypeWithGenericArguments<T>(this ModuleDefinition module, Type type,
params T[] genericArguments) where T : TypeReference
=> module.ImportTypeWithGenericArguments(type, (ICollection<T>)genericArguments);
public static VariableDefinition DeclareLocal(this ILProcessor ilProcessor, Type localType)
{
var variableDefinition = new VariableDefinition(ilProcessor.GetModule().ImportReference(localType));
ilProcessor.Body.Variables.Add(variableDefinition);
return variableDefinition;
}
public static VariableDefinition DeclareLocal(this ILProcessor ilProcessor, TypeReference localType)
{
var variableDefinition = new VariableDefinition(localType);
ilProcessor.Body.Variables.Add(variableDefinition);
return variableDefinition;
}
public static void InsertAt(this ILProcessor ilProcessor, int index, Instruction instruction)
=> ilProcessor.instructions.Insert(index, instruction);
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, TypeReference type)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, type));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Type type)
=> ilProcessor.InsertAt(index, opCode, ilProcessor.GetModule().ImportReference(type));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, CallSite site)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, site));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, MethodReference method)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, method));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, MethodBase method,
params Type[] genericArguments)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode,
ImportReferenceWithOptionalGenericArguments(method, ilProcessor.GetModule(), genericArguments)));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Delegate method,
params Type[] genericArguments)
=> ilProcessor.InsertAt(index, opCode, method.Method, genericArguments);
private static MethodReference ImportReferenceWithOptionalGenericArguments(MethodBase method,
ModuleDefinition module, params Type[] genericArguments)
=> genericArguments.Length > 0
? module.ImportMethodWithGenericArguments((MethodInfo)method, genericArguments)
: module.ImportReference(method);
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, FieldReference field)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, field));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, FieldInfo field)
=> ilProcessor.InsertAt(index, opCode, ilProcessor.GetModule().ImportReference(field));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, string value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, sbyte value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, byte value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, int value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, long value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, nint value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, float value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, double value)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Instruction target)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, target));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Instruction[] targets)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, targets));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, VariableDefinition variable)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, variable));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, ParameterDefinition parameter)
=> ilProcessor.InsertAt(index, Instruction.Create(opCode, parameter));
public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, object operand)
=> ilProcessor.InsertAt(index, CreateInstruction(opCode, operand));
public static void InsertAt(this ILProcessor ilProcessor, int index, object instruction)
{
var module = ilProcessor.GetModule();
ilProcessor.InsertAt(index, instruction switch
{
Instruction codeInstruction => codeInstruction,
OpCode opCode => Instruction.Create(opCode),
ITuple tuple => CreateInstruction(tuple is [OpCode opCode, _, ..]
? opCode
: ThrowArgumentExceptionForInvalidType<OpCode>(instruction),
tuple[1] switch
{
MethodBase => ImportMethodFromTuple(tuple, module),
FieldInfo fieldInfo => module.ImportReference(fieldInfo),
Type => ImportTypeFromTuple(tuple, module),
ParameterInfo parameterInfo => GetParameterDefinition(ilProcessor, parameterInfo),
Delegate @delegate => module.ImportReference(@delegate.Method),
_ => tuple[1]
}),
_ => ThrowArgumentExceptionForInvalidType<Instruction>(instruction)
});
}
private static ParameterDefinition GetParameterDefinition(ILProcessor ilProcessor, ParameterInfo parameterInfo)
{
var parameters = ilProcessor.Body.Method.Parameters;
foreach (var parameter in parameters)
{
if (parameter.Name == parameterInfo.Name)
return parameter;
}
return ThrowHelper.ThrowArgumentException<ParameterDefinition>($"Could not find parameter named {
parameterInfo.Name} in method {ilProcessor.Body.Method.FullName}");
}
private static MethodReference ImportMethodFromTuple(ITuple tuple, ModuleDefinition module)
{
var tupleLength = tuple.Length;
Guard.IsGreaterThanOrEqualTo(tupleLength, 2);
if (tupleLength == 2)
return module.ImportReference((MethodBase)tuple[1]);
var methodInfo = (MethodInfo)tuple[1];
switch (tuple[2])
{
case Type[] typeArray:
{
return module.ImportMethodWithGenericArguments(methodInfo, typeArray);
}
case Type:
{
var genericArguments = new Type[tupleLength - 2];
for (var i = 2; i < tupleLength; i++)
genericArguments[i - 2] = (Type)tuple[i];
return module.ImportMethodWithGenericArguments(methodInfo, genericArguments);
}
case Collection<GenericParameter> genericParameterCollection:
{
return module.ImportMethodWithGenericArguments(methodInfo, genericParameterCollection);
}
case Collection<TypeReference> typeReferenceCollection:
{
return module.ImportMethodWithGenericArguments(methodInfo, typeReferenceCollection);
}
case TypeReference:
{
var genericArguments = new TypeReference[tupleLength - 2];
for (var i = 2; i < tupleLength; i++)
genericArguments[i - 2] = (TypeReference)tuple[i];
return module.ImportMethodWithGenericArguments(methodInfo, genericArguments);
}
default:
{
return ThrowHelper.ThrowArgumentException<MethodReference>(
$"Tuple contains invalid type: {tuple[2].GetType().FullName}");
}
}
}
private static TypeReference ImportTypeFromTuple(ITuple tuple, ModuleDefinition module)
{
var tupleLength = tuple.Length;
Guard.IsGreaterThanOrEqualTo(tupleLength, 2);
if (tupleLength == 2)
return module.ImportReference((Type)tuple[1]);
var type = (Type)tuple[1];
switch (tuple[2])
{
case Type[] typeArray:
{
return module.ImportTypeWithGenericArguments(type, typeArray);
}
case Type:
{
var genericArguments = new Type[tupleLength - 2];
for (var i = 2; i < tupleLength; i++)
genericArguments[i - 2] = (Type)tuple[i];
return module.ImportTypeWithGenericArguments(type, genericArguments);
}
case Collection<GenericParameter> genericParameterCollection:
{
return module.ImportTypeWithGenericArguments(type, genericParameterCollection);
}
case Collection<TypeReference> typeReferenceCollection:
{
return module.ImportTypeWithGenericArguments(type, typeReferenceCollection);
}
case TypeReference:
{
var genericArguments = new TypeReference[tupleLength - 2];
for (var i = 2; i < tupleLength; i++)
genericArguments[i - 2] = (TypeReference)tuple[i];
return module.ImportTypeWithGenericArguments(type, genericArguments);
}
default:
{
return ThrowHelper.ThrowArgumentException<TypeReference>(
$"Tuple contains invalid type: {tuple[2].GetType().FullName}");
}
}
}
public static void InsertRange(this ILProcessor ilProcessor, int index, IEnumerable<Instruction> instructions)
{
foreach (var instruction in instructions)
ilProcessor.InsertAt(index++, instruction);
}
public static void InsertRange(this ILProcessor ilProcessor, int index, params Instruction[] instructions)
{
for (var i = 0; i < instructions.Length; i++)
ilProcessor.InsertAt(index++, instructions[i]);
}
public static void InsertRange(this ILProcessor ilProcessor, int index, IEnumerable<object> instructions)
{
foreach (var instruction in instructions)
ilProcessor.InsertAt(index++, instruction);
}
public static void InsertRange(this ILProcessor ilProcessor, int index, params object[] instructions)
{
for (var i = 0; i < instructions.Length; i++)
ilProcessor.InsertAt(index++, instructions[i]);
}
public static void ReplaceBodyWith(this ILProcessor ilProcessor, Delegate replacement)
=> ilProcessor.ReplaceBodyWith(replacement.Method);
public static void ReplaceBodyWith(this ILProcessor ilProcessor, MethodInfo replacement)
{
var targetBody = ilProcessor.Body;
var module = targetBody.Method.Module;
if (replacement.IsGenericMethod)
replacement = replacement.GetGenericMethodDefinition();
if (replacement.DeclaringType!.IsGenericType)
{
replacement = AccessTools.DeclaredMethod(replacement.DeclaringType.GetGenericTypeDefinition(),
replacement.Name, replacement.GetParameters().GetTypes());
}
var newMethodReference = module.ImportReference(replacement);
var newBody = newMethodReference.Resolve().Body;
newBody.SimplifyMacros();
MethodBodyRocks.ComputeOffsets(newBody);
var targetInstructions = targetBody.instructions;
targetInstructions.Clear();
foreach (var instruction in newBody.instructions)
{
targetInstructions.Add(new(instruction.OpCode, instruction.Operand switch
{
FieldReference field => module.ImportReference(field, newMethodReference),
MethodReference method => module.ImportReference(method, newMethodReference),
TypeReference type => module.ImportReference(type, newMethodReference),
ParameterReference parameter => targetBody.GetParameter(parameter.Index),
_ => instruction.Operand
}));
}
MethodBodyRocks.ComputeOffsets(targetBody);
foreach (var instruction in targetInstructions)
{
if (instruction.Operand is Instruction operand)
instruction.Operand = targetInstructions.First(ti => ti.Offset == operand.Offset);
}
targetBody.Variables.Clear();
foreach (var variable in newBody.Variables)
targetBody.Variables.Add(new(module.ImportReference(variable.VariableType, newMethodReference)));
targetBody.MaxStackSize = newBody.MaxStackSize;
targetBody.ExceptionHandlers.Clear();
foreach (var exceptionHandler in newBody.ExceptionHandlers)
{
var handler = new ExceptionHandler(exceptionHandler.HandlerType)
{
TryStart = exceptionHandler.TryStart,
TryEnd = exceptionHandler.TryEnd,
FilterStart = exceptionHandler.FilterStart,
HandlerStart = exceptionHandler.HandlerStart,
HandlerEnd = exceptionHandler.HandlerEnd
};
if (exceptionHandler.CatchType is { } catchType)
handler.CatchType = module.ImportReference(catchType, newMethodReference);
targetBody.ExceptionHandlers.Add(handler);
}
targetBody.Method.ImplAttributes = newBody.Method.ImplAttributes;
targetBody.OptimizeMacros();
newBody.OptimizeMacros();
}
public static MethodDefinition GetMethod(this ILProcessor ilProcessor) => ilProcessor.Body.Method;
public static MethodDefinition? GetMethod(this TypeDefinition type, string name, Type?[]? parameters = null)
{
var methods = type.Methods;
MethodDefinition? foundMethod = null;
for (var i = 0; i < methods.Count; i++)
{
var method = methods[i];
if (method.Name != name
|| !TestForMatchingTypes(parameters, method.Parameters, static p => p.ParameterType.Name))
{
continue;
}
if (foundMethod != null)
ThrowForAmbiguousMatch(type, name, parameters);
else
foundMethod = method;
}
return foundMethod;
}
public static TypeDefinition? GetTypeDefinition(this ModuleDefinition module, Type type)
=> type.DeclaringType is { } declaringType
? module.GetTypeDefinition(declaringType)?.NestedTypes.GetNamed(type.Name)
: module.GetType(type.Namespace, type.Name);
private static void ThrowForAmbiguousMatch(TypeDefinition type, string name, Type?[]? parameters)
=> throw new AmbiguousMatchException($"Ambiguous match found for type: {type.FullName}, name: {
name}, parameters: {parameters.ToStringSafeEnumerable()}");
private static bool TestForMatchingTypes<T>(Type?[]? types, Collection<T>? testedTypes,
Func<T, string> typeNameGetter)
{
if (types is null)
return true;
if (testedTypes is null || testedTypes.Count != types.Length)
return false;
var typesNotMatching = false;
for (var i = 0; i < types.Length; i++)
{
var type = types[i];
if (type is null || type.IsGenericParameter)
continue;
if (typeNameGetter(testedTypes[i]) != type.Name)
typesNotMatching = true;
}
return !typesNotMatching;
}
public static ModuleDefinition GetModule(this ILProcessor ilProcessor) => ilProcessor.GetMethod().Module;
public static Collection<TypeReference>? TryGetGenericArguments(this MethodReference method)
=> method is GenericInstanceMethod instanceMethod
? instanceMethod.GenericArguments
: method.DeclaringType.TryGetGenericArguments();
public static Collection<TypeReference>? TryGetGenericArguments(this TypeReference type)
{
while (type != null)
{
if (type is GenericInstanceType instanceType)
return instanceType.GenericArguments;
type = type.DeclaringType;
}
return null;
}
public static Collection<TypeReference>? TryGetGenericArguments(this TypeReference type,
IGenericParameterProvider provider)
{
while (type != null)
{
if (type is GenericInstanceType instanceType)
return instanceType.GenericArguments.ConvertAll(g => g.TryGetGenericParameterType(provider) ?? g);
type = type.DeclaringType;
}
return null;
}
public static Collection<TypeReference>? TryGetGenericArguments(this IGenericParameterProvider provider)
=> provider switch
{
null => ThrowHelper.ThrowArgumentNullException<Collection<TypeReference>>(nameof(provider)),
MethodReference method => method.TryGetGenericArguments(),
TypeReference type => type.TryGetGenericArguments(),
_ => ThrowHelper.ThrowArgumentException<Collection<TypeReference>>(
$"Generic parameter provider has unsupported type: {provider.GetType()}")
};
public static TypeReference? TryGetGenericParameterType(this TypeReference typeReference,
IGenericParameterProvider provider)
=> typeReference is GenericParameter genericParameter
? TryGetUnderlyingType(genericParameter, provider)
: null;
public static TypeReference? TryGetUnderlyingType(GenericParameter genericParameter,
IGenericParameterProvider provider)
=> (genericParameter.Owner is TypeReference ownerType && provider is MemberReference providerMember
? providerMember.TryGetParentOfName(ownerType.Name)
: provider)?.TryGetGenericArguments()?[genericParameter.Position];
public static Collection<TOutput> ConvertAll<TInput, TOutput>(this Collection<TInput> collection,
Converter<TInput, TOutput> converter)
{
var result = new Collection<TOutput>(collection.Count);
for (var i = 0; i < collection.Count; i++)
result.Add(converter(collection[i])); // somehow throws OutOfRangeExceptions when using set_Item here
// Storing collection.Count in a var makes no difference
return result;
}
private static TypeReference? TryGetParentOfName(this MemberReference providerMember, string name)
{
var type = providerMember as TypeReference ?? providerMember.DeclaringType;
while (type != null)
{
if (type.Name == name)
return type;
type = type.DeclaringType;
}
return null;
}
private static T ThrowArgumentExceptionForInvalidType<T>(object instruction)
=> ThrowHelper.ThrowArgumentException<T>(nameof(instruction),
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
$"Argument is of invalid type: {instruction?.GetType().FullName ?? "NULL"}");
[SuppressMessage("Maintainability", "CA1508")]
private static Instruction CreateInstruction(OpCode opCode, object operand)
=> operand switch
{
TypeReference type => Instruction.Create(opCode, type),
CallSite site => Instruction.Create(opCode, site),
MethodReference method => Instruction.Create(opCode, method),
FieldReference field => Instruction.Create(opCode, field),
string value => Instruction.Create(opCode, value),
sbyte value => Instruction.Create(opCode, value),
byte value => Instruction.Create(opCode, value),
int value => Instruction.Create(opCode, value),
long value => Instruction.Create(opCode, value),
float value => Instruction.Create(opCode, value),
double value => Instruction.Create(opCode, value),
nint value => Instruction.Create(opCode, value),
Instruction target => Instruction.Create(opCode, target),
Instruction[] targets => Instruction.Create(opCode, targets),
VariableDefinition variable => Instruction.Create(opCode, variable),
ParameterDefinition parameter => Instruction.Create(opCode, parameter),
_ => ThrowHelper.ThrowArgumentException<Instruction>(nameof(operand),
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
$"Operand for OpCode {opCode} is of invalid type: {
operand?.GetType().FullName ?? operand?.GetType().Name ?? "NULL"}")
};
}