diff --git a/IPA.Loader/Utilities/Accessor.cs b/IPA.Loader/Utilities/Accessor.cs index 6e79bf36..a612e097 100644 --- a/IPA.Loader/Utilities/Accessor.cs +++ b/IPA.Loader/Utilities/Accessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Reflection.Emit; @@ -28,6 +29,8 @@ namespace IPA.Utilities var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (field == null) throw new MissingFieldException(typeof(T).Name, fieldName); + if (field.FieldType != typeof(U)) + throw new ArgumentException($"Field '{fieldName}' not of type {typeof(U)}"); var dynMethodName = $"<>_accessor__{fieldName}"; // unfortunately DynamicMethod doesn't like having a ByRef return type, so reflection it @@ -149,6 +152,8 @@ namespace IPA.Utilities var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (prop == null) throw new MissingMemberException(typeof(T).Name, propName); + if (prop.PropertyType != typeof(U)) + throw new ArgumentException($"Property '{propName}' on {typeof(T)} is not of type {typeof(U)}"); var getM = prop.GetGetMethod(true); var setM = prop.GetSetMethod(true); @@ -204,14 +209,14 @@ namespace IPA.Utilities /// /// the name of the property /// a that can access that property - /// when the property does not exist + /// if the property does not exist public static Getter GetGetter(string name) => GetAccessors(name).get; /// /// Gets a for the property identified by . /// /// the name of the property /// a that can access that property - /// when the property does not exist + /// if the property does not exist public static Setter GetSetter(string name) => GetAccessors(name).set; /// @@ -224,7 +229,7 @@ namespace IPA.Utilities /// the instance to access /// the name of the property /// the value of the property - /// when the property does not exist + /// if the property does not exist /// /// public static U Get(ref T obj, string name) => GetGetter(name)(ref obj); @@ -234,7 +239,7 @@ namespace IPA.Utilities /// the instance to access /// the name of the property /// the value of the property - /// when the property does not exist + /// if the property does not exist /// /// public static U Get(T obj, string name) => GetGetter(name)(ref obj); @@ -247,7 +252,7 @@ namespace IPA.Utilities /// the instance to access /// the name of the property /// the new value of the property - /// when the property does not exist + /// if the property does not exist /// /// public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref obj, val); @@ -260,9 +265,75 @@ namespace IPA.Utilities /// the instance to access /// the name of the property /// the new value of the property - /// when the property does not exist + /// if the property does not exist /// /// public static void Set(T obj, string name, U val) => GetSetter(name)(ref obj, val); } + + internal class AccessorDelegateInfo where TDelegate : Delegate + { + public static readonly Type Type = typeof(TDelegate); + public static readonly MethodInfo Invoke = Type.GetMethod("Invoke"); + public static readonly ParameterInfo[] Parameters = Invoke.GetParameters(); + } + + /// + /// A type containing utilities for calling non-public methods on an object. + /// + /// the type to find the methods on + /// the delegate type to create, and to use as a signature to search for + public static class MethodAccessor where TDelegate : Delegate + { + private static readonly Dictionary methods = new Dictionary(); + + static MethodAccessor() + { + // ensure that first argument of delegate type is valid + var firstArg = AccessorDelegateInfo.Parameters.First(); + var firstType = firstArg.ParameterType; + + if (typeof(T).IsValueType) + { + if (!firstType.IsByRef) + throw new InvalidOperationException("First parameter of a method accessor to a value type is not byref"); + else + firstType = firstType.GetElementType(); // get the non-byref type to check compatability + } + + if (!typeof(T).IsAssignableFrom(firstType)) + throw new InvalidOperationException("First parameter of a method accessor is not assignable to the method owning type"); + } + + private static TDelegate MakeDelegate(string name) + { + var delParams = AccessorDelegateInfo.Parameters; + var method = typeof(T).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, delParams.Skip(1).Select(p => p.ParameterType).ToArray(), Array.Empty()); + + if (method == null) + throw new MissingMethodException(typeof(T).FullName, name); + + var retType = AccessorDelegateInfo.Invoke.ReturnType; + if (!retType.IsAssignableFrom(method.ReturnType)) + throw new ArgumentException($"The method found returns a type incompatable with the return type of {typeof(TDelegate)}"); + + return (TDelegate)Delegate.CreateDelegate(AccessorDelegateInfo.Type, method, true); + } + + /// + /// Gets a delegate to the named method with the signature specified by . + /// + /// the name of the method to get + /// a delegate that can call the specified method + /// if does not represent the name of a method with the given signature + /// if the method found returns a type incompatable with the return type of + public static TDelegate GetDelegate(string name) + { + if (!methods.TryGetValue(name, out var del)) + methods.Add(name, del = MakeDelegate(name)); + return del; + } + } + }