// Copyright (c) 2022 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; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading.Tasks; using FisheryLib.Pools; using Verse; namespace FisheryLib; [PublicAPI] public static class Reflection { private static Assembly[] _allAssemblies = Array.Empty(); private static object _allAssembliesLock = new(); public static CodeInstructions GetCodeInstructions(Delegate method, ILGenerator? generator = null) => GetCodeInstructions(method.Method, generator); public static CodeInstructions GetCodeInstructions(Expression method, ILGenerator? generator = null) => GetCodeInstructions(SymbolExtensions.GetMethodInfo(method), generator); public static CodeInstructions GetCodeInstructions(MethodInfo method, ILGenerator? generator = null) => PatchProcessor.GetCurrentInstructions(method, generator: generator); public static CodeInstructions MakeReplacementCall(Delegate method) => MakeReplacementCall(method.Method); public static CodeInstructions MakeReplacementCall(MethodInfo method) { if (!method.IsStatic) yield return FishTranspiler.This; foreach (var argument in method.GetParameters()) yield return FishTranspiler.Argument(argument); yield return FishTranspiler.Call(method); yield return FishTranspiler.Return; } public static Assembly[] AllAssemblies { get { lock (_allAssembliesLock) { return _allAssemblies.Length == AppDomain.CurrentDomain.GetAssemblies().Length ? _allAssemblies : _allAssemblies = AccessTools.AllAssemblies().ToArray(); } } } public static Type? Type(string assembly, string typeFullName) => System.Type.GetType(string.Concat(typeFullName, ", ", assembly)); public static Type? Type(string assembly, string @namespace, string type) => System.Type.GetType(StringHelper.Resolve($"{@namespace}.{type}, {assembly}")); public static Type? Type(string fullName) { var assemblies = AllAssemblies; for (var i = assemblies.Length; i-- > 0;) { if (assemblies[i].GetType(fullName) is { } type) return type; } return null; } public static MethodInfo MethodInfo(T method) where T : Delegate => method.Method; public static MethodInfo? MethodInfo(string assembly, string type, string name, Type[]? parameters = null, Type[]? generics = null) => AccessTools.Method(Type(assembly, type), name, parameters, generics); public static IEnumerable MethodsOfName(Type type, string name) => MethodsOfNameInternal(type, name, true); private static void ThrowForMethodsOfName(Type? type, string name) => ThrowHelper.ThrowArgumentException($"No methods found for {type.FullDescription()}:{name}"); public static IEnumerable MethodsOfNameSilentFail(Type? type, string name) => MethodsOfNameInternal(type, name, false); private static IEnumerable MethodsOfNameInternal(Type? type, string name, bool throwWhenEmpty) { var methods = type?.GetMethods(AccessTools.allDeclared); if (methods is null) { if (throwWhenEmpty) ThrowForMethodsOfName(type, name); yield break; } var count = 0; for (var i = 0; i < methods.Length; i++) { if (methods[i].Name != name) continue; count++; yield return methods[i]; } if (throwWhenEmpty && count < 1) ThrowForMethodsOfName(type, name); } public static FieldInfo? FieldInfo(string assembly, string type, string name) => AccessTools.Field(Type(assembly, type), name); public static MethodInfo? GetGenericMethod(this Type type, string name, params Type[] typeArguments) => type.GetMethod(name)?.MakeGenericMethod(typeArguments); public static MethodInfo? GetGenericMethod(this Type type, string name, BindingFlags bindingAttr, params Type[] typeArguments) => type.GetMethod(name, bindingAttr)?.MakeGenericMethod(typeArguments); public static bool IsAssignableTo(this Type type, [NotNullWhen(true)] Type? targetType, params Type[] generics) { Guard.IsNotNull(type); return targetType != null && (generics is { Length: >= 1 } ? targetType.MakeGenericType(generics) : targetType) .IsAssignableFrom(type); } public static bool AreAssignableFrom(this IEnumerable types, IEnumerable targetTypes) => targetTypes.AreAssignableTo(types); public static bool AreAssignableTo(this IEnumerable types, IEnumerable targetTypes) { Guard.IsNotNull(types); Guard.IsNotNull(targetTypes); using var typesEnumerator = types.GetEnumerator(); using var targetTypesEnumerator = targetTypes.GetEnumerator(); while (typesEnumerator.MoveNext()) { if (!targetTypesEnumerator.MoveNext() || !targetTypesEnumerator.Current!.IsAssignableFrom(typesEnumerator.Current)) return false; } return !targetTypesEnumerator.MoveNext(); } public static bool IsNullable(this Type type) { Guard.IsNotNull(type); return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } public static bool IsNullable(this Type nullableType, [NotNullWhen(true)] out Type? valueType) { if (nullableType.IsNullable()) { valueType = nullableType.GetGenericArguments()[0]; return true; } else { valueType = null; return false; } } public static Type GetDelegateReturnType(this Type type) { Guard.IsTypeAssignableToType(type, typeof(Delegate)); return type.GetMethod("Invoke")!.ReturnType; } public static ParameterInfo[] GetDelegateParameters(this Type type) { Guard.IsTypeAssignableToType(type, typeof(Delegate)); return type.GetMethod("Invoke")!.GetParameters(); } public static bool HasGenericInterfaceDefinition(this Type type, Type interfaceType) { Guard.IsNotNull(type); return Array.Exists(type.GetInterfaces(), @interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == interfaceType); } [SecuritySafeCritical] public static IntPtr GetFunctionPointer(this MethodBase method) => method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); [SecuritySafeCritical] public static unsafe delegate* GetFunctionPointer(this MethodBase method) => (delegate*)method.MethodHandle.GetFunctionPointer(); /// /// Finds a constructor with parameters that are either assignable to or assignable from the types in the provided /// parameters array /// /// The type of the object to construct /// /// An array of parameter types that are either assignable to or from the parameters of a constructor for the /// target type /// /// Search for static constructors instead of instance constructors /// Throw an exception on failure to find a matching constructor /// /// A ConstructorInfo that best matches the provided arguments. Null on failure if throwOnFailure is set to false /// public static ConstructorInfo? MatchingConstructor(Type type, Type[]? parameters = null, bool searchForStatic = false, bool throwOnFailure = true) { Guard.IsNotNull(type); parameters ??= Array.Empty(); var flags = searchForStatic ? AccessTools.all & ~BindingFlags.Instance : AccessTools.all & ~BindingFlags.Static; return TryGetConstructor(type, flags, paramTypes => paramTypes.AreAssignableFrom(parameters)) ?? TryGetConstructor(type, flags, paramTypes => paramTypes.AreAssignableTo(parameters)) ?? (throwOnFailure ? ThrowHelper.ThrowInvalidOperationException( $"No constructor found for type: {type}, parameters: { parameters.ToStringSafeEnumerable()}, static: {searchForStatic}, found constructors: { type.GetConstructors(flags).ToStringSafeEnumerable()}") : null); } private static ConstructorInfo? TryGetConstructor(Type type, BindingFlags flags, Predicate> predicate) => type.GetConstructors(flags).FirstOrDefault(c => predicate(c.GetParameters().Select(static p => p.ParameterType))); public static T? CreateConstructorDelegate(Type type, params Type[] parameters) where T : Delegate { try { Guard.IsTypeAssignableToType(typeof(T), typeof(Delegate)); var delegateReturnType = typeof(T).GetDelegateReturnType(); var dm = new DynamicMethod($"FisheryConstructorFunc_{type.Name}{parameters.Length.ToString()}", delegateReturnType, parameters, type, true); var il = dm.GetILGenerator(); var needsTempVar = type.IsValueType && parameters.Length == 0 && !Array.Exists(type.GetConstructors(AccessTools.allDeclared & ~BindingFlags.Static), static c => c.GetParameters().Length == 0); var tempVariable = needsTempVar ? FishTranspiler.NewLocalVariable(type, il) : default; if (needsTempVar) il.Emit(tempVariable.Load().Address()); for (var i = 0; i < parameters.Length; i++) il.Emit(FishTranspiler.Argument(i)); il.Emit(FishTranspiler.New(type, parameters)); if (needsTempVar) il.Emit(tempVariable.Load()); if (delegateReturnType != type) { if (type.IsValueType && (delegateReturnType == typeof(object) || (delegateReturnType.IsInterface && type.IsAssignableTo(delegateReturnType)))) { il.Emit(FishTranspiler.Box(type)); } else if (delegateReturnType != typeof(IConvertible) && delegateReturnType.IsAssignableTo(typeof(IConvertible)) && type.IsAssignableTo(typeof(IConvertible))) { il.Emit(FishTranspiler.Call(Array.Find(typeof(Convert).GetMethods(), static m => m.GetParameters().Length == 1 && m.Name == "Type") .MakeGenericMethod(type, delegateReturnType))); } else if (!type.IsAssignableTo(delegateReturnType)) { il.Emit(FishTranspiler.Cast(delegateReturnType)); } } il.Emit(FishTranspiler.Return); return (T)dm.CreateDelegate(typeof(T)); } catch (Exception e) { Log.Error($"Failed creating a constructor delegate for {type.FullDescription()} with parameters { parameters.ToStringSafeEnumerable()}\n{e}\n{new StackTrace()}"); return null; } } [SecuritySafeCritical] public static string FullDescription(this MemberInfo? memberInfo) => memberInfo switch { MethodBase methodBase => GeneralExtensions.FullDescription(methodBase), Type type => GeneralExtensions.FullDescription(type), _ => FullDescriptionInternal(memberInfo) }; private static string FullDescriptionInternal(MemberInfo? memberInfo) { if (memberInfo is null) return "NULL"; using var pooledStringBuilder = new PooledStringBuilder(); var stringBuilder = pooledStringBuilder.Builder; switch (memberInfo) { case FieldInfo fieldInfo: AppendFieldPrefix(fieldInfo, stringBuilder); break; case PropertyInfo propertyInfo: AppendPropertyPrefix(propertyInfo, stringBuilder); break; case EventInfo eventInfo: AppendEventPrefix(eventInfo, stringBuilder); break; } if (memberInfo.DeclaringType != null) stringBuilder.AppendType(memberInfo.DeclaringType).Append("::"); stringBuilder.Append(memberInfo.Name); if (memberInfo is PropertyInfo propertyInfo1) AppendPropertyPostfix(stringBuilder, propertyInfo1); return pooledStringBuilder.ToString(); } private static void AppendFieldPrefix(FieldInfo fieldInfo, StringBuilder stringBuilder) { if (fieldInfo.IsPublic) stringBuilder.Append("public "); if (fieldInfo is { IsLiteral: true, IsInitOnly: false }) stringBuilder.Append("const "); else if (fieldInfo.IsStatic) stringBuilder.Append("static "); stringBuilder.AppendType(fieldInfo.FieldType).Append(' '); } private static void AppendPropertyPrefix(PropertyInfo propertyInfo, StringBuilder stringBuilder) { if ((propertyInfo.GetMethod?.IsPublic ?? false) || (propertyInfo.SetMethod?.IsPublic ?? false)) { stringBuilder.Append("public "); } if ((propertyInfo.GetMethod?.IsStatic ?? true) && (propertyInfo.SetMethod?.IsStatic ?? true)) { stringBuilder.Append("static "); } stringBuilder.AppendType(propertyInfo.PropertyType).Append(' '); } private static void AppendPropertyPostfix(StringBuilder stringBuilder, PropertyInfo propertyInfo) { stringBuilder.Append(" { "); var getMethod = propertyInfo.GetMethod; var setMethod = propertyInfo.SetMethod; if (getMethod is not null) { if (getMethod.IsPrivate && !(setMethod?.IsPrivate ?? true)) stringBuilder.Append("private "); stringBuilder.Append("get; "); } if (setMethod is not null) { if (setMethod.IsPrivate && !(getMethod?.IsPrivate ?? true)) stringBuilder.Append("private "); stringBuilder.Append("set; "); } stringBuilder.Append('}'); } private static void AppendEventPrefix(EventInfo eventInfo, StringBuilder stringBuilder) { if ((eventInfo.AddMethod?.IsPublic ?? true) && (eventInfo.RemoveMethod?.IsPublic ?? true)) { stringBuilder.Append("public "); } if ((eventInfo.AddMethod?.IsStatic ?? true) && (eventInfo.RemoveMethod?.IsStatic ?? true)) { stringBuilder.Append("static "); } stringBuilder.Append("event "); stringBuilder.AppendType(eventInfo.EventHandlerType).Append(' '); } private static StringBuilder AppendType(this StringBuilder stringBuilder, Type type) => stringBuilder.Append(type.FullDescription()); private static T CreateInstanceUsingActivator(params object?[] args) => (T)Activator.CreateInstance(typeof(T), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, args, null, null); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T New() => Create.@new(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T_result New(T_argument argument) => Create.@new(argument); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T_result New(T1 arg1, T2 arg2) => Create.@new(arg1, arg2); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T_result New(T1 arg1, T2 arg2, T3 arg3) => Create.@new(arg1, arg2, arg3); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T_result New(T1 arg1, T2 arg2, T3 arg3, T4 arg4) => Create.@new(arg1, arg2, arg3, arg4); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T_result New(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) => Create.@new(arg1, arg2, arg3, arg4, arg5); private static class Create { internal static volatile Func @new = SimpleFunc; static Create() => Task.Run(ConstructorTask); private static T SimpleFunc() => (T)Activator.CreateInstance(typeof(T), true); private static void ConstructorTask() { if (CreateConstructorDelegate>(typeof(T), Array.Empty()) is { } newFunc) @new = newFunc; } } private static class Create { internal static volatile Func @new = SimpleFunc; static Create() => Task.Run(ConstructorTask); private static TResult SimpleFunc(TArgument argument) => CreateInstanceUsingActivator(argument); private static void ConstructorTask() { if (CreateConstructorDelegate>(typeof(TResult), typeof(TArgument)) is { } newFunc) @new = newFunc; } } private static class Create { internal static volatile Func @new = SimpleFunc; static Create() => Task.Run(ConstructorTask); private static TResult SimpleFunc(T1 first, T2 second) => CreateInstanceUsingActivator(first, second); private static void ConstructorTask() { if (CreateConstructorDelegate>(typeof(TResult), typeof(T1), typeof(T2)) is { } newFunc) @new = newFunc; } } private static class Create { internal static volatile Func @new = SimpleFunc; static Create() => Task.Run(ConstructorTask); private static TResult SimpleFunc(T1 first, T2 second, T3 third) => CreateInstanceUsingActivator(first, second, third); private static void ConstructorTask() { if (CreateConstructorDelegate>(typeof(TResult), typeof(T1), typeof(T2), typeof(T3)) is { } newFunc) { @new = newFunc; } } } private static class Create { internal static volatile Func @new = SimpleFunc; static Create() => Task.Run(ConstructorTask); private static TResult SimpleFunc(T1 first, T2 second, T3 third, T4 fourth) => CreateInstanceUsingActivator(first, second, third, fourth); private static void ConstructorTask() { if (CreateConstructorDelegate>(typeof(TResult), typeof(T1), typeof(T2), typeof(T3), typeof(T4)) is { } newFunc) { @new = newFunc; } } } private static class Create { internal static volatile Func @new = SimpleFunc; static Create() => Task.Run(ConstructorTask); private static TResult SimpleFunc(T1 first, T2 second, T3 third, T4 fourth, T5 fifth) => CreateInstanceUsingActivator(first, second, third, fourth, fifth); private static void ConstructorTask() { if (CreateConstructorDelegate>(typeof(TResult), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)) is { } newFunc) { @new = newFunc; } } } }