279 lines
12 KiB
C#
279 lines
12 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Linq.Expressions;
|
|
using MonoMod.Utils;
|
|
using System.Collections.Generic;
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
|
|
#if NETSTANDARD
|
|
using TypeOrTypeInfo = System.Reflection.TypeInfo;
|
|
using static System.Reflection.IntrospectionExtensions;
|
|
#else
|
|
using TypeOrTypeInfo = System.Type;
|
|
#endif
|
|
|
|
namespace MonoMod.Utils {
|
|
public static class ReflectionHelper {
|
|
|
|
public static readonly Dictionary<string, Assembly> AssemblyCache = new Dictionary<string, Assembly>();
|
|
public static readonly Dictionary<MemberReference, MemberInfo> ResolveReflectionCache = new Dictionary<MemberReference, MemberInfo>();
|
|
|
|
private static MemberInfo _Cache(MemberReference key, MemberInfo value) {
|
|
if (key != null && value != null) {
|
|
lock (ResolveReflectionCache) {
|
|
ResolveReflectionCache[key] = value;
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public static Type ResolveReflection(this TypeReference mref)
|
|
=> (_ResolveReflection(mref, null) as TypeOrTypeInfo).AsType();
|
|
public static MethodBase ResolveReflection(this MethodReference mref)
|
|
=> _ResolveReflection(mref, null) as MethodBase;
|
|
public static FieldInfo ResolveReflection(this FieldReference mref)
|
|
=> _ResolveReflection(mref, null) as FieldInfo;
|
|
public static PropertyInfo ResolveReflection(this PropertyReference mref)
|
|
=> _ResolveReflection(mref, null) as PropertyInfo;
|
|
public static EventInfo ResolveReflection(this EventReference mref)
|
|
=> _ResolveReflection(mref, null) as EventInfo;
|
|
|
|
public static MemberInfo ResolveReflection(this MemberReference mref)
|
|
=> _ResolveReflection(mref, null);
|
|
|
|
private static MemberInfo _ResolveReflection(MemberReference mref, Module[] modules) {
|
|
if (mref == null)
|
|
return null;
|
|
|
|
lock (ResolveReflectionCache) {
|
|
if (ResolveReflectionCache.TryGetValue(mref, out MemberInfo cached) && cached != null)
|
|
return cached;
|
|
}
|
|
|
|
TypeOrTypeInfo type;
|
|
|
|
// Special cases.
|
|
if (mref is GenericParameter genParam) {
|
|
// TODO: Handle GenericParameter in ResolveReflection.
|
|
throw new NotSupportedException("ResolveReflection on GenericParameter currently not supported");
|
|
}
|
|
|
|
if (mref is MethodReference method && mref.DeclaringType is ArrayType) {
|
|
// ArrayType holds special methods.
|
|
type = _ResolveReflection(mref.DeclaringType, modules) as TypeOrTypeInfo;
|
|
// ... but all of the methods have the same MetadataToken. We couldn't compare it anyway.
|
|
|
|
string methodID = method.GetFindableID(withType: false);
|
|
MethodInfo found = type.AsType().GetMethods().First(m => m.GetFindableID(withType: false) == methodID);
|
|
if (found != null)
|
|
return _Cache(mref, found);
|
|
}
|
|
|
|
TypeReference tscope =
|
|
mref.DeclaringType ??
|
|
mref as TypeReference ??
|
|
throw new ArgumentException("MemberReference hasn't got a DeclaringType / isn't a TypeReference in itself");
|
|
|
|
if (modules == null) {
|
|
string asmName;
|
|
string moduleName;
|
|
|
|
switch (tscope.Scope) {
|
|
case AssemblyNameReference asmNameRef:
|
|
asmName = asmNameRef.FullName;
|
|
moduleName = null;
|
|
break;
|
|
|
|
case ModuleDefinition moduleDef:
|
|
asmName = moduleDef.Assembly.FullName;
|
|
moduleName = moduleDef.Name;
|
|
break;
|
|
|
|
case ModuleReference moduleRef:
|
|
// TODO: Is this correct? It's what cecil itself is doing...
|
|
asmName = tscope.Module.Assembly.FullName;
|
|
moduleName = tscope.Module.Name;
|
|
break;
|
|
|
|
default:
|
|
throw new NotSupportedException($"Unsupported scope type {tscope.Scope.GetType().FullName}");
|
|
}
|
|
|
|
Assembly asm = null;
|
|
lock (AssemblyCache) {
|
|
if (!AssemblyCache.TryGetValue(asmName, out asm)) {
|
|
#if !NETSTANDARD1_X
|
|
asm =
|
|
AppDomain.CurrentDomain.GetAssemblies()
|
|
.FirstOrDefault(other => {
|
|
AssemblyName name = other.GetName();
|
|
return name.Name == asmName || name.FullName == asmName;
|
|
});
|
|
#endif
|
|
if (asm == null)
|
|
asm = Assembly.Load(new AssemblyName(asmName));
|
|
AssemblyCache[asmName] = asm;
|
|
}
|
|
}
|
|
modules = string.IsNullOrEmpty(moduleName) ? asm.GetModules() : new Module[] { asm.GetModule(moduleName) };
|
|
}
|
|
|
|
if (mref is TypeReference tref) {
|
|
if (tref.FullName == "<Module>")
|
|
throw new ArgumentException("Type <Module> cannot be resolved to a runtime reflection type");
|
|
|
|
if (mref is TypeSpecification ts) {
|
|
type = _ResolveReflection(ts.ElementType, null) as TypeOrTypeInfo;
|
|
if (type == null)
|
|
return null;
|
|
|
|
if (ts.IsByReference)
|
|
return ResolveReflectionCache[mref] = type.MakeByRefType().GetTypeInfo();
|
|
|
|
if (ts.IsPointer)
|
|
return ResolveReflectionCache[mref] = type.MakePointerType().GetTypeInfo();
|
|
|
|
if (ts.IsArray)
|
|
return ResolveReflectionCache[mref] = (ts as ArrayType).IsVector ? type.MakeArrayType().GetTypeInfo() : type.MakeArrayType((ts as ArrayType).Dimensions.Count).GetTypeInfo();
|
|
|
|
if (ts.IsGenericInstance)
|
|
return ResolveReflectionCache[mref] = type.MakeGenericType((ts as GenericInstanceType).GenericArguments.Select(arg => (_ResolveReflection(arg, null) as TypeOrTypeInfo).AsType()).ToArray()).GetTypeInfo();
|
|
|
|
} else {
|
|
type = modules
|
|
.Select(module => module.GetType(mref.FullName.Replace("/", "+"), false, false).GetTypeInfo())
|
|
.FirstOrDefault(m => m != null);
|
|
#if !NETSTANDARD1_X
|
|
if (type == null)
|
|
type = modules
|
|
.Select(module => module.GetTypes().FirstOrDefault(m => mref.Is(m)).GetTypeInfo())
|
|
.FirstOrDefault(m => m != null);
|
|
#endif
|
|
}
|
|
|
|
return _Cache(mref, type);
|
|
}
|
|
|
|
bool typeless = mref.DeclaringType.FullName == "<Module>";
|
|
|
|
MemberInfo member;
|
|
|
|
if (mref is GenericInstanceMethod mrefGenMethod) {
|
|
member = _ResolveReflection(mrefGenMethod.ElementMethod, modules);
|
|
member = (member as MethodInfo)?.MakeGenericMethod(mrefGenMethod.GenericArguments.Select(arg => (_ResolveReflection(arg, null) as TypeOrTypeInfo).AsType()).ToArray());
|
|
|
|
} else if (typeless) {
|
|
if (mref is MethodReference)
|
|
member = modules
|
|
.Select(module => module.GetMethods((BindingFlags) (-1)).FirstOrDefault(m => mref.Is(m)))
|
|
.FirstOrDefault(m => m != null);
|
|
else if (mref is FieldReference)
|
|
member = modules
|
|
.Select(module => module.GetFields((BindingFlags) (-1)).FirstOrDefault(m => mref.Is(m)))
|
|
.FirstOrDefault(m => m != null);
|
|
else
|
|
throw new NotSupportedException($"Unsupported <Module> member type {mref.GetType().FullName}");
|
|
|
|
} else {
|
|
member = (_ResolveReflection(mref.DeclaringType, modules) as TypeOrTypeInfo).AsType().GetMembers((BindingFlags) (-1)).FirstOrDefault(m => mref.Is(m));
|
|
}
|
|
|
|
return _Cache(mref, member);
|
|
}
|
|
|
|
public static SignatureHelper ResolveReflection(this CallSite csite, Module context)
|
|
=> ResolveReflectionSignature(csite, context);
|
|
public static SignatureHelper ResolveReflectionSignature(this IMethodSignature csite, Module context) {
|
|
SignatureHelper shelper;
|
|
switch (csite.CallingConvention) {
|
|
#if !NETSTANDARD
|
|
case MethodCallingConvention.C:
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConvention.Cdecl, csite.ReturnType.ResolveReflection());
|
|
break;
|
|
|
|
case MethodCallingConvention.StdCall:
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConvention.StdCall, csite.ReturnType.ResolveReflection());
|
|
break;
|
|
|
|
case MethodCallingConvention.ThisCall:
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConvention.ThisCall, csite.ReturnType.ResolveReflection());
|
|
break;
|
|
|
|
case MethodCallingConvention.FastCall:
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConvention.FastCall, csite.ReturnType.ResolveReflection());
|
|
break;
|
|
|
|
case MethodCallingConvention.VarArg:
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConventions.VarArgs, csite.ReturnType.ResolveReflection());
|
|
break;
|
|
|
|
#else
|
|
case MethodCallingConvention.C:
|
|
case MethodCallingConvention.StdCall:
|
|
case MethodCallingConvention.ThisCall:
|
|
case MethodCallingConvention.FastCall:
|
|
case MethodCallingConvention.VarArg:
|
|
throw new NotSupportedException("Unmanaged calling conventions for callsites not supported");
|
|
|
|
#endif
|
|
|
|
default:
|
|
if (csite.ExplicitThis) {
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConventions.ExplicitThis, csite.ReturnType.ResolveReflection());
|
|
} else {
|
|
shelper = SignatureHelper.GetMethodSigHelper(context, CallingConventions.Standard, csite.ReturnType.ResolveReflection());
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (context != null) {
|
|
List<Type> modReq = new List<Type>();
|
|
List<Type> modOpt = new List<Type>();
|
|
|
|
foreach (ParameterDefinition param in csite.Parameters) {
|
|
if (param.ParameterType.IsSentinel)
|
|
shelper.AddSentinel();
|
|
|
|
if (param.ParameterType.IsPinned) {
|
|
shelper.AddArgument(param.ParameterType.ResolveReflection(), true);
|
|
continue;
|
|
}
|
|
|
|
modOpt.Clear();
|
|
modReq.Clear();
|
|
|
|
for (
|
|
TypeReference paramTypeRef = param.ParameterType;
|
|
paramTypeRef is TypeSpecification paramTypeSpec;
|
|
paramTypeRef = paramTypeSpec.ElementType
|
|
) {
|
|
switch (paramTypeRef) {
|
|
case RequiredModifierType paramTypeModReq:
|
|
modReq.Add(paramTypeModReq.ModifierType.ResolveReflection());
|
|
break;
|
|
|
|
case OptionalModifierType paramTypeOptReq:
|
|
modOpt.Add(paramTypeOptReq.ModifierType.ResolveReflection());
|
|
break;
|
|
}
|
|
}
|
|
|
|
shelper.AddArgument(param.ParameterType.ResolveReflection(), modReq.ToArray(), modOpt.ToArray());
|
|
}
|
|
|
|
} else {
|
|
foreach (ParameterDefinition param in csite.Parameters) {
|
|
shelper.AddArgument(param.ParameterType.ResolveReflection());
|
|
}
|
|
}
|
|
|
|
return shelper;
|
|
}
|
|
|
|
}
|
|
}
|