diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 78141e32f0fa66..f2254ea4f22873 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -172,6 +172,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs index aa351553b79966..6cf00e6edab4f3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs @@ -1,23 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; - namespace System.Reflection { public partial class ConstructorInvoker { - private readonly Signature? _signature; + private IntrinsicInvokeHelper.InvokeState _invokeState; internal unsafe ConstructorInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - _signature = constructor.Signature; - _invokeFunc_RefArgs = InterpretedInvoke; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } - private unsafe object? InterpretedInvoke(object? obj, IntPtr* args) - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); - } + private unsafe object? InvokeWithSharedThunk(object? obj, IntPtr* args) => + IntrinsicInvokeHelper.Invoke(ref _invokeState, ref _strategy, ref _invokeFunc_RefArgs, + _method, _argTypes, obj, args, backwardsCompat: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs index 08758cdf75d59f..0e16149b224058 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/InstanceCalliHelper.cs @@ -156,6 +156,62 @@ internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6) => fn(o, arg1, arg2, arg3, arg4, arg5, arg6); + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6, object? arg7) + => fn(o, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4, object? arg5, object? arg6, object? arg7, object? arg8) + => fn(o, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1) + => fn(o, arg1); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1, object? arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3) + => fn(o, arg1, arg2, arg3); + + [Intrinsic] + internal static object? Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, object? arg4) + => fn(o, arg1, arg2, arg3, arg4); + + [Intrinsic] + internal static int Call(delegate* fn, object o, object? arg1, object? arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, int arg1, int arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, long arg1, long arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, int arg2) + => fn(o, arg1, arg2); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, int arg2, object? arg3, object? arg4) + => fn(o, arg1, arg2, arg3, arg4); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, bool arg3, object? arg4) + => fn(o, arg1, arg2, arg3, arg4); + + [Intrinsic] + internal static void Call(delegate* fn, object o, object? arg1, object? arg2, object? arg3, bool arg4, object? arg5) + => fn(o, arg1, arg2, arg3, arg4, arg5); + + [Intrinsic] + internal static void Call(delegate* fn, object o, float arg1, float arg2, float arg3, int arg4) + => fn(o, arg1, arg2, arg3, arg4); + [Intrinsic] internal static void Call(delegate*?, void> fn, object o, IEnumerable? arg1) => fn(o, arg1); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs new file mode 100644 index 00000000000000..dc467a81eef993 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/IntrinsicInvokeHelper.cs @@ -0,0 +1,1237 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Reflection +{ + // Shared, precompilable thunks avoid emitting a method-specific stub for cold invocations. + // This type is included in SystemDomain::IsReflectionInvocationMethod for caller stack walks. + internal static class IntrinsicInvokeHelper + { + private const int SpecializationThreshold = 10_000; + private const MethodBase.InvokerStrategy StrategyDetermined = + MethodBase.InvokerStrategy.StrategyDetermined_Obj4Args | + MethodBase.InvokerStrategy.StrategyDetermined_ObjSpanArgs | + MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; + + internal struct InvokeState + { + internal IntPtr Thunk; + internal IntPtr FunctionPointer; + internal int InvocationCount; + } + + internal static unsafe object? Invoke( + ref InvokeState state, + ref MethodBase.InvokerStrategy strategy, + ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, + MethodBase method, + RuntimeType[] argumentTypes, + object? obj, + IntPtr* args, + bool backwardsCompat) + { + unsafe + { + if (!method.IsStatic && obj is not null && obj.GetType().IsValueType) + { + return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); + } + + var thunk = (delegate*)Volatile.Read(ref state.Thunk); + if (thunk is null) + { + if (!TryGetShape(method, argumentTypes, out thunk, out IntPtr functionPointer)) + { + return InvokeEmitted(ref strategy, ref invokeFunc, method, obj, args, backwardsCompat); + } + + state.FunctionPointer = functionPointer; + strategy |= StrategyDetermined; + Volatile.Write(ref state.Thunk, (IntPtr)thunk); + } + + if (RuntimeFeature.IsDynamicCodeCompiled && + !(LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) && + Interlocked.Increment(ref state.InvocationCount) >= SpecializationThreshold) + { + // Let the normal strategy selection specialize the next invocation's argument path. + strategy &= ~StrategyDetermined; + } + + IntPtr target = state.FunctionPointer; + if (target == IntPtr.Zero) + { + // The same MethodInfo can be invoked on different implementations. + target = RuntimeMethodHandle.GetVirtualFunctionPointer((RuntimeMethodInfo)method, obj!); + } + + object? result = thunk(target, obj, args, method.DeclaringType); + GC.KeepAlive(method); + return result; + } + } + + private static unsafe object? InvokeEmitted( + ref MethodBase.InvokerStrategy strategy, + ref InvokerEmitUtil.InvokeFunc_RefArgs? invokeFunc, + MethodBase method, object? obj, IntPtr* args, bool backwardsCompat) + { + unsafe + { + InvokerEmitUtil.InvokeFunc_RefArgs emitDelegate; + using (AssemblyBuilder.ForceAllowDynamicCode()) + { + emitDelegate = InvokerEmitUtil.CreateInvokeDelegate_RefArgs(method, backwardsCompat); + } + + Volatile.Write(ref invokeFunc, emitDelegate); + strategy |= MethodBase.InvokerStrategy.StrategyDetermined_RefArgs; + return emitDelegate(obj, args); + } + } + + private static unsafe bool TryGetShape( + MethodBase method, + ReadOnlySpan argumentTypes, + out delegate* thunk, + out IntPtr functionPointer) + { + unsafe + { + thunk = null; + functionPointer = IntPtr.Zero; + + if (method is System.Reflection.Emit.DynamicMethod || + method.ContainsGenericParameters || + (method.CallingConvention & CallingConventions.VarArgs) != 0) + { + return false; + } + + int argCount = argumentTypes.Length; + + bool referenceArguments = true; + for (int i = 0; i < argCount; i++) + { + if (!IsReferenceType(argumentTypes[i])) + { + referenceArguments = false; + break; + } + } + + if (method is ConstructorInfo) + { + if (method.IsStatic || + method.DeclaringType is not Type declaringType || + !IsReferenceType(declaringType) || + declaringType.IsAbstract || + declaringType.IsArray || + declaringType.ContainsGenericParameters || + declaringType == typeof(string)) + { + return false; + } + + // ActivatorUtilities.CreateInstance reaches StaticFileMiddleware (4), SessionMiddleware and + // RateLimitingMiddleware (5), OutputCacheMiddleware (6), and EndpointRoutingMiddleware (8). + thunk = referenceArguments ? argCount switch + { + 0 => &Ctor_0, + 1 => &Ctor_1, + 2 => &Ctor_2, + 3 => &Ctor_3, + 4 => &Ctor_4, + 5 => &Ctor_5, + 6 => &Ctor_6, + 7 => &Ctor_7, + 8 => &Ctor_8, + _ => null, + } : ClassifyConstructor(argumentTypes); + } + else if (method is MethodInfo methodInfo) + { + Type returnType = methodInfo.ReturnType; + if (method.IsStatic) + { + if (referenceArguments) + { + thunk = ClassifyStaticReferenceArguments(argCount, returnType); + } + else if (argCount == 1 && GetInputType(argumentTypes[0]) == typeof(int) && IsReferenceType(returnType)) + { + // SignalR BuildStream(int streamBufferCapacity). + thunk = &Static_Object_Int; + } + else if (argCount == 2 && IsReferenceType(argumentTypes[0]) && + argumentTypes[1].IsByRef && IsReferenceType(argumentTypes[1].GetElementType()!) && + returnType == typeof(bool)) + { + // Thunk for the .NET TryParse pattern with reference-type results. + // Typed-header TryParse(string, out T) and TryParseList(IList, out IList). + thunk = &Static_Bool_ObjByRefObj; + } + } + else if (method.DeclaringType is Type declaringType && IsReferenceType(declaringType)) + { + if (referenceArguments) + { + thunk = ClassifyInstanceReferenceArguments(argCount, returnType); + } + else if (returnType == typeof(void)) + { + if (argCount == 1) + { + // Named attribute setters (Duration, NoStore, Location, CaptureUnmatchedValues) + // and Blazor RemoveRootComponent(int). + thunk = ClassifyInstancePrimitive(GetInputType(argumentTypes[0])); + } + else if (argCount == 4 && + argumentTypes[0] == typeof(float) && argumentTypes[1] == typeof(float) && + argumentTypes[2] == typeof(float) && GetInputType(argumentTypes[3]) == typeof(int)) + { + // Blazor VirtualizeJsInterop.OnSpacerBeforeVisible and OnSpacerAfterVisible. + thunk = &Instance_Void_FloatFloatFloatInt; + } + } + } + } + + if (thunk is null) + { + return false; + } + + if (method.IsStatic || !method.IsVirtual || method.IsFinal) + { + functionPointer = method.MethodHandle.GetFunctionPointer(); + } + + return true; + } + } + + private static bool IsReferenceType(Type type) => + !type.IsValueType && !type.IsByRef && !type.IsPointer && !type.IsFunctionPointer; + + private static Type GetInputType(RuntimeType type) => + type.IsActualEnum ? type.GetEnumUnderlyingType() : type; + + private static unsafe delegate* ClassifyConstructor(ReadOnlySpan arguments) + { + unsafe + { + if (arguments.Length == 1) + { + Type type = GetInputType(arguments[0]); + // StreamRenderingAttribute and RequireAntiforgeryTokenAttribute. + if (type == typeof(bool)) return &Ctor_Bool; + // Length-based route constraints and BindingBehaviorAttribute's enum constructor. + if (type == typeof(int)) return &Ctor_Int; + // Min/Max route constraints and RequestSizeLimitAttribute. + if (type == typeof(long)) return &Ctor_Long; + } + else if (arguments.Length == 2) + { + Type first = GetInputType(arguments[0]); + Type second = GetInputType(arguments[1]); + // LengthRouteConstraint(int, int) and RangeRouteConstraint(long, long). + if (first == typeof(int) && second == typeof(int)) return &Ctor_IntInt; + if (first == typeof(long) && second == typeof(long)) return &Ctor_LongLong; + // ProducesResponseTypeAttribute(Type, int). + if (IsReferenceType(first) && second == typeof(int)) return &Ctor_ObjInt; + } + else if (arguments.Length == 4 && IsReferenceType(arguments[0]) && IsReferenceType(arguments[3])) + { + // ProducesResponseTypeAttribute(Type, int, string, string[]). + if (GetInputType(arguments[1]) == typeof(int) && IsReferenceType(arguments[2])) return &Ctor_ObjIntObjObj; + // MVC ArrayModelBinder and CollectionModelBinder. + if (IsReferenceType(arguments[1]) && GetInputType(arguments[2]) == typeof(bool)) return &Ctor_ObjObjBoolObj; + } + else if (arguments.Length == 5 && + IsReferenceType(arguments[0]) && IsReferenceType(arguments[1]) && IsReferenceType(arguments[2]) && + GetInputType(arguments[3]) == typeof(bool) && IsReferenceType(arguments[4])) + { + // MVC DictionaryModelBinder. + return &Ctor_ObjObjObjBoolObj; + } + + return null; + } + } + + private static unsafe delegate* ClassifyStaticReferenceArguments(int count, Type returnType) + { + unsafe + { + if (count == 0) + { + return ClassifyStatic0Return(returnType); + } + + if (returnType == typeof(void)) + { + return count switch + { + 1 => &Static_Void_1Obj, + 2 => &Static_Void_2Obj, + 3 => &Static_Void_3Obj, + 4 => &Static_Void_4Obj, + _ => null, + }; + } + + return IsReferenceType(returnType) ? count switch + { + 1 => &Static_Object_1Obj, + 2 => &Static_Object_2Obj, + 3 => &Static_Object_3Obj, + 4 => &Static_Object_4Obj, + _ => null, + } : null; + } + } + + private static unsafe delegate* ClassifyInstanceReferenceArguments(int count, Type returnType) + { + unsafe + { + if (returnType == typeof(void)) + { + return count switch + { + 0 => &Instance_Void_0, + 1 => &Instance_Void_1Obj, + 2 => &Instance_Void_2Obj, + 3 => &Instance_Void_3Obj, + 4 => &Instance_Void_4Obj, + _ => null, + }; + } + + if (IsReferenceType(returnType)) + { + return count switch + { + 0 => &Instance_Object_0, + 1 => &Instance_Object_1Obj, + 2 => &Instance_Object_2Obj, + 3 => &Instance_Object_3Obj, + 4 => &Instance_Object_4Obj, + _ => null, + }; + } + + if (count == 2 && returnType == typeof(int)) + { + // Blazor AddRootComponent(string, string). + return &Instance_Int_2Obj; + } + + if (count == 0) + { + if (returnType == typeof(bool)) return &Instance_Bool_0; + if (returnType == typeof(byte)) return &Instance_Byte_0; + if (returnType == typeof(sbyte)) return &Instance_SByte_0; + if (returnType == typeof(char)) return &Instance_Char_0; + if (returnType == typeof(short)) return &Instance_Short_0; + if (returnType == typeof(ushort)) return &Instance_UShort_0; + if (returnType == typeof(int)) return &Instance_Int_0; + if (returnType == typeof(uint)) return &Instance_UInt_0; + if (returnType == typeof(long)) return &Instance_Long_0; + if (returnType == typeof(ulong)) return &Instance_ULong_0; + if (returnType == typeof(float)) return &Instance_Float_0; + if (returnType == typeof(double)) return &Instance_Double_0; + if (returnType == typeof(nint)) return &Instance_NInt_0; + if (returnType == typeof(nuint)) return &Instance_NUInt_0; + } + + return null; + } + } + + private static unsafe delegate* ClassifyInstancePrimitive(Type type) + { + unsafe + { + if (type == typeof(bool)) return &Instance_Void_Bool; + if (type == typeof(byte)) return &Instance_Void_Byte; + if (type == typeof(sbyte)) return &Instance_Void_SByte; + if (type == typeof(char)) return &Instance_Void_Char; + if (type == typeof(short)) return &Instance_Void_Short; + if (type == typeof(ushort)) return &Instance_Void_UShort; + if (type == typeof(int)) return &Instance_Void_Int; + if (type == typeof(uint)) return &Instance_Void_UInt; + if (type == typeof(long)) return &Instance_Void_Long; + if (type == typeof(ulong)) return &Instance_Void_ULong; + if (type == typeof(float)) return &Instance_Void_Float; + if (type == typeof(double)) return &Instance_Void_Double; + if (type == typeof(nint)) return &Instance_Void_NInt; + if (type == typeof(nuint)) return &Instance_Void_NUInt; + return null; + } + } + + // Classifiers return a fn pointer (not invoking it) so the JIT doesn't pull thunks into + // the classifier's compiled body. + + private static unsafe delegate* ClassifyStatic0Return(Type returnType) + { + unsafe + { + if (returnType == typeof(void)) return &Static_Void_0; + if (returnType == typeof(bool)) return &Static_Bool_0; + if (returnType == typeof(byte)) return &Static_Byte_0; + if (returnType == typeof(sbyte)) return &Static_SByte_0; + if (returnType == typeof(char)) return &Static_Char_0; + if (returnType == typeof(short)) return &Static_Short_0; + if (returnType == typeof(ushort)) return &Static_UShort_0; + if (returnType == typeof(int)) return &Static_Int_0; + if (returnType == typeof(uint)) return &Static_UInt_0; + if (returnType == typeof(long)) return &Static_Long_0; + if (returnType == typeof(ulong)) return &Static_ULong_0; + if (returnType == typeof(float)) return &Static_Float_0; + if (returnType == typeof(double)) return &Static_Double_0; + if (returnType == typeof(nint) || returnType.IsFunctionPointer) return &Static_NInt_0; + if (returnType == typeof(nuint)) return &Static_NUInt_0; + + if (!returnType.IsValueType && !returnType.IsByRef && !returnType.IsPointer && !returnType.IsFunctionPointer) + return &Static_Object_0; + + return null; + } + } + + // Per-shape thunks. JIT compiles only the ones used. + + private static unsafe object? Static_Void_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + ((delegate*)fn)(); + return null; + } + } + + private static unsafe object? Static_Bool_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Byte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_SByte_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Char_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Short_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_UShort_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Int_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_UInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Long_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_ULong_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Float_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Double_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_NInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_NUInt_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Object_0(IntPtr fn, object? _, IntPtr* __, Type? ___) + { + unsafe + { + return ((delegate*)fn)(); + } + } + + private static unsafe object? Static_Void_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)(*(object?*)(void*)args[0]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Object_1Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Void_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)( + *(object?*)(void*)args[0], + *(object?*)(void*)args[1]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Object_2Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Object_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Object_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Void_3Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Void_4Obj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + ((delegate*)fn)(*(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Static_Object_Int(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { + return ((delegate*)fn)(*(int*)(void*)args[0]); + } + } + + private static unsafe object? Static_Bool_ObjByRefObj(IntPtr fn, object? _, IntPtr* args, Type? __) + { + unsafe + { +#pragma warning disable CS8500 + return ((delegate*)fn)(*(object?*)(void*)args[0], ref Unsafe.AsRef((void*)args[1])); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Object_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + +#pragma warning disable CA1859 // These thunks must match the shared object-returning function-pointer signature. + private static unsafe object? Instance_Bool_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Byte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_SByte_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Char_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Short_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_UShort_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Int_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_UInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Long_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_ULong_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Float_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Double_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_NInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_NUInt_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + return InstanceCalliHelper.Call((delegate*)fn, obj!); + } + } + + private static unsafe object? Instance_Int_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1]); +#pragma warning restore CS8500 + } + } +#pragma warning restore CA1859 + + private static unsafe object? Instance_Object_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Object_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Object_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Object_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + return InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Void_0(IntPtr fn, object? obj, IntPtr* _, Type? __) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!); + return null; + } + } + + private static unsafe object? Instance_Void_1Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Void_2Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Void_3Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Void_4Obj(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { +#pragma warning disable CS8500 + InstanceCalliHelper.Call((delegate*)fn, obj!, *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); + return null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Instance_Void_Bool(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(bool*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Byte(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(byte*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_SByte(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(sbyte*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Char(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(char*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Short(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(short*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_UShort(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(ushort*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Int(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(int*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_UInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(uint*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Long(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(long*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_ULong(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(ulong*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Float(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(float*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_Double(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(double*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_NInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(nint*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_NUInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, *(nuint*)(void*)args[0]); + return null; + } + } + + private static unsafe object? Instance_Void_FloatFloatFloatInt(IntPtr fn, object? obj, IntPtr* args, Type? _) + { + unsafe + { + InstanceCalliHelper.Call((delegate*)fn, obj!, + *(float*)(void*)args[0], *(float*)(void*)args[1], *(float*)(void*)args[2], *(int*)(void*)args[3]); + return null; + } + } + + // Ctor thunks: `obj` non-null = call ctor on existing instance, null = allocate first. + // `string` excluded: `newobj String(...)` is JIT-lowered to a hidden static allocator + // (`METHOD__STRING__CTORF_*` in src/coreclr/vm/corelib.h, wired by + // `ECall::PopulateManagedStringConstructors`); the public ctor has no callable instance + // entry, and `GetUninitializedObject(typeof(string))` is runtime-rejected. + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "Caller anchors the ctor MethodBase, keeping its type reachable.")] + private static object GetConstructorInstance(object? obj, Type? declaringType) => + obj ?? RuntimeHelpers.GetUninitializedObject(declaringType!); + + private static unsafe object? Ctor_0(IntPtr fn, object? obj, IntPtr* _, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_1(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + *(object?*)(void*)args[0]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_2(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + *(object?*)(void*)args[0], + *(object?*)(void*)args[1]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_3(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call( + (delegate*)fn, + instance, + *(object?*)(void*)args[0], + *(object?*)(void*)args[1], + *(object?*)(void*)args[2]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_4(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_4Obj(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_5(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_6(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4], *(object?*)(void*)args[5]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_7(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4], *(object?*)(void*)args[5], *(object?*)(void*)args[6]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_8(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3], *(object?*)(void*)args[4], *(object?*)(void*)args[5], *(object?*)(void*)args[6], *(object?*)(void*)args[7]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_Bool(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Bool(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_Int(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Int(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_Long(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + Instance_Void_Long(fn, instance, args, declaringType); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_IntInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, *(int*)(void*)args[0], *(int*)(void*)args[1]); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_LongLong(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, *(long*)(void*)args[0], *(long*)(void*)args[1]); + return obj is null ? instance : null; + } + } + + private static unsafe object? Ctor_ObjInt(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, *(object?*)(void*)args[0], *(int*)(void*)args[1]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_ObjIntObjObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(int*)(void*)args[1], *(object?*)(void*)args[2], *(object?*)(void*)args[3]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_ObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(bool*)(void*)args[2], *(object?*)(void*)args[3]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + + private static unsafe object? Ctor_ObjObjObjBoolObj(IntPtr fn, object? obj, IntPtr* args, Type? declaringType) + { + unsafe + { +#pragma warning disable CS8500 + object instance = GetConstructorInstance(obj, declaringType); + InstanceCalliHelper.Call((delegate*)fn, instance, + *(object?*)(void*)args[0], *(object?*)(void*)args[1], *(object?*)(void*)args[2], *(bool*)(void*)args[3], *(object?*)(void*)args[4]); + return obj is null ? instance : null; +#pragma warning restore CS8500 + } + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs index cec67b5e37c193..d7dddf1a764e8a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.CoreCLR.cs @@ -1,39 +1,43 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; namespace System.Reflection { internal partial class MethodBaseInvoker { - private readonly Signature? _signature; + private IntrinsicInvokeHelper.InvokeState _invokeState; internal unsafe MethodBaseInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments) { - _signature = method.Signature; _invocationFlags = method.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } internal unsafe MethodBaseInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - _signature = constructor.Signature; _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } internal unsafe MethodBaseInvoker(DynamicMethod method, Signature signature) : this(method, signature.Arguments) { - _signature = signature; - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; } - private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); + private unsafe object? InvokeWithSharedThunk(object? obj, IntPtr* args) => + IntrinsicInvokeHelper.Invoke(ref _invokeState, ref _strategy, ref _invokeFunc_RefArgs, + _method, _argTypes, obj, args, backwardsCompat: true); - private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: false); + internal unsafe object? InvokeDirectByRef(object? obj, IntPtr* args) + { + if ((_strategy & MethodBase.InvokerStrategy.StrategyDetermined_RefArgs) == 0) + { + MethodInvokerCommon.DetermineStrategy_RefArgs(ref _strategy, ref _invokeFunc_RefArgs, _method, backwardsCompat: true); + } + + return _invokeFunc_RefArgs!(obj, args); + } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs index e9dcc90fc1b1f7..48b0e67bef659e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs @@ -1,40 +1,35 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; namespace System.Reflection { public partial class MethodInvoker { - private readonly Signature? _signature; + private IntrinsicInvokeHelper.InvokeState _invokeState; private unsafe MethodInvoker(RuntimeMethodInfo method) : this(method, method.Signature.Arguments) { - _signature = method.Signature; - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; _invocationFlags = method.ComputeAndUpdateInvocationFlags(); } private unsafe MethodInvoker(DynamicMethod method) : this(method, method.Signature.Arguments) { - _signature = method.Signature; - _invokeFunc_RefArgs = InterpretedInvoke_Method; + _invokeFunc_RefArgs = InvokeWithSharedThunk; // No _invocationFlags for DynamicMethod. } private unsafe MethodInvoker(RuntimeConstructorInfo constructor) : this(constructor, constructor.Signature.Arguments) { - _signature = constructor.Signature; - _invokeFunc_RefArgs = InterpretedInvoke_Constructor; + _invokeFunc_RefArgs = InvokeWithSharedThunk; _invocationFlags = constructor.ComputeAndUpdateInvocationFlags(); + _needsByRefStrategy = true; } - private unsafe object? InterpretedInvoke_Method(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: false); - - private unsafe object? InterpretedInvoke_Constructor(object? obj, IntPtr* args) => - RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _signature!, isConstructor: obj is null); + private unsafe object? InvokeWithSharedThunk(object? obj, IntPtr* args) => + IntrinsicInvokeHelper.Invoke(ref _invokeState, ref _strategy, ref _invokeFunc_RefArgs, + _method, _argTypes, obj, args, backwardsCompat: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 4c2e270b14d9ce..09311706a2b588 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1096,6 +1096,17 @@ public IntPtr GetFunctionPointer() return ptr; } + [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)] + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetVirtualFunctionPointer")] + private static partial IntPtr GetVirtualFunctionPointer(RuntimeMethodHandleInternal method, QCallTypeHandle declaringType, ObjectHandleOnStack target); + + internal static IntPtr GetVirtualFunctionPointer(RuntimeMethodInfo method, object target) + { + RuntimeType declaringType = (RuntimeType)method.DeclaringType!; + return GetVirtualFunctionPointer(IRuntimeMethodInfo.GetValue(method), new QCallTypeHandle(ref declaringType), + ObjectHandleOnStack.Create(ref target)); + } + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool IsCollectible(RuntimeMethodHandleInternal method); @@ -1200,26 +1211,6 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method) return new MdUtf8String(name); } - [DebuggerStepThrough] - [DebuggerHidden] - [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)] - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_InvokeMethod")] - private static partial void InvokeMethod(ObjectHandleOnStack target, void** arguments, ObjectHandleOnStack sig, Interop.BOOL isConstructor, ObjectHandleOnStack result); - - [DebuggerStepThrough] - [DebuggerHidden] - internal static object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor) - { - object? result = null; - InvokeMethod( - ObjectHandleOnStack.Create(ref target), - arguments, - ObjectHandleOnStack.Create(ref sig), - isConstructor ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, - ObjectHandleOnStack.Create(ref result)); - return result; - } - /// /// For a true boxed Nullable{T}, re-box to a boxed {T} or null, otherwise just return the input. /// diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index bf8331776d7a32..58e39783ffd282 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -25,6 +25,7 @@ internal MethodInvoker(RuntimeMethodInfo method) internal MethodInvoker(RuntimeConstructorInfo constructor) { _methodBaseInvoker = constructor.MethodInvoker; + _parameterCount = constructor.GetParametersAsSpan().Length; } public static MethodInvoker Create(MethodBase method) diff --git a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs index 76cf68a550c3a2..6402632ee1b3ca 100644 --- a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs +++ b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs @@ -1852,8 +1852,6 @@ private enum AsyncContinuationLocation ASYNC_CONTINUATION_REGISTER_ECX = 0x0080, ASYNC_CONTINUATION_REGISTER_EDX = 0x00C0,*/ - // METHOD_INVOKE_NEEDS_ACTIVATION = 0x0040, // Flag used by ArgIteratorForMethodInvoke - // RETURN_FP_SIZE_SHIFT = 8, // The rest of the flags is cached value of GetFPReturnSize private void ComputeReturnFlags() diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs new file mode 100644 index 00000000000000..0cb8aa30dfa30c --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/InstanceCalliHelperTests.cs @@ -0,0 +1,86 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +extern alias crossgen2; + +using System; +using System.Collections.Generic; +using ILCompiler.ReadyToRun.Tests.TestCasesRunner; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; +using crossgen2::ILCompiler; +using crossgen2::Internal.IL; +using crossgen2::Internal.JitInterface; +using Xunit; + +namespace ILCompiler.ReadyToRun.Tests; + +public class InstanceCalliHelperTests +{ + [Fact] + public void CallOverloadsUseExplicitThis() + { + TargetArchitecture architecture = TestPaths.TargetArchitecture switch + { + "wasm" => TargetArchitecture.Wasm32, + "armel" => TargetArchitecture.ARM, + _ => Enum.Parse(TestPaths.TargetArchitecture, ignoreCase: true) + }; + TargetOS operatingSystem = Enum.Parse(TestPaths.TargetOS, ignoreCase: true); + var instructionSets = new InstructionSetSupport(default, default, architecture); + var target = new TargetDetails(architecture, operatingSystem, TargetAbi.NativeAot, instructionSets.GetVectorTSimdVector()); + var context = new ReadyToRunCompilerContext(target, SharedGenericsMode.CanonicalReferenceTypes, + bubbleIncludesCoreModule: true, targetAllowsRuntimeCodeGeneration: !TestPaths.IsWasmTarget, + instructionSets, oldTypeSystemContext: null) + { + InputFilePaths = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["System.Private.CoreLib"] = TestPaths.SystemPrivateCoreLibPath + }, + ReferenceFilePaths = new Dictionary(StringComparer.OrdinalIgnoreCase) + }; + var coreLib = (EcmaModule)context.GetModuleForSimpleName("System.Private.CoreLib"); + context.SetSystemModule(coreLib); + MetadataType helper = coreLib.GetType("System.Reflection"u8, "InstanceCalliHelper"u8); + int overloadCount = 0; + foreach (MethodDesc method in helper.GetMethods()) + { + if (method.Name != "Call"u8) + { + continue; + } + + overloadCount++; + MethodSignature expected = Assert.IsType(method.Signature[0]).Signature; + MethodIL il = InstanceCalliHelperIntrinsics.EmitIL(method); + var reader = new ILReader(il.GetILBytes()); + int callCount = 0; + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + if (opcode == ILOpcode.calli) + { + callCount++; + MethodSignature actual = Assert.IsType(il.GetObject(reader.ReadILToken())); + Assert.False(actual.IsStatic); + Assert.True((actual.Flags & MethodSignatureFlags.ExplicitThis) != 0); + Assert.Equal(expected.ReturnType, actual.ReturnType); + Assert.Equal(expected.Length, actual.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + else + { + reader.Skip(opcode); + } + } + + Assert.Equal(1, callCount); + } + + Assert.NotEqual(0, overloadCount); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs index a5d42af06d398c..2e97ddef090bdb 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/IL/Stubs/InstanceCalliHelperIntrinsics.cs @@ -14,7 +14,7 @@ public static MethodIL EmitIL(MethodDesc method) { MethodIL methodIL = EcmaMethodIL.Create((EcmaMethod)method); - if (method.Name.StartsWith("Invoke"u8)) + if (method.Name == "Call"u8) { methodIL = new ExplicitThisCall(methodIL); } diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 5f717668958e3e..f54ea1a9d0668e 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1242,6 +1242,8 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) CLASS__DYNAMICMETHOD, CLASS__DELEGATE, CLASS__METHODBASEINVOKER, + CLASS__INSTANCE_CALLI_HELPER, + CLASS__INTRINSIC_INVOKE_HELPER, CLASS__INITHELPERS, CLASS__STATICSHELPERS, }; diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 715f7348f533c3..d596c1dfc2a9da 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -1054,8 +1054,6 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE ASYNC_CONTINUATION_REGISTER_EDX = 0x00C0, #endif - METHOD_INVOKE_NEEDS_ACTIVATION = 0x0100, // Flag used by ArgIteratorForMethodInvoke - RETURN_FP_SIZE_SHIFT = 10, // The rest of the flags is cached value of GetFPReturnSize }; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index e56ae7f76fe941..076e779d6fbaaa 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -537,6 +537,7 @@ DEFINE_CLASS(MEMBER, Reflection, MemberInfo) DEFINE_CLASS(METHODBASEINVOKER, Reflection, MethodBaseInvoker) DEFINE_CLASS(INSTANCE_CALLI_HELPER, Reflection, InstanceCalliHelper) +DEFINE_CLASS(INTRINSIC_INVOKE_HELPER, Reflection, IntrinsicInvokeHelper) DEFINE_CLASS_U(Reflection, RuntimeMethodInfo, NoClass) DEFINE_FIELD_U(m_handle, ReflectMethodObject, m_pMD) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 06f6c895914696..26c88018ed1c95 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -175,9 +175,9 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemoryAligned) DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency) DllImportEntry(MethodBase_GetCurrentMethod) - DllImportEntry(RuntimeMethodHandle_InvokeMethod) DllImportEntry(RuntimeMethodHandle_ConstructInstantiation) DllImportEntry(RuntimeMethodHandle_GetFunctionPointer) + DllImportEntry(RuntimeMethodHandle_GetVirtualFunctionPointer) DllImportEntry(RuntimeMethodHandle_GetMethodInstantiation) DllImportEntry(RuntimeMethodHandle_GetTypicalMethodDefinition) DllImportEntry(RuntimeMethodHandle_StripMethodInstantiation) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 973927d691fc82..c671758bad8e4e 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -149,592 +149,6 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_InternalAllocNoChecks(MethodTable* p END_QCALL; } -static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - // Validate the argCnt an the Rank. Also allow nested SZARRAY's. - _ASSERTE(argCnt == (int) th.GetRank() || argCnt == (int) th.GetRank() * 2 || - th.GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY); - - // Validate all of the parameters. These all typed as integers - int allocSize = 0; - if (!ClrSafeInt::multiply(sizeof(INT32), argCnt, allocSize)) - COMPlusThrow(kArgumentException, IDS_EE_SIGTOOCOMPLEX); - - INT32* indexes = (INT32*) _alloca((size_t)allocSize); - ZeroMemory(indexes, allocSize); - MethodTable* pMT = CoreLibBinder::GetElementType(ELEMENT_TYPE_I4); - - for (DWORD i=0; i<(DWORD)argCnt; i++) - { - _ASSERTE(args[i] != NULL); - - INT32 size = *(INT32*)args[i]; - ARG_SLOT value = size; - memcpyNoGCRefs(indexes + i, ArgSlotEndiannessFixup(&value, sizeof(INT32)), sizeof(INT32)); - } - - return AllocateArrayEx(th, indexes, argCnt); -} - -static BOOL IsActivationNeededForMethodInvoke(MethodDesc * pMD) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - // The activation for non-generic instance methods is covered by non-null "this pointer" - if (!pMD->IsStatic() && !pMD->HasMethodInstantiation() && !pMD->IsInterface()) - return FALSE; - - // We need to activate the instance at least once - pMD->EnsureActive(); - return FALSE; -} - -class ArgIteratorBaseForMethodInvoke -{ -protected: - SIGNATURENATIVEREF * m_ppNativeSig; - bool m_fHasThis; - -public: - FORCEINLINE CorElementType GetReturnType(TypeHandle * pthValueType) - { - WRAPPER_NO_CONTRACT; - return (*pthValueType = (*m_ppNativeSig)->GetReturnTypeHandle()).GetInternalCorElementType(); - } -protected: - - FORCEINLINE CorElementType GetNextArgumentType(DWORD iArg, TypeHandle * pthValueType) - { - WRAPPER_NO_CONTRACT; - return (*pthValueType = (*m_ppNativeSig)->GetArgumentAt(iArg)).GetInternalCorElementType(); - } - - FORCEINLINE void Reset() - { - LIMITED_METHOD_CONTRACT; - } - - FORCEINLINE BOOL IsRegPassedStruct(TypeHandle th) - { - return th.AsMethodTable()->IsRegPassedStruct(); - } - -#if defined(UNIX_AMD64_ABI) - FORCEINLINE SystemVEightByteRegistersInfo GetEightByteRegistersInfo(TypeHandle th) - { - return th.AsMethodTable()->GetClass()->GetEightByteRegistersInfo(); - } -#endif // defined(UNIX_AMD64_ABI) - -public: - FORCEINLINE BOOL IsRetBuffPassedAsFirstArg() - { - return ::IsRetBuffPassedAsFirstArg(); - } - - BOOL HasThis() - { - LIMITED_METHOD_CONTRACT; - return m_fHasThis; - } - - BOOL HasParamType() - { - LIMITED_METHOD_CONTRACT; - // param type methods are not supported for reflection invoke, so HasParamType is always false for them - return FALSE; - } - - BOOL HasAsyncContinuation() - { - LIMITED_METHOD_CONTRACT; - // async calls are also not supported for reflection invoke - return FALSE; - } - - BOOL IsVarArg() - { - LIMITED_METHOD_CONTRACT; - // vararg methods are not supported for reflection invoke, so IsVarArg is always false for them - return FALSE; - } - - DWORD NumFixedArgs() - { - LIMITED_METHOD_CONTRACT; - return (*m_ppNativeSig)->NumFixedArgs(); - } -}; - -class ArgIteratorForMethodInvoke : public ArgIteratorTemplate -{ -public: - ArgIteratorForMethodInvoke(SIGNATURENATIVEREF * ppNativeSig, BOOL fCtorOfVariableSizedObject) - { - m_ppNativeSig = ppNativeSig; - - m_fHasThis = (*m_ppNativeSig)->HasThis() && !fCtorOfVariableSizedObject; - - DWORD dwFlags = (*m_ppNativeSig)->GetArgIteratorFlags(); - - // Use the cached values if they are available - if (dwFlags & SIZE_OF_ARG_STACK_COMPUTED) - { - m_dwFlags = dwFlags; - m_nSizeOfArgStack = (*m_ppNativeSig)->GetSizeOfArgStack(); - return; - } - - // - // Compute flags and stack argument size, and cache them for next invocation - // - - ForceSigWalk(); - - if (IsActivationNeededForMethodInvoke((*m_ppNativeSig)->GetMethod())) - { - m_dwFlags |= METHOD_INVOKE_NEEDS_ACTIVATION; - } - - (*m_ppNativeSig)->SetSizeOfArgStack(m_nSizeOfArgStack); - _ASSERTE((*m_ppNativeSig)->GetSizeOfArgStack() == m_nSizeOfArgStack); - - // This has to be last - (*m_ppNativeSig)->SetArgIteratorFlags(m_dwFlags); - _ASSERTE((*m_ppNativeSig)->GetArgIteratorFlags() == m_dwFlags); - } - - BOOL IsActivationNeeded() - { - LIMITED_METHOD_CONTRACT; - return (m_dwFlags & METHOD_INVOKE_NEEDS_ACTIVATION) != 0; - } -}; - -extern "C" void QCALLTYPE RuntimeMethodHandle_InvokeMethod( - QCall::ObjectHandleOnStack target, - PVOID* args, // An array of byrefs - QCall::ObjectHandleOnStack pSig, - BOOL fConstructor, - QCall::ObjectHandleOnStack result, - QCallExceptionStatus* qcallError) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - Thread * pThread = GetThread(); - GCX_COOP(); - - struct - { - OBJECTREF target; - SIGNATURENATIVEREF pSig; - OBJECTREF retVal; - } gc; - gc.target = NULL; - gc.pSig = NULL; - gc.retVal = NULL; - GCPROTECT_BEGIN(gc); - gc.target = target.Get(); - gc.pSig = (SIGNATURENATIVEREF)pSig.Get(); - - MethodDesc* pMeth = gc.pSig->GetMethod(); - TypeHandle ownerType = gc.pSig->GetDeclaringType(); - - if (ownerType.IsSharedByGenericInstantiations()) - { - COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); - } - -#ifdef _DEBUG - if (g_pConfig->ShouldInvokeHalt(pMeth)) - { - _ASSERTE(!"InvokeHalt"); - } -#endif - - BOOL fCtorOfVariableSizedObject = FALSE; - - if (fConstructor) - { - // If we are invoking a constructor on an array then we must - // handle this specially. - if (ownerType.IsArray()) { - gc.retVal = InvokeArrayConstructor(ownerType, - args, - gc.pSig->NumFixedArgs()); - goto Done; - } - - // Variable sized objects, like String instances, allocate themselves - // so they are a special case. - MethodTable * pMT = ownerType.AsMethodTable(); - fCtorOfVariableSizedObject = pMT->HasComponentSize(); - if (!fCtorOfVariableSizedObject) - gc.retVal = pMT->Allocate(); - } - - { - ArgIteratorForMethodInvoke argit(&gc.pSig, fCtorOfVariableSizedObject); - - if (argit.IsActivationNeeded()) - pMeth->EnsureActive(); - CONSISTENCY_CHECK(pMeth->CheckActivated()); - - UINT nStackBytes = argit.SizeOfFrameArgumentArray(); - - // Note that SizeOfFrameArgumentArray does overflow checks with sufficient margin to prevent overflows here - SIZE_T nAllocaSize = TransitionBlock::GetNegSpaceSize() + sizeof(TransitionBlock) + nStackBytes; - - LPBYTE pAlloc = (LPBYTE)_alloca(nAllocaSize); - - LPBYTE pTransitionBlock = pAlloc + TransitionBlock::GetNegSpaceSize(); - - CallDescrData callDescrData; - - callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); - callDescrData.numStackSlots = ALIGN_UP(nStackBytes, TARGET_REGISTER_SIZE) / TARGET_REGISTER_SIZE; -#ifdef CALLDESCR_ARGREGS - callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); -#endif -#ifdef CALLDESCR_RETBUFFARGREG - callDescrData.pRetBuffArg = (UINT64*)(pTransitionBlock + TransitionBlock::GetOffsetOfRetBuffArgReg()); -#endif -#ifdef CALLDESCR_FPARGREGS - callDescrData.pFloatArgumentRegisters = NULL; -#endif -#ifdef CALLDESCR_REGTYPEMAP - callDescrData.dwRegTypeMap = 0; -#endif - callDescrData.fpReturnSize = argit.GetFPReturnSize(); -#ifdef TARGET_WASM - // WASM-TODO: this is now called from the interpreter, so the arguments layout is OK. reconsider with codegen - callDescrData.nArgsSize = nStackBytes; - callDescrData.hasThis = argit.HasThis(); - - TypeHandle thValueType; - CorElementType type = argit.GetReturnType(&thValueType); - DWORD retSize = 0; - if (type == ELEMENT_TYPE_TYPEDBYREF) - { - retSize = sizeof(TypedByRef); - } - else if (type == ELEMENT_TYPE_VALUETYPE) - { - retSize = thValueType.GetSize(); - } - - callDescrData.hasRetBuff = retSize > sizeof(callDescrData.returnValue); -#endif // TARGET_WASM - - // This is duplicated logic from MethodDesc::GetCallTarget - PCODE pTarget; - { - if (pMeth->IsVtableMethod()) - { - MethodTable *pMT = gc.target->GetMethodTable(); - GCX_PREEMP(); - pTarget = pMeth->GetSingleCallableAddrOfVirtualizedCode(&gc.target, pMT, ownerType); - } - else - { - GCX_PREEMP(); - pTarget = pMeth->GetSingleCallableAddrOfCode(); - } - } - callDescrData.pTarget = pTarget; - - // Build the arguments on the stack - - GCStress::MaybeTrigger(); - - ProtectValueClassFrame *pProtectValueClassFrame = NULL; - ValueClassInfo *pValueClasses = NULL; - - // if we have the magic Value Class return, we need to allocate that class - // and place a pointer to it on the stack. - - BOOL hasRefReturnAndNeedsBoxing = FALSE; // Indicates that the method has a BYREF return type and the target type needs to be copied into a preallocated boxed object. - - TypeHandle retTH = gc.pSig->GetReturnTypeHandle(); - - TypeHandle refReturnTargetTH; // Valid only if retType == ELEMENT_TYPE_BYREF. Caches the TypeHandle of the byref target. -#ifdef TARGET_WASM - BOOL fHasRetBuffArg = callDescrData.hasRetBuff; -#else - BOOL fHasRetBuffArg = argit.HasRetBuffArg(); -#endif - CorElementType retType = retTH.GetSignatureCorElementType(); - BOOL hasValueTypeReturn = retTH.IsValueType() && retType != ELEMENT_TYPE_VOID; - _ASSERTE(hasValueTypeReturn || !fHasRetBuffArg); // only valuetypes are returned via a return buffer. - if (hasValueTypeReturn) { - gc.retVal = retTH.GetMethodTable()->Allocate(); - } - else if (retType == ELEMENT_TYPE_BYREF) - { - refReturnTargetTH = retTH.AsTypeDesc()->GetTypeParam(); - - // If the target of the byref is a value type, we need to preallocate a boxed object to hold the managed return value. - if (refReturnTargetTH.IsValueType()) - { - _ASSERTE(refReturnTargetTH.GetSignatureCorElementType() != ELEMENT_TYPE_VOID); // Managed Reflection layer has a bouncer for "ref void" returns. - hasRefReturnAndNeedsBoxing = TRUE; - gc.retVal = refReturnTargetTH.GetMethodTable()->Allocate(); - } - } - - // Copy "this" pointer - if (!pMeth->IsStatic() && !fCtorOfVariableSizedObject) { - PVOID pThisPtr; - - if (fConstructor) - { - // Copy "this" pointer: only unbox if type is value type and method is not unboxing stub - if (ownerType.IsValueType() && !pMeth->IsUnboxingStub()) { - // Note that we create a true boxed nullabe and then convert it to a T below - pThisPtr = gc.retVal->GetData(); - } - else - pThisPtr = OBJECTREFToObject(gc.retVal); - } - else if (!pMeth->GetMethodTable()->IsValueType()) - pThisPtr = OBJECTREFToObject(gc.target); - else { - if (pMeth->IsUnboxingStub()) - pThisPtr = OBJECTREFToObject(gc.target); - else { - // Create a true boxed Nullable and use that as the 'this' pointer. - // since what is passed in is just a boxed T - MethodTable* pMT = pMeth->GetMethodTable(); - if (Nullable::IsNullableType(pMT)) { - OBJECTREF bufferObj = pMT->Allocate(); - void* buffer = bufferObj->GetData(); - Nullable::UnBox(buffer, gc.target, pMT); - pThisPtr = buffer; - } - else - pThisPtr = gc.target->UnBox(); - } - } - - *((LPVOID*) (pTransitionBlock + argit.GetThisOffset())) = pThisPtr; - } - - // NO GC AFTER THIS POINT. The object references in the method frame are not protected. - // - // We have already copied "this" pointer so we do not want GC to happen even sooner. Unfortunately, - // we may allocate in the process of copying this pointer that makes it hard to express using contracts. - // - // If an exception occurs a gc may happen but we are going to dump the stack anyway and we do - // not need to protect anything. - - // Allocate a local buffer for the return buffer if necessary - PVOID pLocalRetBuf = nullptr; - - { - BEGINFORBIDGC(); -#ifdef _DEBUG - GCForbidLoaderUseHolder forbidLoaderUse; -#endif - - // Take care of any return arguments - if (fHasRetBuffArg) - { - _ASSERT(hasValueTypeReturn); - PTR_MethodTable pMT = retTH.GetMethodTable(); - size_t localRetBufSize = retTH.GetSize(); - - // Allocate a local buffer. The invoked method will write the return value to this - // buffer which will be copied to gc.retVal later. - pLocalRetBuf = _alloca(localRetBufSize); - ZeroMemory(pLocalRetBuf, localRetBufSize); -#ifdef TARGET_WASM - callDescrData.pRetBuffArg = reinterpret_cast(pLocalRetBuf); -#else - *((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pLocalRetBuf; -#endif - if (pMT->ContainsGCPointers()) - { - pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pLocalRetBuf, pMT, pValueClasses); - } - } - - // copy args - UINT nNumArgs = gc.pSig->NumFixedArgs(); - for (UINT i = 0 ; i < nNumArgs; i++) { - TypeHandle th = gc.pSig->GetArgumentAt(i); - - int ofs = argit.GetNextOffset(); - _ASSERTE(ofs != TransitionBlock::InvalidOffset); - -#ifdef CALLDESCR_REGTYPEMAP - FillInRegTypeMap(ofs, argit.GetArgType(), (BYTE *)&callDescrData.dwRegTypeMap); -#endif - -#ifdef CALLDESCR_FPARGREGS - // Under CALLDESCR_FPARGREGS -ve offsets indicate arguments in floating point registers. If we have at - // least one such argument we point the call worker at the floating point area of the frame (we leave - // it null otherwise since the worker can perform a useful optimization if it knows no floating point - // registers need to be set up). - - if (TransitionBlock::HasFloatRegister(ofs, argit.GetArgLocDescForStructInRegs()) && - (callDescrData.pFloatArgumentRegisters == NULL)) - { - callDescrData.pFloatArgumentRegisters = (FloatArgumentRegisters*) (pTransitionBlock + - TransitionBlock::GetOffsetOfFloatArgumentRegisters()); - } -#endif - - UINT structSize = argit.GetArgSize(); - - ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - -#ifdef ENREGISTERED_PARAMTYPE_MAXSIZE - if (argit.IsArgPassedByRef()) - { - MethodTable* pMT = th.GetMethodTable(); - _ASSERTE(pMT && pMT->IsValueType()); - - PVOID pArgDst = argDest.GetDestinationAddress(); - - PVOID pStackCopy = _alloca(structSize); - *(PVOID *)pArgDst = pStackCopy; - - // save the info into ValueClassInfo - if (pMT->ContainsGCPointers()) - { - pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pStackCopy, pMT, pValueClasses); - } - - // We need a new ArgDestination that points to the stack copy - argDest = ArgDestination(pStackCopy, 0, NULL); - } -#endif - - InvokeUtil::CopyArg(th, args[i], &argDest); - } - - ENDFORBIDGC(); - } - - if (pValueClasses != NULL) - { - pProtectValueClassFrame = new (_alloca (sizeof (ProtectValueClassFrame))) - ProtectValueClassFrame(pThread, pValueClasses); - } - - // Call the method - CallDescrWorkerWithHandler(&callDescrData); - - if (fHasRetBuffArg) - { - // Copy the return value from the return buffer to the object - if (retTH.GetMethodTable()->ContainsGCPointers()) - { - memmoveGCRefs(gc.retVal->GetData(), pLocalRetBuf, retTH.GetSize()); - } - else - { - memcpyNoGCRefs(gc.retVal->GetData(), pLocalRetBuf, retTH.GetSize()); - } - } - - // It is still illegal to do a GC here. The return type might have/contain GC pointers. - if (fConstructor) - { - // We have a special case for Strings...The object is returned... - if (fCtorOfVariableSizedObject) { - PVOID pReturnValue = &callDescrData.returnValue; - gc.retVal = ObjectToOBJECTREF(*(Object**)pReturnValue); - } - - // If it is a Nullable, box it using Nullable conventions. - // TODO: this double allocates on constructions which is wasteful - gc.retVal = Nullable::NormalizeBox(gc.retVal); - } - else - if (hasValueTypeReturn || hasRefReturnAndNeedsBoxing) - { - _ASSERTE(gc.retVal != NULL); - - if (hasRefReturnAndNeedsBoxing) - { - // Method has BYREF return and the target type is one that needs boxing. We need to copy into the boxed object we have allocated for this purpose. - LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; - if (pReturnedReference == NULL) - { - COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); - } - CopyValueClass(gc.retVal->GetData(), pReturnedReference, gc.retVal->GetMethodTable()); - } - // if the structure is returned by value, then we need to copy in the boxed object - // we have allocated for this purpose. - else if (!fHasRetBuffArg) - { -#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) - if (callDescrData.fpReturnSize != FpStruct::UseIntCallConv) - { - FpStructInRegistersInfo info = argit.GetReturnFpStructInRegistersInfo(); - bool hasPointers = gc.retVal->GetMethodTable()->ContainsGCPointers(); - CopyReturnedFpStructFromRegisters(gc.retVal->GetData(), callDescrData.returnValue, info, hasPointers); - } - else -#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) - { - CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable()); - } - } - // From here on out, it is OK to have GCs since the return object (which may have had - // GC pointers has been put into a GC object and thus protected. - - // TODO this creates two objects which is inefficient - // If the return type is a Nullable box it into the correct form - gc.retVal = Nullable::NormalizeBox(gc.retVal); - } - else if (retType == ELEMENT_TYPE_BYREF) - { - // WARNING: pReturnedReference is an unprotected inner reference so we must not trigger a GC until the referenced value has been safely captured. - LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; - if (pReturnedReference == NULL) - { - COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); - } - - gc.retVal = InvokeUtil::CreateObjectAfterInvoke(refReturnTargetTH, pReturnedReference); - } - else - { - gc.retVal = InvokeUtil::CreateObjectAfterInvoke(retTH, &callDescrData.returnValue); - } - - if (pProtectValueClassFrame != NULL) - pProtectValueClassFrame->Pop(pThread); - - } - -Done: - result.Set(gc.retVal); - - GCPROTECT_END(); - - END_QCALL; -} - struct SkipStruct { SkipStruct(StackCrawlMark* mark, PTR_Thread thread) : pStackMark(mark) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 5dc686e5a71795..9207ecc69b8bc9 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1318,6 +1318,38 @@ extern "C" void * QCALLTYPE RuntimeMethodHandle_GetFunctionPointer(MethodDesc * return funcPtr; } +extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer( + MethodDesc* pMethod, QCall::TypeHandle declaringType, QCall::ObjectHandleOnStack target, QCallExceptionStatus* qcallError) +{ + QCALL_CONTRACT; + + void* result = nullptr; + BEGIN_QCALL; + + GCX_COOP(); + OBJECTREF receiver = nullptr; + GCPROTECT_BEGIN(receiver); + receiver = target.Get(); + _ASSERTE(receiver != nullptr); + MethodTable* pReceiverMT = receiver->GetMethodTable(); + { + GCX_PREEMP(); + pMethod->EnsureActive(); + PCODE callTarget = pMethod->IsVtableMethod() + ? pMethod->GetSingleCallableAddrOfVirtualizedCode(&receiver, pReceiverMT, declaringType.AsTypeHandle()) + : pMethod->GetSingleCallableAddrOfCode(); +#ifdef FEATURE_PORTABLE_ENTRYPOINTS + // Virtual dispatch can return an entrypoint whose R2R-to-interpreter thunk is not prepared yet. + MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(callTarget); +#endif // FEATURE_PORTABLE_ENTRYPOINTS + result = reinterpret_cast(callTarget); + } + GCPROTECT_END(); + + END_QCALL; + return result; +} + FCIMPL1(LPCUTF8, RuntimeMethodHandle::GetUtf8Name, MethodDesc* pMethod) { CONTRACTL diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 0d366b257f5ab1..d2bedd2c90add2 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -225,16 +225,9 @@ extern "C" BOOL QCALLTYPE RuntimeMethodHandle_IsCAVisibleFromDecoratedType( extern "C" void QCALLTYPE RuntimeMethodHandle_GetMethodInstantiation(MethodDesc * pMethod, QCall::ObjectHandleOnStack retTypes, BOOL fAsRuntimeTypeArray, QCallExceptionStatus* qcallError); -extern "C" void QCALLTYPE RuntimeMethodHandle_InvokeMethod( - QCall::ObjectHandleOnStack target, - PVOID* args, - QCall::ObjectHandleOnStack pSigUNSAFE, - BOOL fConstructor, - QCall::ObjectHandleOnStack result, - QCallExceptionStatus* qcallError); - extern "C" void QCALLTYPE RuntimeMethodHandle_ConstructInstantiation(MethodDesc * pMethod, DWORD format, QCall::StringHandleOnStack retString, QCallExceptionStatus* qcallError); extern "C" void* QCALLTYPE RuntimeMethodHandle_GetFunctionPointer(MethodDesc * pMethod, QCallExceptionStatus* qcallError); +extern "C" void* QCALLTYPE RuntimeMethodHandle_GetVirtualFunctionPointer(MethodDesc* pMethod, QCall::TypeHandle declaringType, QCall::ObjectHandleOnStack target, QCallExceptionStatus* qcallError); extern "C" BOOL QCALLTYPE RuntimeMethodHandle_GetIsCollectible(MethodDesc * pMethod); extern "C" void QCALLTYPE RuntimeMethodHandle_GetTypicalMethodDefinition(MethodDesc * pMethod, QCall::ObjectHandleOnStack refMethod, QCallExceptionStatus* qcallError); extern "C" void QCALLTYPE RuntimeMethodHandle_StripMethodInstantiation(MethodDesc * pMethod, QCall::ObjectHandleOnStack refMethod, QCallExceptionStatus* qcallError); diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs index 05c6ee32132359..7abda092f10b5f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -170,8 +170,34 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, } } - // Push the arguments. ReadOnlySpan parameters = method.GetParametersAsSpan(); +#if !MONO + if (emitNew) + { + Label allocateAndInvoke = il.DefineLabel(); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Brfalse, allocateAndInvoke); + + il.Emit(OpCodes.Ldarg_1); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } + + EmitLoadRefArguments(il, parameters); + EmitCallAndReturnHandling(il, method, emitNew: false, backwardsCompat); + il.MarkLabel(allocateAndInvoke); + } +#endif + EmitLoadRefArguments(il, parameters); + EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); + + // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null); + } + + private static void EmitLoadRefArguments(ILGenerator il, ReadOnlySpan parameters) + { for (int i = 0; i < parameters.Length; i++) { il.Emit(OpCodes.Ldarg_2); @@ -189,11 +215,6 @@ public static InvokeFunc_RefArgs CreateInvokeDelegate_RefArgs(MethodBase method, il.Emit(OpCodes.Ldobj, parameterType.IsPointer || parameterType.IsFunctionPointer ? typeof(IntPtr) : parameterType); } } - - EmitCallAndReturnHandling(il, method, emitNew, backwardsCompat); - - // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. - return (InvokeFunc_RefArgs)dm.CreateDelegate(typeof(InvokeFunc_RefArgs), target: null); } private static void Unbox(ILGenerator il, Type parameterType) @@ -224,6 +245,10 @@ private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, { il.Emit(OpCodes.Newobj, (ConstructorInfo)method); } + else if (method is ConstructorInfo constructor) + { + il.Emit(OpCodes.Call, constructor); + } else if (method.IsStatic || method.DeclaringType!.IsValueType) { il.Emit(OpCodes.Call, (MethodInfo)method); @@ -242,6 +267,10 @@ private static void EmitCallAndReturnHandling(ILGenerator il, MethodBase method, il.Emit(OpCodes.Box, returnType); } } + else if (method is ConstructorInfo) + { + il.Emit(OpCodes.Ldnull); + } else { RuntimeType returnType; diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs index 61ce60ff5bd4ec..998f18c15a746d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.Constructor.cs @@ -47,8 +47,11 @@ internal sealed partial class MethodBaseInvoker try { - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. +#if MONO ret = InterpretedInvoke_Constructor(obj, pByRefStorage); +#else + ret = InvokeDirectByRef(obj, pByRefStorage); +#endif } catch (Exception e) when (wrapInTargetInvocationException) { @@ -69,8 +72,11 @@ internal sealed partial class MethodBaseInvoker { try { - // Use the interpreted version to avoid having to generate a new method that doesn't allocate. +#if MONO return InterpretedInvoke_Constructor(obj, null); +#else + return InvokeDirectByRef(obj, null); +#endif } catch (Exception e) when (wrapInTargetInvocationException) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index f03b1d420858a8..0df895bb1bb0ce 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -68,8 +68,10 @@ public static MethodInvoker Create(MethodBase method) // such as created from RuntimeHelpers.GetUninitializedObject(Type). MethodInvoker invoker = new MethodInvoker(rci); +#if MONO // Use the interpreted version to avoid having to generate a new method that doesn't allocate. invoker._strategy = GetStrategyForUsingInterpreted(); +#endif return invoker; } @@ -264,7 +266,7 @@ private MethodInvoker(MethodBase method, RuntimeType[] argumentTypes) } } - if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers)) != 0) + if ((_invocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) { ThrowForBadInvocationFlags(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs index 3e8bdb5778970b..f328f1425acd37 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvokerCommon.cs @@ -18,13 +18,12 @@ internal static void Initialize( { if (LocalAppContextSwitches.ForceInterpretedInvoke && !LocalAppContextSwitches.ForceEmitInvoke) { - // Always use the native interpreted invoke. - // Useful for testing, to avoid startup overhead of emit, or for calling a ctor on already initialized object. + // Keep using the slow path instead of promoting to a specialized invoke stub. strategy = GetStrategyForUsingInterpreted(); } else if (LocalAppContextSwitches.ForceEmitInvoke && !LocalAppContextSwitches.ForceInterpretedInvoke) { - // Always use emit invoke (if IsDynamicCodeSupported == true); useful for testing. + // Always use emit invoke (if IsDynamicCodeCompiled == true); useful for testing. strategy = GetStrategyForUsingEmit(); } else @@ -72,7 +71,7 @@ internal static void Initialize( internal static InvokerStrategy GetStrategyForUsingInterpreted() { - // This causes the default strategy, which is interpreted, to always be used. + // Keep the default strategy instead of promoting to a specialized invoke stub. return InvokerStrategy.StrategyDetermined_Obj4Args | InvokerStrategy.StrategyDetermined_ObjSpanArgs | InvokerStrategy.StrategyDetermined_RefArgs; } @@ -113,15 +112,18 @@ ref InvokeFunc_ObjSpanArgs? // If ByRefs are used, we can't use this strategy. strategy |= InvokerStrategy.StrategyDetermined_ObjSpanArgs; } - else if (((strategy & InvokerStrategy.HasBeenInvoked_ObjSpanArgs) == 0) && !Debugger.IsAttached) + else if ((strategy & InvokerStrategy.HasBeenInvoked_ObjSpanArgs) == 0 +#if MONO + && !Debugger.IsAttached +#endif + ) { - // The first time, ignoring race conditions, use the slow path, except for the case when running under a debugger. - // This is a workaround for the debugger issues with understanding exceptions propagation over the slow path. + // Start with the slow path unless a forced strategy has already been selected. strategy |= InvokerStrategy.HasBeenInvoked_ObjSpanArgs; } else { - if (RuntimeFeature.IsDynamicCodeSupported) + if (RuntimeFeature.IsDynamicCodeCompiled) { invokeFunc_ObjSpanArgs = CreateInvokeDelegate_ObjSpanArgs(method, backwardsCompat); } @@ -142,15 +144,18 @@ internal static void DetermineStrategy_Obj4Args( // If ByRefs are used, we can't use this strategy. strategy |= InvokerStrategy.StrategyDetermined_Obj4Args; } - else if (((strategy & InvokerStrategy.HasBeenInvoked_Obj4Args) == 0) && !Debugger.IsAttached) + else if ((strategy & InvokerStrategy.HasBeenInvoked_Obj4Args) == 0 +#if MONO + && !Debugger.IsAttached +#endif + ) { - // The first time, ignoring race conditions, use the slow path, except for the case when running under a debugger. - // This is a workaround for the debugger issues with understanding exceptions propagation over the slow path. + // Start with the slow path unless a forced strategy has already been selected. strategy |= InvokerStrategy.HasBeenInvoked_Obj4Args; } else { - if (RuntimeFeature.IsDynamicCodeSupported) + if (RuntimeFeature.IsDynamicCodeCompiled) { invokeFunc_Obj4Args = CreateInvokeDelegate_Obj4Args(method, backwardsCompat); } @@ -165,15 +170,18 @@ internal static void DetermineStrategy_RefArgs( MethodBase method, bool backwardsCompat) { - if (((strategy & InvokerStrategy.HasBeenInvoked_RefArgs) == 0) && !Debugger.IsAttached) + if ((strategy & InvokerStrategy.HasBeenInvoked_RefArgs) == 0 +#if MONO + && !Debugger.IsAttached +#endif + ) { - // The first time, ignoring race conditions, use the slow path, except for the case when running under a debugger. - // This is a workaround for the debugger issues with understanding exceptions propagation over the slow path. + // Start with the slow path unless a forced strategy has already been selected. strategy |= InvokerStrategy.HasBeenInvoked_RefArgs; } else { - if (RuntimeFeature.IsDynamicCodeSupported) + if (RuntimeFeature.IsDynamicCodeCompiled) { invokeFunc_RefArgs = CreateInvokeDelegate_RefArgs(method, backwardsCompat); } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index 7823d7733e2fdf..9171a0fd9014ff 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; using Xunit; #pragma warning disable 0414 @@ -25,6 +28,354 @@ public override object Invoke(ConstructorInfo constructorInfo, object?[]? parame protected override bool IsExceptionWrapped => true; + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Invoke_AllocatingAndExistingInstanceAcrossTiers(bool valueArgument) + { + Type argumentType = valueArgument ? typeof(int) : typeof(object); + ConstructorInfo constructor = typeof(MutableConstructorTarget).GetConstructor(new[] { argumentType }); + var existing = new MutableConstructorTarget(null); + object value = valueArgument ? 42 : new object(); + object[] arguments = { value }; + + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + { + var allocated = (MutableConstructorTarget)constructor.Invoke(arguments); + Assert.NotSame(existing, allocated); + Assert.Equal(value, allocated.Value); + Assert.Null(constructor.Invoke(existing, arguments)); + Assert.Equal(value, existing.Value); + } + } + + public sealed class MutableConstructorTarget + { + public object? Value; + + public MutableConstructorTarget(object? value) => Value = value; + public MutableConstructorTarget(int value) => Value = value; + } + + public static IEnumerable Invoke_ReferenceConstructors_SharedThunk_TestData() + { + yield return new object[] { Type.EmptyTypes, Array.Empty() }; + yield return new object[] + { + new Type[] { typeof(IIntrinsicInvokeReference) }, + new object?[] { new IntrinsicInvokeReference(1) } + }; + yield return new object[] + { + new Type[] { typeof(object[]), typeof(Action) }, + new object?[] { new object[] { "two" }, (Action)(() => { }) } + }; + yield return new object[] + { + new Type[] { typeof(Task), typeof(IIntrinsicInvokeReference), typeof(object[]) }, + new object?[] { Task.FromResult("three"), new IntrinsicInvokeReference(3), new object[] { "three" } } + }; + yield return new object[] + { + new Type[] { typeof(object), typeof(IIntrinsicInvokeReference), typeof(Action), typeof(Task) }, + new object?[] { new object(), new IntrinsicInvokeReference(4), (Action)(() => { }), Task.FromResult(4) } + }; + yield return new object[] + { + new Type[] { typeof(Task), typeof(object[]), typeof(IIntrinsicInvokeReference), typeof(Action), typeof(string) }, + new object?[] { Task.FromResult(5), new object[] { "five" }, new IntrinsicInvokeReference(5), (Action)(() => { }), "five" } + }; + yield return new object[] + { + new Type[] { typeof(object), typeof(IIntrinsicInvokeReference), typeof(object[]), typeof(Action), typeof(Task), typeof(Func) }, + new object?[] + { + new object(), + new IntrinsicInvokeReference(6), + new object[] { "six" }, + (Action)(() => { }), + Task.FromResult("six"), + (Func)(() => "six") + } + }; + yield return new object[] + { + new Type[] + { + typeof(IIntrinsicInvokeReference), + typeof(object[]), + typeof(Action), + typeof(Task), + typeof(string), + typeof(object), + typeof(Func) + }, + new object?[] + { + new IntrinsicInvokeReference(7), + new object[] { "seven" }, + (Action)(() => { }), + Task.FromResult(7), + "seven", + new object(), + (Func)(() => 7) + } + }; + yield return new object[] + { + new Type[] + { + typeof(object[]), + typeof(IIntrinsicInvokeReference), + typeof(Action), + typeof(Task), + typeof(string), + typeof(object), + typeof(Func), + typeof(Task) + }, + new object?[] + { + new object[] { "eight" }, + new IntrinsicInvokeReference(8), + (Action)(() => { }), + Task.FromResult("eight"), + "eight", + new object(), + (Func)(() => 8), + Task.FromResult(8) + } + }; + } + + [Theory] + [MemberData(nameof(Invoke_ReferenceConstructors_SharedThunk_TestData))] + public void Invoke_ReferenceConstructors_SharedThunk(Type[] parameterTypes, object?[] arguments) + { + ConstructorInfo constructor = typeof(IntrinsicInvokeReferenceConstructorTarget).GetConstructor(parameterTypes)!; + + var result = (IntrinsicInvokeReferenceConstructorTarget)constructor.Invoke(arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(constructor); + Assert.Equal(arguments.Length, result.Values.Length); + for (int i = 0; i < arguments.Length; i++) + { + Assert.Same(arguments[i], result.Values[i]); + } + } + + public static IEnumerable Invoke_PrimitivePatternConstructors_SharedThunk_TestData() + { + yield return new object[] { new Type[] { typeof(bool) }, new object?[] { true } }; + yield return new object[] { new Type[] { typeof(bool) }, new object?[] { false } }; + yield return new object[] { new Type[] { typeof(int) }, new object?[] { int.MinValue + 12345 } }; + yield return new object[] { new Type[] { typeof(long) }, new object?[] { long.MaxValue - 12345 } }; + yield return new object[] { new Type[] { typeof(int), typeof(int) }, new object?[] { int.MinValue, int.MaxValue } }; + yield return new object[] { new Type[] { typeof(long), typeof(long) }, new object?[] { long.MinValue, long.MaxValue } }; + yield return new object[] + { + new Type[] { typeof(IIntrinsicInvokeReference), typeof(int) }, + new object?[] { new IntrinsicInvokeReference(7), 8 } + }; + yield return new object[] + { + new Type[] { typeof(object[]), typeof(int), typeof(Action), typeof(Task) }, + new object?[] { new object[] { "nine" }, 9, (Action)(() => { }), Task.FromResult("nine") } + }; + yield return new object[] + { + new Type[] { typeof(Task), typeof(IIntrinsicInvokeReference), typeof(bool), typeof(Action) }, + new object?[] { Task.FromResult("ten"), new IntrinsicInvokeReference(10), true, (Action)(() => { }) } + }; + yield return new object[] + { + new Type[] { typeof(object[]), typeof(Action), typeof(Task), typeof(bool), typeof(IIntrinsicInvokeReference) }, + new object?[] + { + new object[] { "eleven" }, + (Action)(() => { }), + Task.FromResult("eleven"), + false, + new IntrinsicInvokeReference(11) + } + }; + } + + [Theory] + [MemberData(nameof(Invoke_PrimitivePatternConstructors_SharedThunk_TestData))] + public void Invoke_PrimitivePatternConstructors_SharedThunk(Type[] parameterTypes, object?[] arguments) + { + ConstructorInfo constructor = typeof(IntrinsicInvokePrimitiveConstructorTarget).GetConstructor(parameterTypes)!; + + var result = (IntrinsicInvokePrimitiveConstructorTarget)constructor.Invoke(arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(constructor); + Assert.Equal(arguments.Length, result.Values.Length); + for (int i = 0; i < arguments.Length; i++) + { + if (parameterTypes[i].IsValueType) + { + Assert.Equal(arguments[i], result.Values[i]); + } + else + { + Assert.Same(arguments[i], result.Values[i]); + } + } + } + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Byte() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeByteEnum)), IntrinsicInvokeByteEnum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_SByte() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeSByteEnum)), IntrinsicInvokeSByteEnum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Int16() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt16Enum)), IntrinsicInvokeInt16Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt16() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt16Enum)), IntrinsicInvokeUInt16Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Int32() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt32Enum)), IntrinsicInvokeInt32Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt32() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt32Enum)), IntrinsicInvokeUInt32Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_Int64() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt64Enum)), IntrinsicInvokeInt64Enum.Value); + + [Fact] + public void Invoke_EnumConstructors_MapActualUnderlyingType_UInt64() => + Invoke_EnumConstructors_MapActualUnderlyingType(typeof(IntrinsicInvokeEnumConstructorTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt64Enum)), IntrinsicInvokeUInt64Enum.Value); + + private static void Invoke_EnumConstructors_MapActualUnderlyingType( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicFields)] Type targetType, + object value) + { + Type enumType = value.GetType(); + ConstructorInfo constructor = targetType.GetConstructor(new Type[] { enumType })!; + + object result = constructor.Invoke(new object?[] { value }); + + if (enumType == typeof(IntrinsicInvokeInt32Enum) || enumType == typeof(IntrinsicInvokeInt64Enum)) + { + IntrinsicInvokeSelectionAssertions.AssertShared(constructor); + } + else + { + IntrinsicInvokeSelectionAssertions.AssertFallback(constructor); + } + + object? actual = targetType.GetField(nameof(IntrinsicInvokeEnumConstructorTarget.Value))!.GetValue(result); + Assert.NotNull(actual); + Assert.Equal(enumType, actual.GetType()); + Assert.Equal(value, actual); + } + + public static IEnumerable Invoke_ExcludedConstructors_Fallback_TestData() + { + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(DateTime) }, + new object?[] { new DateTime(2026, 9, 10) }, + new DateTime(2026, 9, 10) + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(int?) }, + new object?[] { (int?)42 }, + 42 + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(ValueTask) }, + new object?[] { new ValueTask(43) }, + new ValueTask(43) + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(CancellationToken) }, + new object?[] { new CancellationToken(canceled: true) }, + new CancellationToken(canceled: true) + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] { typeof(int), typeof(int), typeof(int) }, + new object?[] { 1, 2, 3 }, + 6 + }; + yield return new object[] + { + typeof(IntrinsicInvokeStructConstructorTarget), + new Type[] { typeof(int) }, + new object?[] { 44 }, + 44 + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedConstructorTarget), + new Type[] + { + typeof(object), typeof(object), typeof(object), typeof(object), typeof(object), + typeof(object), typeof(object), typeof(object), typeof(object) + }, + new object?[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + 9 + }; + } + + [Theory] + [MemberData(nameof(Invoke_ExcludedConstructors_Fallback_TestData))] + public void Invoke_ExcludedConstructors_Fallback( + Type declaringType, + Type[] parameterTypes, + object?[] arguments, + object expected) + { + ConstructorInfo constructor = declaringType.GetConstructor(parameterTypes)!; + + object result = constructor.Invoke(arguments); + + Assert.Equal(expected, declaringType.GetField(nameof(IntrinsicInvokeExcludedConstructorTarget.Value))!.GetValue(result)); + IntrinsicInvokeSelectionAssertions.AssertFallback(constructor); + } + + [Fact] + public void Invoke_SharedConstructorThunk_UsesNormalArgumentValidation() + { + ConstructorInfo referenceConstructor = typeof(IntrinsicInvokeConstructorValidationTarget).GetConstructor( + new Type[] { typeof(IIntrinsicInvokeReference) })!; + referenceConstructor.Invoke(new object?[] { new IntrinsicInvokeReference(1) }); + IntrinsicInvokeSelectionAssertions.AssertShared(referenceConstructor); + Assert.Throws(() => referenceConstructor.Invoke(new object?[] { new object() })); + + ConstructorInfo primitiveConstructor = typeof(IntrinsicInvokeConstructorValidationTarget).GetConstructor( + new Type[] { typeof(int) })!; + primitiveConstructor.Invoke(new object?[] { 1 }); + IntrinsicInvokeSelectionAssertions.AssertShared(primitiveConstructor); + Assert.Throws(() => primitiveConstructor.Invoke(new object?[] { 1L })); + + ConstructorInfo enumConstructor = typeof(IntrinsicInvokeConstructorValidationTarget).GetConstructor( + new Type[] { typeof(IntrinsicInvokeInt32Enum) })!; + enumConstructor.Invoke(new object?[] { IntrinsicInvokeInt32Enum.Value }); + IntrinsicInvokeSelectionAssertions.AssertShared(enumConstructor); + Assert.Throws(() => enumConstructor.Invoke(new object?[] { IntrinsicInvokeInt64Enum.Value })); + } + [Fact] public void ConstructorName() { @@ -208,4 +559,199 @@ public StructWith1Constructor(int x, int y) this.y = y; } } + + internal sealed class IntrinsicInvokeReferenceConstructorTarget + { + internal object?[] Values { get; } + + public IntrinsicInvokeReferenceConstructorTarget() => + Values = Collect(); + + public IntrinsicInvokeReferenceConstructorTarget(IIntrinsicInvokeReference value) => + Values = Collect(value); + + public IntrinsicInvokeReferenceConstructorTarget(object[] values, Action callback) => + Values = Collect(values, callback); + + public IntrinsicInvokeReferenceConstructorTarget( + Task task, + IIntrinsicInvokeReference reference, + object[] values) => + Values = Collect(task, reference, values); + + public IntrinsicInvokeReferenceConstructorTarget( + object value, + IIntrinsicInvokeReference reference, + Action callback, + Task task) => + Values = Collect(value, reference, callback, task); + + public IntrinsicInvokeReferenceConstructorTarget( + Task task, + object[] values, + IIntrinsicInvokeReference reference, + Action callback, + string text) => + Values = Collect(task, values, reference, callback, text); + + public IntrinsicInvokeReferenceConstructorTarget( + object value, + IIntrinsicInvokeReference reference, + object[] values, + Action callback, + Task task, + Func factory) => + Values = Collect(value, reference, values, callback, task, factory); + + public IntrinsicInvokeReferenceConstructorTarget( + IIntrinsicInvokeReference reference, + object[] values, + Action callback, + Task task, + string text, + object value, + Func factory) => + Values = Collect(reference, values, callback, task, text, value, factory); + + public IntrinsicInvokeReferenceConstructorTarget( + object[] values, + IIntrinsicInvokeReference reference, + Action callback, + Task textTask, + string text, + object value, + Func factory, + Task valueTask) => + Values = Collect(values, reference, callback, textTask, text, value, factory, valueTask); + + private static object?[] Collect(params object?[] values) + { + GC.Collect(); + return values; + } + } + + internal sealed class IntrinsicInvokePrimitiveConstructorTarget + { + internal object?[] Values { get; } + + public IntrinsicInvokePrimitiveConstructorTarget(bool value) => + Values = Collect(value); + + public IntrinsicInvokePrimitiveConstructorTarget(int value) => + Values = Collect(value); + + public IntrinsicInvokePrimitiveConstructorTarget(long value) => + Values = Collect(value); + + public IntrinsicInvokePrimitiveConstructorTarget(int first, int second) => + Values = Collect(first, second); + + public IntrinsicInvokePrimitiveConstructorTarget(long first, long second) => + Values = Collect(first, second); + + public IntrinsicInvokePrimitiveConstructorTarget(IIntrinsicInvokeReference reference, int value) => + Values = Collect(reference, value); + + public IntrinsicInvokePrimitiveConstructorTarget( + object[] values, + int number, + Action callback, + Task task) => + Values = Collect(values, number, callback, task); + + public IntrinsicInvokePrimitiveConstructorTarget( + Task task, + IIntrinsicInvokeReference reference, + bool value, + Action callback) => + Values = Collect(task, reference, value, callback); + + public IntrinsicInvokePrimitiveConstructorTarget( + object[] values, + Action callback, + Task task, + bool value, + IIntrinsicInvokeReference reference) => + Values = Collect(values, callback, task, value, reference); + + private static object?[] Collect(params object?[] values) + { + GC.Collect(); + return values; + } + } + + internal sealed class IntrinsicInvokeEnumConstructorTarget where T : struct, Enum + { + public T Value; + + public IntrinsicInvokeEnumConstructorTarget(T value) + { + GC.Collect(); + Value = value; + } + } + + internal sealed class IntrinsicInvokeExcludedConstructorTarget + { + public object? Value; + + public IntrinsicInvokeExcludedConstructorTarget(DateTime value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(int? value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(ValueTask value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(CancellationToken value) => + Value = Collect(value); + + public IntrinsicInvokeExcludedConstructorTarget(int first, int second, int third) => + Value = Collect(first + second + third); + + public IntrinsicInvokeExcludedConstructorTarget(object first, object second, object third, object fourth, + object fifth, object sixth, object seventh, object eighth, object ninth) => + Value = Collect(ninth); + + private static T Collect(T value) + { + GC.Collect(); + return value; + } + } + + internal struct IntrinsicInvokeStructConstructorTarget + { + public object Value; + + public IntrinsicInvokeStructConstructorTarget(int value) + { + Value = value; + GC.Collect(); + } + } + + internal sealed class IntrinsicInvokeConstructorValidationTarget + { + public IntrinsicInvokeConstructorValidationTarget(IIntrinsicInvokeReference value) + { + GC.Collect(); + GC.KeepAlive(value); + } + + public IntrinsicInvokeConstructorValidationTarget(int value) + { + GC.Collect(); + GC.KeepAlive(value); + } + + public IntrinsicInvokeConstructorValidationTarget(IntrinsicInvokeInt32Enum value) + { + GC.Collect(); + GC.KeepAlive(value); + } + } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs index f5313bde579844..30efd2a744bf49 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInvokerTests.cs @@ -22,6 +22,32 @@ public override object Invoke(ConstructorInfo constructorInfo, object?[]? parame protected override bool IsExceptionWrapped => false; + [Theory] + [InlineData(1)] + [InlineData(8)] + public void SharedThunk_CachedInvokerPromotes(int argumentCount) + { + Type[] parameterTypes = new Type[argumentCount]; + Array.Fill(parameterTypes, typeof(CachedInvokerArgument)); + ConstructorInfo constructor = typeof(CachedInvokerTarget).GetConstructor(parameterTypes)!; + ConstructorInvoker invoker = ConstructorInvoker.Create(constructor); + var argument = new CachedInvokerArgument(); + object?[] arguments = new object?[argumentCount]; + Array.Fill(arguments, argument); + + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + { + var result = (CachedInvokerTarget)(argumentCount == 1 ? invoker.Invoke(argument) : invoker.Invoke(arguments.AsSpan())); + Assert.Same(argument, result.Value); + if (i == 0 || i == IntrinsicInvokeSelectionAssertions.SpecializationThreshold - 1) + { + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); + } + } + + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + } + [Fact] public void Args_0() { @@ -227,5 +253,43 @@ public TestClassThrowsOnCreate(string arg1) => public TestClassThrowsOnCreate(string arg1, string arg2, string arg3, string arg4, string arg5) => throw new InvalidOperationException(); } + + private sealed class CachedInvokerArgument + { + private bool _collected; + + internal void CollectOnce() + { + if (!_collected) + { + GC.Collect(); + _collected = true; + } + } + } + + private sealed class CachedInvokerTarget + { + internal CachedInvokerArgument Value { get; } + + public CachedInvokerTarget(CachedInvokerArgument value) + { + value.CollectOnce(); + Value = value; + } + + public CachedInvokerTarget(CachedInvokerArgument first, CachedInvokerArgument second, + CachedInvokerArgument third, CachedInvokerArgument fourth, CachedInvokerArgument fifth, + CachedInvokerArgument sixth, CachedInvokerArgument seventh, CachedInvokerArgument eighth) : this(first) + { + Assert.Same(first, second); + Assert.Same(first, third); + Assert.Same(first, fourth); + Assert.Same(first, fifth); + Assert.Same(first, sixth); + Assert.Same(first, seventh); + Assert.Same(first, eighth); + } + } } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index 20585f722e1c94..8c0aedd156e21d 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -2,9 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.Reflection.Tests @@ -21,777 +25,2473 @@ public sealed class MethodInfoTests : MethodCommonTests protected override bool SupportsMissing => false; - [Fact] - public void CreateDelegate_PublicMethod() + public static IEnumerable Invoke_ReturnValueAcrossTiers_TestData() + { + yield return new object[] { typeof(bool), true }; + yield return new object[] { typeof(byte), (byte)42 }; + yield return new object[] { typeof(sbyte), (sbyte)-42 }; + yield return new object[] { typeof(char), 'x' }; + yield return new object[] { typeof(short), (short)-1234 }; + yield return new object[] { typeof(ushort), (ushort)1234 }; + yield return new object[] { typeof(int), -12345 }; + yield return new object[] { typeof(uint), 12345u }; + yield return new object[] { typeof(long), -1234567890123L }; + yield return new object[] { typeof(ulong), 1234567890123UL }; + yield return new object[] { typeof(float), 12.5f }; + yield return new object[] { typeof(double), -25.5 }; + yield return new object[] { typeof(nint), (nint)12345 }; + yield return new object[] { typeof(nuint), (nuint)54321 }; + yield return new object[] { typeof(string), "returned value" }; + yield return new object[] { typeof(object), new object() }; + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR))] + [MemberData(nameof(Invoke_ReturnValueAcrossTiers_TestData))] + public void Invoke_ReturnValueAcrossTiers(Type returnType, object expected) + { + Type target = typeof(ReturnValueTarget<>).MakeGenericType(returnType); + target.GetField(nameof(ReturnValueTarget.Value)).SetValue(null, expected); + MethodInfo method = target.GetMethod(nameof(ReturnValueTarget.GetValue)); + MethodInvoker invoker = MethodInvoker.Create(method); + + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + { + Assert.Equal(expected, method.Invoke(null, null)); + Assert.Equal(expected, invoker.Invoke(null)); + } + } + + public static class ReturnValueTarget { - Type typeTestClass = typeof(MI_BaseClass); + public static T Value; - MI_BaseClass baseClass = (MI_BaseClass)Activator.CreateInstance(typeTestClass); - MethodInfo virtualMethodInfo = GetMethod(typeTestClass, nameof(MI_BaseClass.VirtualMethod)); - MethodInfo privateInstanceMethodInfo = GetMethod(typeTestClass, "PrivateInstanceMethod"); - MethodInfo publicStaticMethodInfo = GetMethod(typeTestClass, nameof(MI_BaseClass.PublicStaticMethod)); + public static T GetValue() => Value; + } - Delegate methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_TC_Int)); - object returnValue = ((Delegate_TC_Int)methodDelegate).DynamicInvoke(new object[] { baseClass }); - Assert.Equal(baseClass.VirtualMethod(), returnValue); + public static IEnumerable Invoke_InstanceReferenceVoid_SharedThunk_TestData() + { + yield return new object[] { nameof(IntrinsicInvokeReferenceTarget.Void0), Array.Empty(), -1 }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void1), + new object?[] { new IntrinsicInvokeReference(1) }, + 0 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void2), + new object?[] { new object[] { "array" }, (Action)(() => { }) }, + 1 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void3), + new object?[] { Task.FromResult("task"), new IntrinsicInvokeReference(3), new string[] { "array" } }, + 2 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Void4), + new object?[] { new object(), new IntrinsicInvokeReference(4), (Action)(() => { }), Task.FromResult(4) }, + 3 + }; + } - Delegate genMethodDelegate = virtualMethodInfo.CreateDelegate(); - object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { baseClass }); - Assert.Equal(returnValue, genReturnValue); + [Theory] + [MemberData(nameof(Invoke_InstanceReferenceVoid_SharedThunk_TestData))] + public void Invoke_InstanceReferenceVoid_SharedThunk(string methodName, object?[] arguments, int retainedArgument) + { + var target = new IntrinsicInvokeReferenceTarget(); + MethodInfo method = typeof(IntrinsicInvokeReferenceTarget).GetMethod(methodName)!; - methodDelegate = privateInstanceMethodInfo.CreateDelegate(typeof(Delegate_TC_Int)); - returnValue = ((Delegate_TC_Int)methodDelegate).DynamicInvoke(new object[] { baseClass }); - Assert.Equal(21, returnValue); + Assert.Null(method.Invoke(target, arguments)); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(1, target.CallCount); + Assert.Same(retainedArgument < 0 ? target.Sentinel : arguments[retainedArgument], target.LastValue); + } - genMethodDelegate = privateInstanceMethodInfo.CreateDelegate(); - genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { baseClass }); - Assert.Equal(returnValue, genReturnValue); + public static IEnumerable Invoke_InstanceReferenceReturn_SharedThunk_TestData() + { + yield return new object[] { nameof(IntrinsicInvokeReferenceTarget.Return0), Array.Empty(), -1 }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return1), + new object?[] { new IntrinsicInvokeReference(1) }, + 0 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return2), + new object?[] { new object[] { "array" }, (Action)(() => { }) }, + 0 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return3), + new object?[] { Task.FromResult("task"), (Func)(() => "delegate"), new IntrinsicInvokeReference(3) }, + 1 + }; + yield return new object[] + { + nameof(IntrinsicInvokeReferenceTarget.Return4), + new object?[] { Task.FromResult("task"), new object[] { "array" }, new IntrinsicInvokeReference(4), (Action)(() => { }) }, + 0 + }; + } - methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_Void_Int), baseClass); - returnValue = ((Delegate_Void_Int)methodDelegate).DynamicInvoke(null); - Assert.Equal(baseClass.VirtualMethod(), returnValue); + [Theory] + [MemberData(nameof(Invoke_InstanceReferenceReturn_SharedThunk_TestData))] + public void Invoke_InstanceReferenceReturn_SharedThunk(string methodName, object?[] arguments, int returnedArgument) + { + var target = new IntrinsicInvokeReferenceTarget(); + MethodInfo method = typeof(IntrinsicInvokeReferenceTarget).GetMethod(methodName)!; - genMethodDelegate = virtualMethodInfo.CreateDelegate(baseClass); - genReturnValue = genMethodDelegate.DynamicInvoke(null); - Assert.Equal(returnValue, genReturnValue); + object? result = method.Invoke(target, arguments); - methodDelegate = publicStaticMethodInfo.CreateDelegate(typeof(Delegate_Str_Str)); - returnValue = ((Delegate_Str_Str)methodDelegate).DynamicInvoke(new object[] { "85" }); - Assert.Equal("85", returnValue); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Same(returnedArgument < 0 ? target.Sentinel : arguments[returnedArgument], result); + } - genMethodDelegate = publicStaticMethodInfo.CreateDelegate(); - genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { "85" }); - Assert.Equal(returnValue, genReturnValue); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Boolean() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(bool)), true); - methodDelegate = publicStaticMethodInfo.CreateDelegate(typeof(Delegate_Void_Str), "93"); - returnValue = ((Delegate_Void_Str)methodDelegate).DynamicInvoke(null); - Assert.Equal("93", returnValue); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Byte() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(byte)), (byte)42); - genMethodDelegate = publicStaticMethodInfo.CreateDelegate("93"); - genReturnValue = genMethodDelegate.DynamicInvoke(null); - Assert.Equal(returnValue, genReturnValue); - } + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_SByte() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(sbyte)), (sbyte)-42); [Fact] - public void CreateDelegate_InheritedMethod() - { - Type typeTestClass = typeof(MI_BaseClass); - Type TestSubClassType = typeof(MI_SubClass); + public void Invoke_InstancePrimitiveReturn_SharedThunk_Char() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(char)), 'x'); - MI_SubClass testSubClass = (MI_SubClass)Activator.CreateInstance(TestSubClassType); - MI_BaseClass testClass = (MI_BaseClass)Activator.CreateInstance(typeTestClass); - MethodInfo virtualMethodInfo = GetMethod(typeTestClass, nameof(MI_BaseClass.VirtualMethod)); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Int16() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(short)), (short)-1234); - Delegate methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_TC_Int)); - object returnValue = ((Delegate_TC_Int)methodDelegate).DynamicInvoke(new object[] { testSubClass }); - Assert.Equal(testSubClass.VirtualMethod(), returnValue); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt16() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(ushort)), (ushort)1234); - Delegate genMethodDelegate = virtualMethodInfo.CreateDelegate(); - object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { testSubClass }); - Assert.Equal(returnValue, genReturnValue); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Int32() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(int)), -12345); - methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_Void_Int), testSubClass); - returnValue = ((Delegate_Void_Int)methodDelegate).DynamicInvoke(); - Assert.Equal(testSubClass.VirtualMethod(), returnValue); + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt32() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(uint)), 12345u); - genMethodDelegate = virtualMethodInfo.CreateDelegate(testSubClass); - genReturnValue = genMethodDelegate.DynamicInvoke(); - Assert.Equal(returnValue, genReturnValue); - } + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Int64() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(long)), -1234567890123L); [Fact] - public void CreateDelegate_GenericMethod() + public void Invoke_InstancePrimitiveReturn_SharedThunk_UInt64() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(ulong)), 1234567890123UL); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Single() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(float)), 12.5f); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_Double() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(double)), -25.5); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_IntPtr() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(nint)), (nint)12345); + + [Fact] + public void Invoke_InstancePrimitiveReturn_SharedThunk_UIntPtr() => + Invoke_InstancePrimitiveReturn_SharedThunk(typeof(IntrinsicInvokePrimitiveReturnTarget<>).MakeGenericType(typeof(nuint)), (nuint)54321); + + private static void Invoke_InstancePrimitiveReturn_SharedThunk( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, + object expected) { - Type typeGenericClassString = typeof(MI_GenericClass); + object target = Activator.CreateInstance(targetType)!; + targetType.GetProperty(nameof(IntrinsicInvokePrimitiveReturnTarget.Value))!.SetValue(target, expected); + MethodInfo method = targetType.GetMethod(nameof(IntrinsicInvokePrimitiveReturnTarget.GetValue))!; - MI_GenericClass genericClass = (MI_GenericClass)Activator.CreateInstance(typeGenericClassString); + object? result = method.Invoke(target, null); - MethodInfo miMethod1String = GetMethod(typeGenericClassString, nameof(MI_GenericClass.GenericMethod1)); - MethodInfo miMethod2String = GetMethod(typeGenericClassString, nameof(MI_GenericClass.GenericMethod3)); - MethodInfo miMethod2IntGeneric = miMethod2String.MakeGenericMethod(new Type[] { typeof(int) }); - MethodInfo miMethod2StringGeneric = miMethod2String.MakeGenericMethod(new Type[] { typeof(string) }); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.NotNull(result); + Assert.Equal(expected.GetType(), result.GetType()); + Assert.Equal(expected, result); + } - Delegate methodDelegate = miMethod1String.CreateDelegate(typeof(Delegate_GC_T_T)); - object returnValue = ((Delegate_GC_T_T)methodDelegate).DynamicInvoke(new object[] { genericClass, "TestGeneric" }); - Assert.Equal(genericClass.GenericMethod1("TestGeneric"), returnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Boolean() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(bool)), true); - Delegate genMethodDelegate = miMethod1String.CreateDelegate>(); - object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { genericClass, "TestGeneric" }); - Assert.Equal(returnValue, genReturnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Byte() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(byte)), (byte)42); - methodDelegate = miMethod1String.CreateDelegate(typeof(Delegate_T_T), genericClass); - returnValue = ((Delegate_T_T)methodDelegate).DynamicInvoke(new object[] { "TestGeneric" }); - Assert.Equal(genericClass.GenericMethod1("TestGeneric"), returnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_SByte() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(sbyte)), (sbyte)-42); - genMethodDelegate = miMethod1String.CreateDelegate>(genericClass); - genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { "TestGeneric" }); - Assert.Equal(returnValue, genReturnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Char() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(char)), 'x'); - methodDelegate = miMethod2IntGeneric.CreateDelegate(typeof(Delegate_T_T)); - returnValue = ((Delegate_T_T)methodDelegate).DynamicInvoke(new object[] { 58 }); - Assert.Equal(58, returnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int16() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(short)), (short)-1234); - genMethodDelegate = miMethod2IntGeneric.CreateDelegate>(); - genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { 58 }); - Assert.Equal(returnValue, genReturnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt16() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(ushort)), (ushort)1234); - methodDelegate = miMethod2StringGeneric.CreateDelegate(typeof(Delegate_Void_T), "firstArg"); - returnValue = ((Delegate_Void_T)methodDelegate).DynamicInvoke(); - Assert.Equal("firstArg", returnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int32() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(int)), -12345); - genMethodDelegate = miMethod2StringGeneric.CreateDelegate>("firstArg"); - genReturnValue = genMethodDelegate.DynamicInvoke(); - Assert.Equal(returnValue, genReturnValue); - } + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt32() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(uint)), 12345u); [Fact] - public void CreateDelegate_ValueTypeParameters() - { - MethodInfo miPublicStructMethod = GetMethod(typeof(MI_BaseClass), nameof(MI_BaseClass.PublicStructMethod)); - MI_BaseClass testClass = new MI_BaseClass(); + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int64() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(long)), -1234567890123L); - Delegate methodDelegate = miPublicStructMethod.CreateDelegate(typeof(Delegate_DateTime_Str)); - object returnValue = ((Delegate_DateTime_Str)methodDelegate).DynamicInvoke(new object[] { testClass, null }); - Assert.Equal(testClass.PublicStructMethod(new DateTime()), returnValue); + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt64() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(ulong)), 1234567890123UL); - Delegate genMethodDelegate = miPublicStructMethod.CreateDelegate(); - object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { testClass, null }); - Assert.Equal(returnValue, genReturnValue); - } + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Single() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(float)), 12.5f); - private interface IStaticInterface + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Double() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(double)), -25.5); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_IntPtr() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(nint)), (nint)12345); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UIntPtr() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(nuint)), (nuint)54321); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_ByteEnum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeByteEnum)), IntrinsicInvokeByteEnum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_SByteEnum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeSByteEnum)), IntrinsicInvokeSByteEnum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int16Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt16Enum)), IntrinsicInvokeInt16Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt16Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt16Enum)), IntrinsicInvokeUInt16Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int32Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt32Enum)), IntrinsicInvokeInt32Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt32Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt32Enum)), IntrinsicInvokeUInt32Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_Int64Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeInt64Enum)), IntrinsicInvokeInt64Enum.Value); + + [Fact] + public void Invoke_InstancePrimitiveArgument_SharedThunk_UInt64Enum() => + Invoke_InstancePrimitiveArgument_SharedThunk(typeof(IntrinsicInvokePrimitiveArgumentTarget<>).MakeGenericType(typeof(IntrinsicInvokeUInt64Enum)), IntrinsicInvokeUInt64Enum.Value); + + private static void Invoke_InstancePrimitiveArgument_SharedThunk( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | + DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields)] Type targetType, + object value) { - public static virtual string? StaticVirtual(string? s) => s; + object target = Activator.CreateInstance(targetType)!; + MethodInfo method = targetType.GetMethod(nameof(IntrinsicInvokePrimitiveArgumentTarget.SetValue))!; + + Assert.Null(method.Invoke(target, new object?[] { value })); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + object? actual = targetType.GetField(nameof(IntrinsicInvokePrimitiveArgumentTarget.Value))!.GetValue(target); + Assert.NotNull(actual); + Assert.Equal(value.GetType(), actual.GetType()); + Assert.Equal(value, actual); } [Fact] - public void CreateDelegate_StaticVirtual() + public void Invoke_InstanceTwoReferencesReturningInt_SharedThunk() { - MethodInfo miStaticVirtual = GetMethod(typeof(IStaticInterface), nameof(IStaticInterface.StaticVirtual)); - const string testString = "test"; + var target = new IntrinsicInvokeInstancePatternTarget(); + var reference = new IntrinsicInvokeReference(40); + object[] array = { "first", "second" }; + MethodInfo method = typeof(IntrinsicInvokeInstancePatternTarget).GetMethod( + nameof(IntrinsicInvokeInstancePatternTarget.Sum))!; - Func methodDelegate = miStaticVirtual.CreateDelegate>(); - string? returnValue = methodDelegate(testString); - Assert.Equal(testString, returnValue); + Assert.Equal(42, method.Invoke(target, new object?[] { reference, array })); + IntrinsicInvokeSelectionAssertions.AssertShared(method); } - [Theory] - [InlineData(typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), null, typeof(ArgumentNullException))] - [InlineData(typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_Void_Int), typeof(ArgumentException))] - public void CreateDelegate_Invalid(Type type, string name, Type? delegateType, Type exceptionType) + [Fact] + public void Invoke_InstanceFloatFloatFloatInt_SharedThunk() { - MethodInfo methodInfo = GetMethod(type, name); - Assert.Throws(exceptionType, () => methodInfo.CreateDelegate(delegateType)); + var target = new IntrinsicInvokeInstancePatternTarget(); + MethodInfo method = typeof(IntrinsicInvokeInstancePatternTarget).GetMethod( + nameof(IntrinsicInvokeInstancePatternTarget.SetVector))!; + + Assert.Null(method.Invoke(target, new object?[] { 1.25f, 2.5f, 3.75f, 4 })); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(1.25f, target.X); + Assert.Equal(2.5f, target.Y); + Assert.Equal(3.75f, target.Z); + Assert.Equal(4, target.W); } - public static IEnumerable CreateDelegate_Target_Invalid_TestData() + public static IEnumerable Invoke_StaticIntReturningReference_SharedThunk_TestData() { - yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), null, new MI_BaseClass(), typeof(ArgumentNullException) }; // DelegateType is null - yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_TC_Int), new MI_BaseClass(), typeof(ArgumentException) }; // DelegateType is incorrect - yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_Void_Int), new DummyClass(), typeof(ArgumentException) }; // Target is incorrect - yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_Void_Str), new DummyClass(), typeof(ArgumentException) }; // Target is incorrect + yield return new object[] + { + typeof(IntrinsicInvokeStaticIntReturnTarget), + nameof(IntrinsicInvokeStaticIntReturnTarget.ReturnString), + "42" + }; + yield return new object[] + { + typeof(IntrinsicInvokeGenericStaticIntReturnTarget), + nameof(IntrinsicInvokeGenericStaticIntReturnTarget.ReturnComparable), + 42 + }; } [Theory] - [MemberData(nameof(CreateDelegate_Target_Invalid_TestData))] - public void CreateDelegate_Target_Invalid(Type type, string name, Type delegateType, object target, Type exceptionType) + [MemberData(nameof(Invoke_StaticIntReturningReference_SharedThunk_TestData))] + public void Invoke_StaticIntReturningReference_SharedThunk(Type declaringType, string methodName, object expected) { - MethodInfo methodInfo = GetMethod(type, name); - Assert.Throws(exceptionType, () => methodInfo.CreateDelegate(delegateType, target)); + MethodInfo method = declaringType.GetMethod(methodName)!; + object? result = method.Invoke(null, new object?[] { 42 }); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(expected, result); } - [Theory] - [InlineData(typeof(Int32Attr), "[System.Reflection.Tests.Int32Attr((Int32)77, name = \"Int32AttrSimple\")]")] - [InlineData(typeof(Int64Attr), "[System.Reflection.Tests.Int64Attr((Int64)77, name = \"Int64AttrSimple\")]")] - [InlineData(typeof(StringAttr), "[System.Reflection.Tests.StringAttr(\"hello\", name = \"StringAttrSimple\")]")] - [InlineData(typeof(EnumAttr), "[System.Reflection.Tests.EnumAttr((System.Reflection.Tests.PublicEnum)1, name = \"EnumAttrSimple\")]")] - [InlineData(typeof(TypeAttr), "[System.Reflection.Tests.TypeAttr(typeof(System.Object), name = \"TypeAttrSimple\")]")] - [InlineData(typeof(Attr), "[System.Reflection.Tests.Attr((Int32)77, name = \"AttrSimple\")]")] - public void CustomAttributes(Type type, string expectedToString) + [Fact] + public void Invoke_StaticGenericIntReturningReference_SharedThunk() { - MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "MethodWithAttributes"); - CustomAttributeData attributeData = methodInfo.CustomAttributes.First(attribute => attribute.AttributeType.Equals(type)); - Assert.Equal(expectedToString, attributeData.ToString()); + MethodInfo method = typeof(IntrinsicInvokeStaticIntReturnTarget) + .GetMethod(nameof(IntrinsicInvokeStaticIntReturnTarget.ReturnGeneric))!.MakeGenericMethod(typeof(int)); + + object? result = method.Invoke(null, new object?[] { 42 }); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(typeof(int), result); } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), true)] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt), false)] - [InlineData(typeof(MI_SubClass), nameof(MI_GenericClass.GenericMethod1), typeof(MI_GenericClass<>), nameof(MI_GenericClass.GenericMethod1), false)] - [InlineData(typeof(MI_SubClass), nameof(MI_GenericClass.GenericMethod2), typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2), false)] - public void EqualsTest(Type type1, string name1, Type type2, string name2, bool expected) + public static IEnumerable Invoke_StaticReferenceOutReference_SharedThunk_TestData() { - MethodInfo methodInfo1 = GetMethod(type1, name1); - MethodInfo methodInfo2 = GetMethod(type2, name2); - Assert.Equal(expected, methodInfo1.Equals(methodInfo2)); + var falseInput = new IntrinsicInvokeReference(1); + yield return new object[] + { + nameof(IntrinsicInvokeByRefTarget.ReturnFalse), + new object?[] { falseInput, null }, + false, + falseInput + }; + + yield return new object[] + { + nameof(IntrinsicInvokeByRefTarget.ReturnNull), + new object?[] { new object[] { "input" }, (Action)(() => { }) }, + true, + null + }; + + Task trueInput = Task.FromResult("input"); + yield return new object[] + { + nameof(IntrinsicInvokeByRefTarget.ReturnTrue), + new object?[] { trueInput, null }, + true, + trueInput + }; } [Theory] - //Verify two same MethodInfo objects are equal - [InlineData("DummyMethod1", "DummyMethod1", true)] - //Verify two different MethodInfo objects are not equal - [InlineData("DummyMethod1", "DummyMethod2", false)] - public void Equality1(string str1, string str2, bool expected) + [MemberData(nameof(Invoke_StaticReferenceOutReference_SharedThunk_TestData))] + public void Invoke_StaticReferenceOutReference_SharedThunk( + string methodName, + object?[] arguments, + bool expectedResult, + object? expectedOutput) { - MethodInfo mi1 = GetMethod(typeof(MethodInfoTests), str1); - MethodInfo mi2 = GetMethod(typeof(MethodInfoTests), str2); + MethodInfo method = typeof(IntrinsicInvokeByRefTarget).GetMethod(methodName)!; - Assert.Equal(expected, mi1 == mi2); - Assert.NotEqual(expected, mi1 != mi2); + Assert.Equal(expectedResult, method.Invoke(null, arguments)); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Same(expectedOutput, arguments[1]); } - public static IEnumerable TestEqualityMethodData2() + [Fact] + public void Invoke_StaticReferenceOutReference_DoesNotCopyBackAfterException() { - //Verify two different MethodInfo objects with same name from two different classes are not equal - yield return new object[] { typeof(Sample), typeof(SampleG<>), "Method1", "Method1", false }; - //Verify two different MethodInfo objects with same name from two different classes are not equal - yield return new object[] { typeof(Sample), typeof(SampleG), "Method2", "Method2", false }; + MethodInfo method = typeof(IntrinsicInvokeByRefTarget).GetMethod(nameof(IntrinsicInvokeByRefTarget.ThrowAfterWrite))!; + var input = new IntrinsicInvokeReference(42); + var originalOutput = new IntrinsicInvokeReference(-1); + object?[] arguments = { input, originalOutput }; + + TargetInvocationException exception = Assert.Throws(() => method.Invoke(null, arguments)); + + Assert.IsType(exception.InnerException); + Assert.Same(originalOutput, arguments[1]); + IntrinsicInvokeSelectionAssertions.AssertShared(method); } - [Theory] - [MemberData(nameof(TestEqualityMethodData2))] - public void Equality2(Type sample1, Type sample2, string str1, string str2, bool expected) + public static IEnumerable Invoke_StaticReferenceReturn_SharedThunk_TestData() { - MethodInfo mi1 = GetMethod(sample1, str1); - MethodInfo mi2 = GetMethod(sample2, str2); + var reference2 = new IntrinsicInvokeReference(2); + object[] array2 = { "two" }; + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Return2), + new object?[] { reference2, array2 }, + 1 + }; + + Task task3 = Task.FromResult("three"); + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Return3), + new object?[] { task3, (Action)(() => { }), new IntrinsicInvokeReference(3) }, + 0 + }; + + Action callback4 = () => { }; + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Return4), + new object?[] { callback4, new object[] { "four" }, new IntrinsicInvokeReference(4), Task.FromResult(4) }, + 0 + }; + } + + [Theory] + [MemberData(nameof(Invoke_StaticReferenceReturn_SharedThunk_TestData))] + public void Invoke_StaticReferenceReturn_SharedThunk(string methodName, object?[] arguments, int returnedArgument) + { + MethodInfo method = typeof(IntrinsicInvokeStaticReferenceTarget).GetMethod(methodName)!; + + object? result = method.Invoke(null, arguments); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Same(arguments[returnedArgument], result); + } + + public static IEnumerable Invoke_StaticReferenceVoid_SharedThunk_TestData() + { + var tracker2 = new IntrinsicInvokeActionTracker(); + var reference2 = new IntrinsicInvokeReference(2); + Action callback2 = reference => + { + Assert.Same(reference2, reference); + tracker2.Callback(); + }; + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Void2), + new object?[] { callback2, reference2 }, + tracker2 + }; + + var tracker3 = new IntrinsicInvokeActionTracker(); + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Void3), + new object?[] { Task.FromResult("three"), tracker3.Callback, new IntrinsicInvokeReference(3) }, + tracker3 + }; + + var tracker4 = new IntrinsicInvokeActionTracker(); + yield return new object[] + { + nameof(IntrinsicInvokeStaticReferenceTarget.Void4), + new object?[] { new object[] { "four" }, new IntrinsicInvokeReference(4), tracker4.Callback, Task.FromResult(4) }, + tracker4 + }; + } + + [Theory] + [MemberData(nameof(Invoke_StaticReferenceVoid_SharedThunk_TestData))] + public void Invoke_StaticReferenceVoid_SharedThunk( + string methodName, + object?[] arguments, + object trackerObject) + { + var tracker = (IntrinsicInvokeActionTracker)trackerObject; + MethodInfo method = typeof(IntrinsicInvokeStaticReferenceTarget).GetMethod(methodName)!; + + Assert.Null(method.Invoke(null, arguments)); + + IntrinsicInvokeSelectionAssertions.AssertShared(method); + Assert.Equal(1, tracker.CallCount); + } + + public static IEnumerable Invoke_VirtualDispatch_SharedThunk_TestData() + { + yield return new object[] + { + typeof(IntrinsicInvokeVirtualDispatchBase), + nameof(IntrinsicInvokeVirtualDispatchBase.Dispatch), + new object[] + { + new IntrinsicInvokeVirtualDispatchA(), + new IntrinsicInvokeVirtualDispatchB(), + new IntrinsicInvokeVirtualDispatchA(), + new IntrinsicInvokeVirtualDispatchB() + }, + new string[] { "virtual-a", "virtual-b", "virtual-a", "virtual-b" } + }; + yield return new object[] + { + typeof(IntrinsicInvokeAbstractDispatchBase), + nameof(IntrinsicInvokeAbstractDispatchBase.Dispatch), + new object[] + { + new IntrinsicInvokeAbstractDispatchA(), + new IntrinsicInvokeAbstractDispatchB(), + new IntrinsicInvokeAbstractDispatchA(), + new IntrinsicInvokeAbstractDispatchB() + }, + new string[] { "abstract-a", "abstract-b", "abstract-a", "abstract-b" } + }; + yield return new object[] + { + typeof(IIntrinsicInvokeInterfaceDispatch), + nameof(IIntrinsicInvokeInterfaceDispatch.Dispatch), + new object[] + { + new IntrinsicInvokeInterfaceDispatchA(), + new IntrinsicInvokeInterfaceDispatchB(), + new IntrinsicInvokeInterfaceDispatchA(), + new IntrinsicInvokeInterfaceDispatchB() + }, + new string[] { "interface-a", "interface-b", "interface-a", "interface-b" } + }; + yield return new object[] + { + typeof(IIntrinsicInvokeDefaultInterfaceDispatch), + nameof(IIntrinsicInvokeDefaultInterfaceDispatch.Dispatch), + new object[] + { + new IntrinsicInvokeDefaultInterfaceDispatch(), + new IntrinsicInvokeDefaultInterfaceOverride(), + new IntrinsicInvokeDefaultInterfaceDispatch(), + new IntrinsicInvokeDefaultInterfaceOverride() + }, + new string[] { "default", "override", "default", "override" } + }; + } + + [Theory] + [MemberData(nameof(Invoke_VirtualDispatch_SharedThunk_TestData))] + public void Invoke_VirtualDispatch_SharedThunk( + Type declaringType, + string methodName, + object[] receivers, + string[] expected) + { + MethodInfo method = declaringType.GetMethod(methodName)!; + AssertVirtualDispatchSharedThunk(method, receivers, expected); + } + + [Fact] + public void Invoke_GenericVirtualDispatch_SharedThunk() + { + MethodInfo method = typeof(IntrinsicInvokeGenericVirtualDispatch) + .GetMethod(nameof(IntrinsicInvokeGenericVirtualDispatch.Dispatch))!.MakeGenericMethod(typeof(int)); + + AssertVirtualDispatchSharedThunk(method, + new object[] { new IntrinsicInvokeGenericVirtualA(), new IntrinsicInvokeGenericVirtualB() }, + new string[] { "Int32-generic-a", "Int32-generic-b" }); + } + + [Fact] + public void Invoke_GenericInterfaceDispatch_SharedThunk() + { + MethodInfo method = typeof(IIntrinsicInvokeGenericInterfaceDispatch) + .GetMethod(nameof(IIntrinsicInvokeGenericInterfaceDispatch.Dispatch))!.MakeGenericMethod(typeof(int)); + + AssertVirtualDispatchSharedThunk(method, + new object[] { new IntrinsicInvokeGenericInterfaceA(), new IntrinsicInvokeGenericInterfaceB() }, + new string[] { "Int32-interface-a", "Int32-interface-b" }); + } + + private static void AssertVirtualDispatchSharedThunk(MethodInfo method, object[] receivers, string[] expected) + { + object argument = new object(); + + for (int i = 0; i < receivers.Length; i++) + { + Assert.Equal(expected[i], method.Invoke(receivers[i], new object?[] { argument })); + if (i == 0) + { + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.IsReflectionEmitSupported))] + public void Invoke_EmittedVirtualMethod_SharedThunk() + { + AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName(nameof(Invoke_EmittedVirtualMethod_SharedThunk)), AssemblyBuilderAccess.RunAndCollect); + TypeBuilder typeBuilder = assembly.DefineDynamicModule("Module").DefineType("Target", TypeAttributes.Public); + const string MethodName = "GetValue"; + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + MethodName, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot, + typeof(int), Type.EmptyTypes); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldc_I4, 42); + il.Emit(OpCodes.Ret); + Type targetType = typeBuilder.CreateType()!; + object target = Activator.CreateInstance(targetType)!; + MethodInfo method = targetType.GetMethod(MethodName)!; + + Assert.Equal(42, method.Invoke(target, null)); + IntrinsicInvokeSelectionAssertions.AssertShared(method); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void DynamicInvoke_DelegateInvokeMethod_SharedThunk(int callbackKind) + { + var payload = new IntrinsicInvokeDelegatePayload(); + Delegate callback; + string[] expectedLog; + + switch (callbackKind) + { + case 0: + callback = new IntrinsicInvokeStaticDynamicCallback(IntrinsicInvokeDelegateCallbacks.Static); + expectedLog = new string[] { "static" }; + break; + case 1: + var instanceTarget = new IntrinsicInvokeDelegateCallbacks("instance"); + callback = new IntrinsicInvokeInstanceDynamicCallback(instanceTarget.Instance); + expectedLog = new string[] { "instance" }; + break; + case 2: + var multicastTarget = new IntrinsicInvokeDelegateCallbacks("instance"); + IntrinsicInvokeMulticastDynamicCallback first = IntrinsicInvokeDelegateCallbacks.MulticastStatic; + IntrinsicInvokeMulticastDynamicCallback second = multicastTarget.MulticastInstance; + callback = first + second; + expectedLog = new string[] { "static", "instance" }; + break; + default: + throw new ArgumentOutOfRangeException(nameof(callbackKind)); + } + + MethodInfo invokeMethod = callback.GetType().GetMethod(nameof(Action.Invoke))!; + object? result = callback.DynamicInvoke(new object?[] { payload }); + + Assert.Same(payload, result); + Assert.Equal(expectedLog, payload.Log.ToArray()); + IntrinsicInvokeSelectionAssertions.AssertShared(invokeMethod); + } + + public static IEnumerable Invoke_ExcludedShapes_Fallback_TestData() + { + DateTime date = new DateTime(2026, 9, 10); + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.DateTimeArgument), + null, + new object?[] { date }, + 10, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.DateTimeResult), + null, + Array.Empty(), + date, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.NullableArgument), + null, + new object?[] { (int?)42 }, + 42, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.NullableResult), + null, + Array.Empty(), + 43, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.ValueTaskArgument), + null, + new object?[] { new ValueTask(44) }, + 44, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.ValueTaskResult), + null, + Array.Empty(), + new ValueTask(45), + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.CancellationTokenArgument), + null, + new object?[] { new CancellationToken(canceled: true) }, + true, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.CancellationTokenResult), + null, + Array.Empty(), + new CancellationToken(canceled: true), + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.ByRefValue), + null, + new object?[] { 46 }, + true, + 0, + 47 + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.TwoPrimitiveArguments), + null, + new object?[] { 1, 2 }, + 3, + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeExcludedMethodTarget), + nameof(IntrinsicInvokeExcludedMethodTarget.FiveReferenceArguments), + null, + new object?[] { "one", "two", "three", "four", "five" }, + "five", + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeInstancePatternTarget), + nameof(IntrinsicInvokeInstancePatternTarget.FiveReferenceArguments), + new IntrinsicInvokeInstancePatternTarget(), + new object?[] { "one", "two", "three", "four", "five" }, + "five", + -1, + null + }; + yield return new object[] + { + typeof(IntrinsicInvokeStructReceiver), + nameof(IntrinsicInvokeStructReceiver.GetValue), + new IntrinsicInvokeStructReceiver(48), + Array.Empty(), + 48, + -1, + null + }; + yield return new object[] + { + typeof(IIntrinsicInvokeStructReceiver), + nameof(IIntrinsicInvokeStructReceiver.GetValue), + new IntrinsicInvokeStructReceiver(49), + Array.Empty(), + 49, + -1, + null + }; + } + + [Theory] + [MemberData(nameof(Invoke_ExcludedShapes_Fallback_TestData))] + public void Invoke_ExcludedShapes_Fallback( + Type declaringType, + string methodName, + object? target, + object?[] arguments, + object expectedResult, + int copyBackIndex, + object? expectedCopyBack) + { + MethodInfo method = declaringType.GetMethod(methodName)!; + + object? result = method.Invoke(target, arguments); + + Assert.Equal(expectedResult, result); + if (copyBackIndex >= 0) + { + Assert.Equal(expectedCopyBack, arguments[copyBackIndex]); + } + + IntrinsicInvokeSelectionAssertions.AssertFallback(method); + } + + public static IEnumerable Invoke_EnumResult_Fallback_TestData() + { + yield return new object[] + { + nameof(IntrinsicInvokeEnumResultTarget.InstanceResult), + new IntrinsicInvokeEnumResultTarget(), + IntrinsicInvokeInt32Enum.Value + }; + yield return new object[] + { + nameof(IntrinsicInvokeEnumResultTarget.StaticResult), + null, + IntrinsicInvokeInt64Enum.Value + }; + } + + [Theory] + [MemberData(nameof(Invoke_EnumResult_Fallback_TestData))] + public void Invoke_EnumResult_FallbackPreservesDeclaredType(string methodName, object? target, object expected) + { + MethodInfo method = typeof(IntrinsicInvokeEnumResultTarget).GetMethod(methodName)!; + + object? result = method.Invoke(target, null); + + Assert.NotNull(result); + Assert.Equal(method.ReturnType, result.GetType()); + Assert.Equal(expected, result); + IntrinsicInvokeSelectionAssertions.AssertFallback(method); + } + + [Fact] + public void Invoke_SharedThunk_UsesNormalArgumentValidation() + { + var target = new IntrinsicInvokeArgumentValidationTarget(); + + MethodInfo referenceMethod = typeof(IntrinsicInvokeArgumentValidationTarget).GetMethod( + nameof(IntrinsicInvokeArgumentValidationTarget.Reference))!; + Assert.Null(referenceMethod.Invoke(target, new object?[] { new IntrinsicInvokeReference(1) })); + IntrinsicInvokeSelectionAssertions.AssertShared(referenceMethod); + Assert.Throws(() => referenceMethod.Invoke(target, new object?[] { new object() })); + + MethodInfo primitiveMethod = typeof(IntrinsicInvokeArgumentValidationTarget).GetMethod( + nameof(IntrinsicInvokeArgumentValidationTarget.Primitive))!; + Assert.Null(primitiveMethod.Invoke(target, new object?[] { 1 })); + IntrinsicInvokeSelectionAssertions.AssertShared(primitiveMethod); + Assert.Throws(() => primitiveMethod.Invoke(target, new object?[] { 1L })); + + MethodInfo enumMethod = typeof(IntrinsicInvokeArgumentValidationTarget).GetMethod( + nameof(IntrinsicInvokeArgumentValidationTarget.Enum))!; + Assert.Null(enumMethod.Invoke(target, new object?[] { IntrinsicInvokeInt32Enum.Value })); + IntrinsicInvokeSelectionAssertions.AssertShared(enumMethod); + Assert.Throws(() => enumMethod.Invoke(target, new object?[] { IntrinsicInvokeInt64Enum.Value })); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsCoreCLR), nameof(PlatformDetection.IsReflectionEmitSupported))] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Invoke_CollectibleMethodCanUnload(bool useMethodInvoker, bool referenceArgument) + { + WeakReference assembly = CreateAndInvokeCollectibleMethod(useMethodInvoker, referenceArgument); + for (int i = 0; i < 10 && assembly.IsAlive; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + Assert.False(assembly.IsAlive); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static WeakReference CreateAndInvokeCollectibleMethod(bool useMethodInvoker, bool referenceArgument) + { + AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName(nameof(CreateAndInvokeCollectibleMethod)), AssemblyBuilderAccess.RunAndCollect); + ModuleBuilder module = assembly.DefineDynamicModule("Module"); + TypeBuilder typeBuilder = module.DefineType("Target", TypeAttributes.Public); + Type argumentType = referenceArgument ? typeof(object) : typeof(int); + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + "Echo", MethodAttributes.Public | MethodAttributes.Static, argumentType, new[] { argumentType }); + ILGenerator il = methodBuilder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ret); + Type type = typeBuilder.CreateType(); + MethodInfo method = type.GetMethod("Echo"); + MethodInvoker? invoker = useMethodInvoker ? MethodInvoker.Create(method) : null; + object expected = referenceArgument ? new object() : 42; + object[] arguments = { expected }; + + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + { + Assert.Equal(expected, invoker is not null ? invoker.Invoke(null, expected) : method.Invoke(null, arguments)); + } + + return new WeakReference(type.Assembly); + } + + [Fact] + public void CreateDelegate_PublicMethod() + { + Type typeTestClass = typeof(MI_BaseClass); + + MI_BaseClass baseClass = (MI_BaseClass)Activator.CreateInstance(typeTestClass); + MethodInfo virtualMethodInfo = GetMethod(typeTestClass, nameof(MI_BaseClass.VirtualMethod)); + MethodInfo privateInstanceMethodInfo = GetMethod(typeTestClass, "PrivateInstanceMethod"); + MethodInfo publicStaticMethodInfo = GetMethod(typeTestClass, nameof(MI_BaseClass.PublicStaticMethod)); + + Delegate methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_TC_Int)); + object returnValue = ((Delegate_TC_Int)methodDelegate).DynamicInvoke(new object[] { baseClass }); + Assert.Equal(baseClass.VirtualMethod(), returnValue); + + Delegate genMethodDelegate = virtualMethodInfo.CreateDelegate(); + object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { baseClass }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = privateInstanceMethodInfo.CreateDelegate(typeof(Delegate_TC_Int)); + returnValue = ((Delegate_TC_Int)methodDelegate).DynamicInvoke(new object[] { baseClass }); + Assert.Equal(21, returnValue); + + genMethodDelegate = privateInstanceMethodInfo.CreateDelegate(); + genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { baseClass }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_Void_Int), baseClass); + returnValue = ((Delegate_Void_Int)methodDelegate).DynamicInvoke(null); + Assert.Equal(baseClass.VirtualMethod(), returnValue); + + genMethodDelegate = virtualMethodInfo.CreateDelegate(baseClass); + genReturnValue = genMethodDelegate.DynamicInvoke(null); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = publicStaticMethodInfo.CreateDelegate(typeof(Delegate_Str_Str)); + returnValue = ((Delegate_Str_Str)methodDelegate).DynamicInvoke(new object[] { "85" }); + Assert.Equal("85", returnValue); + + genMethodDelegate = publicStaticMethodInfo.CreateDelegate(); + genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { "85" }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = publicStaticMethodInfo.CreateDelegate(typeof(Delegate_Void_Str), "93"); + returnValue = ((Delegate_Void_Str)methodDelegate).DynamicInvoke(null); + Assert.Equal("93", returnValue); + + genMethodDelegate = publicStaticMethodInfo.CreateDelegate("93"); + genReturnValue = genMethodDelegate.DynamicInvoke(null); + Assert.Equal(returnValue, genReturnValue); + } + + [Fact] + public void CreateDelegate_InheritedMethod() + { + Type typeTestClass = typeof(MI_BaseClass); + Type TestSubClassType = typeof(MI_SubClass); + + MI_SubClass testSubClass = (MI_SubClass)Activator.CreateInstance(TestSubClassType); + MI_BaseClass testClass = (MI_BaseClass)Activator.CreateInstance(typeTestClass); + MethodInfo virtualMethodInfo = GetMethod(typeTestClass, nameof(MI_BaseClass.VirtualMethod)); + + Delegate methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_TC_Int)); + object returnValue = ((Delegate_TC_Int)methodDelegate).DynamicInvoke(new object[] { testSubClass }); + Assert.Equal(testSubClass.VirtualMethod(), returnValue); + + Delegate genMethodDelegate = virtualMethodInfo.CreateDelegate(); + object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { testSubClass }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = virtualMethodInfo.CreateDelegate(typeof(Delegate_Void_Int), testSubClass); + returnValue = ((Delegate_Void_Int)methodDelegate).DynamicInvoke(); + Assert.Equal(testSubClass.VirtualMethod(), returnValue); + + genMethodDelegate = virtualMethodInfo.CreateDelegate(testSubClass); + genReturnValue = genMethodDelegate.DynamicInvoke(); + Assert.Equal(returnValue, genReturnValue); + } + + [Fact] + public void CreateDelegate_GenericMethod() + { + Type typeGenericClassString = typeof(MI_GenericClass); + + MI_GenericClass genericClass = (MI_GenericClass)Activator.CreateInstance(typeGenericClassString); + + MethodInfo miMethod1String = GetMethod(typeGenericClassString, nameof(MI_GenericClass.GenericMethod1)); + MethodInfo miMethod2String = GetMethod(typeGenericClassString, nameof(MI_GenericClass.GenericMethod3)); + MethodInfo miMethod2IntGeneric = miMethod2String.MakeGenericMethod(new Type[] { typeof(int) }); + MethodInfo miMethod2StringGeneric = miMethod2String.MakeGenericMethod(new Type[] { typeof(string) }); + + Delegate methodDelegate = miMethod1String.CreateDelegate(typeof(Delegate_GC_T_T)); + object returnValue = ((Delegate_GC_T_T)methodDelegate).DynamicInvoke(new object[] { genericClass, "TestGeneric" }); + Assert.Equal(genericClass.GenericMethod1("TestGeneric"), returnValue); + + Delegate genMethodDelegate = miMethod1String.CreateDelegate>(); + object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { genericClass, "TestGeneric" }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = miMethod1String.CreateDelegate(typeof(Delegate_T_T), genericClass); + returnValue = ((Delegate_T_T)methodDelegate).DynamicInvoke(new object[] { "TestGeneric" }); + Assert.Equal(genericClass.GenericMethod1("TestGeneric"), returnValue); + + genMethodDelegate = miMethod1String.CreateDelegate>(genericClass); + genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { "TestGeneric" }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = miMethod2IntGeneric.CreateDelegate(typeof(Delegate_T_T)); + returnValue = ((Delegate_T_T)methodDelegate).DynamicInvoke(new object[] { 58 }); + Assert.Equal(58, returnValue); + + genMethodDelegate = miMethod2IntGeneric.CreateDelegate>(); + genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { 58 }); + Assert.Equal(returnValue, genReturnValue); + + methodDelegate = miMethod2StringGeneric.CreateDelegate(typeof(Delegate_Void_T), "firstArg"); + returnValue = ((Delegate_Void_T)methodDelegate).DynamicInvoke(); + Assert.Equal("firstArg", returnValue); + + genMethodDelegate = miMethod2StringGeneric.CreateDelegate>("firstArg"); + genReturnValue = genMethodDelegate.DynamicInvoke(); + Assert.Equal(returnValue, genReturnValue); + } + + [Fact] + public void CreateDelegate_ValueTypeParameters() + { + MethodInfo miPublicStructMethod = GetMethod(typeof(MI_BaseClass), nameof(MI_BaseClass.PublicStructMethod)); + MI_BaseClass testClass = new MI_BaseClass(); + + Delegate methodDelegate = miPublicStructMethod.CreateDelegate(typeof(Delegate_DateTime_Str)); + object returnValue = ((Delegate_DateTime_Str)methodDelegate).DynamicInvoke(new object[] { testClass, null }); + Assert.Equal(testClass.PublicStructMethod(new DateTime()), returnValue); + + Delegate genMethodDelegate = miPublicStructMethod.CreateDelegate(); + object genReturnValue = genMethodDelegate.DynamicInvoke(new object[] { testClass, null }); + Assert.Equal(returnValue, genReturnValue); + } + + private interface IStaticInterface + { + public static virtual string? StaticVirtual(string? s) => s; + } + + [Fact] + public void CreateDelegate_StaticVirtual() + { + MethodInfo miStaticVirtual = GetMethod(typeof(IStaticInterface), nameof(IStaticInterface.StaticVirtual)); + const string testString = "test"; + + Func methodDelegate = miStaticVirtual.CreateDelegate>(); + string? returnValue = methodDelegate(testString); + Assert.Equal(testString, returnValue); + } + + [Theory] + [InlineData(typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), null, typeof(ArgumentNullException))] + [InlineData(typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_Void_Int), typeof(ArgumentException))] + public void CreateDelegate_Invalid(Type type, string name, Type? delegateType, Type exceptionType) + { + MethodInfo methodInfo = GetMethod(type, name); + Assert.Throws(exceptionType, () => methodInfo.CreateDelegate(delegateType)); + } + + public static IEnumerable CreateDelegate_Target_Invalid_TestData() + { + yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), null, new MI_BaseClass(), typeof(ArgumentNullException) }; // DelegateType is null + yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_TC_Int), new MI_BaseClass(), typeof(ArgumentException) }; // DelegateType is incorrect + yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_Void_Int), new DummyClass(), typeof(ArgumentException) }; // Target is incorrect + yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualMethod), typeof(Delegate_Void_Str), new DummyClass(), typeof(ArgumentException) }; // Target is incorrect + } + + [Theory] + [MemberData(nameof(CreateDelegate_Target_Invalid_TestData))] + public void CreateDelegate_Target_Invalid(Type type, string name, Type delegateType, object target, Type exceptionType) + { + MethodInfo methodInfo = GetMethod(type, name); + Assert.Throws(exceptionType, () => methodInfo.CreateDelegate(delegateType, target)); + } + + [Theory] + [InlineData(typeof(Int32Attr), "[System.Reflection.Tests.Int32Attr((Int32)77, name = \"Int32AttrSimple\")]")] + [InlineData(typeof(Int64Attr), "[System.Reflection.Tests.Int64Attr((Int64)77, name = \"Int64AttrSimple\")]")] + [InlineData(typeof(StringAttr), "[System.Reflection.Tests.StringAttr(\"hello\", name = \"StringAttrSimple\")]")] + [InlineData(typeof(EnumAttr), "[System.Reflection.Tests.EnumAttr((System.Reflection.Tests.PublicEnum)1, name = \"EnumAttrSimple\")]")] + [InlineData(typeof(TypeAttr), "[System.Reflection.Tests.TypeAttr(typeof(System.Object), name = \"TypeAttrSimple\")]")] + [InlineData(typeof(Attr), "[System.Reflection.Tests.Attr((Int32)77, name = \"AttrSimple\")]")] + public void CustomAttributes(Type type, string expectedToString) + { + MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "MethodWithAttributes"); + CustomAttributeData attributeData = methodInfo.CustomAttributes.First(attribute => attribute.AttributeType.Equals(type)); + Assert.Equal(expectedToString, attributeData.ToString()); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), true)] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt), false)] + [InlineData(typeof(MI_SubClass), nameof(MI_GenericClass.GenericMethod1), typeof(MI_GenericClass<>), nameof(MI_GenericClass.GenericMethod1), false)] + [InlineData(typeof(MI_SubClass), nameof(MI_GenericClass.GenericMethod2), typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2), false)] + public void EqualsTest(Type type1, string name1, Type type2, string name2, bool expected) + { + MethodInfo methodInfo1 = GetMethod(type1, name1); + MethodInfo methodInfo2 = GetMethod(type2, name2); + Assert.Equal(expected, methodInfo1.Equals(methodInfo2)); + } + + [Theory] + //Verify two same MethodInfo objects are equal + [InlineData("DummyMethod1", "DummyMethod1", true)] + //Verify two different MethodInfo objects are not equal + [InlineData("DummyMethod1", "DummyMethod2", false)] + public void Equality1(string str1, string str2, bool expected) + { + MethodInfo mi1 = GetMethod(typeof(MethodInfoTests), str1); + MethodInfo mi2 = GetMethod(typeof(MethodInfoTests), str2); Assert.Equal(expected, mi1 == mi2); Assert.NotEqual(expected, mi1 != mi2); } + public static IEnumerable TestEqualityMethodData2() + { + //Verify two different MethodInfo objects with same name from two different classes are not equal + yield return new object[] { typeof(Sample), typeof(SampleG<>), "Method1", "Method1", false }; + //Verify two different MethodInfo objects with same name from two different classes are not equal + yield return new object[] { typeof(Sample), typeof(SampleG), "Method2", "Method2", false }; + } + + [Theory] + [MemberData(nameof(TestEqualityMethodData2))] + public void Equality2(Type sample1, Type sample2, string str1, string str2, bool expected) + { + MethodInfo mi1 = GetMethod(sample1, str1); + MethodInfo mi2 = GetMethod(sample2, str2); + + Assert.Equal(expected, mi1 == mi2); + Assert.NotEqual(expected, mi1 != mi2); + } + + [Theory] + [InlineData(typeof(MethodInfoBaseDefinitionBaseClass), "InterfaceMethod1", typeof(MethodInfoBaseDefinitionBaseClass))] + [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "InterfaceMethod1", typeof(MethodInfoBaseDefinitionBaseClass))] + [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "BaseClassVirtualMethod", typeof(MethodInfoBaseDefinitionBaseClass))] + [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "BaseClassMethod", typeof(MethodInfoBaseDefinitionSubClass))] + [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "ToString", typeof(object))] + [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "DerivedClassMethod", typeof(MethodInfoBaseDefinitionSubClass))] + public void GetBaseDefinition(Type type1, string name, Type type2) + { + MethodInfo method = GetMethod(type1, name).GetBaseDefinition(); + Assert.Equal(GetMethod(type2, name), method); + Assert.Equal(MemberTypes.Method, method.MemberType); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong), new string[] { "i", "l" })] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StringArrayMethod), new string[] { "strArray" })] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Increment), new string[] { "location" })] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Decrement), new string[] { "location" })] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Exchange), new string[] { "location1", "value" })] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.CompareExchange), new string[] { "location1", "value", "comparand" })] + public void GetParameters(Type type, string name, string[] expectedParameterNames) + { + MethodInfo method = GetMethod(type, name); + ParameterInfo[] parameters = method.GetParameters(); + + Assert.Equal(expectedParameterNames.Length, parameters.Length); + for (int i = 0; i < parameters.Length; i++) + { + Assert.Equal(parameters[i].Name, expectedParameterNames[i]); + } + } + + [Fact] + public void GetParameters_IsDeepCopy() + { + MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong)); + ParameterInfo[] parameters = method.GetParameters(); + parameters[0] = null; + + // If GetParameters is a deep copy, then this change + // should not affect another call to GetParameters() + ParameterInfo[] parameters2 = method.GetParameters(); + for (int i = 0; i < parameters2.Length; i++) + { + Assert.NotNull(parameters2[i]); + } + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), true)] + public void ContainsGenericParameters(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).ContainsGenericParameters); + } + + [Fact] + public void InvokeUninstantiatedGenericMethod() + { + Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticGenericMethod)).Invoke(null, [null])); + } + + [Fact] + public void InvokeUninstantiatedGenericType_GenericMethod() + { + Assert.Throws(() => GetMethod(typeof(MI_GenericClass<>), "GenericMethod4").Invoke(null, [null])); + } + + [Fact] + public void InvokeUninstantiatedGenericType_NonGenericMethod() + { + Assert.Throws(() => GetMethod(typeof(MI_GenericClass<>), "NonGenericMethod").Invoke(null, [null])); + } + + [Fact] + public void GetFunctionPointerFromUninstantiatedGenericMethod() + { + RuntimeMethodHandle handle = typeof(MI_SubClass).GetMethod(nameof(MI_SubClass.StaticGenericMethod))!.MethodHandle; + Assert.Throws(() => handle.GetFunctionPointer()); + } + + [Fact] + public void GetFunctionPointerOnUninstantiatedGenericType_GenericMethod() + { + RuntimeMethodHandle handle = typeof(MI_GenericClass<>).GetMethod("GenericMethod4")!.MethodHandle; + Assert.Throws(() => handle.GetFunctionPointer()); + } + + [Fact] + public void GetFunctionPointerOnUninstantiatedGenericType_NonGenericMethod() + { + RuntimeMethodHandle handle = typeof(MI_GenericClass<>).GetMethod("NonGenericMethod")!.MethodHandle; + Assert.Throws(() => handle.GetFunctionPointer()); + } + + [Fact] + public void GetHashCodeTest() + { + MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "VoidMethodReturningInt"); + Assert.NotEqual(0, methodInfo.GetHashCode()); + } + + [Fact] + public void GetHashCode_MultipleSubClasses_ShouldBeUnique() + { + var numberOfCollisions = 0; + var hashset = new HashSet(); + + foreach (var type in new Type[] { typeof(MI_BaseClass), typeof(MI_SubClassA), typeof(MI_SubClassB), typeof(MI_SubClassC) }) + { + foreach (var methodInfo in type.GetMethods()) + { + if (!hashset.Add(methodInfo.GetHashCode())) + { + numberOfCollisions++; + } + } + } + + // If intermittent failures are observed, it's acceptable to relax the assertion to allow some collisions. + Assert.Equal(0, numberOfCollisions); + } + + public static IEnumerable Invoke_TestData() + { + yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualReturnIntMethod), new MI_BaseClass(), null, 0 }; + yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualReturnIntMethod), new MethodInfoDummySubClass(), null, 1 }; // From parent class + + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), new MI_SubClass(), new object[] { 42 }, "42" }; // Box primitive integer + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt), new MI_SubClass(), null, 3 }; // No parameters + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong), new MI_SubClass(), null, long.MaxValue }; // No parameters + + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong), new MI_SubClass(), new object[] { 200, 10000 }, 10200L }; // Primitive parameters + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), null, new object[] { 10, 100 }, 110 }; // Static primitive parameters + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), new MI_SubClass(), new object[] { 10, 100 }, 110 }; // Static primitive parameters + yield return new object[] { typeof(MI_BaseClass), nameof(MI_SubClass.StaticIntMethodReturningBool), new MI_SubClass(), new object[] { 10 }, true }; // Static from parent class + + yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.EnumMethodReturningEnum), new MI_SubClass(), new object[] { PublicEnum.Case1 }, PublicEnum.Case2 }; // Enum + yield return new object[] { typeof(MI_Interface), nameof(MI_Interface.IMethod), new MI_SubClass(), new object[0], 10 }; // Interface + yield return new object[] { typeof(MI_Interface), nameof(MI_Interface.IMethodNew), new MI_SubClass(), new object[0], 20 }; // Interface + + yield return new object[] { typeof(MethodInfoDefaultParameters), "Integer", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, 1 }; // Default int parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "Integer", new MethodInfoDefaultParameters(), new object[] { 2 }, 2 }; // Default int parameter, present + yield return new object[] { typeof(MethodInfoDefaultParameters), "AllPrimitives", new MethodInfoDefaultParameters(), Enumerable.Repeat(Type.Missing, 13), "True, test, c, 2, -1, -3, 4, -5, 6, -7, 8, 9.1, 11.12" }; // Default parameters, all missing + + object[] allPrimitives = new object[] { false, "value", 'd', (byte)102, (sbyte)-101, (short)-103, (ushort)104, -105, (uint)106, (long)-107, (ulong)108, 109.1f, 111.12 }; + yield return new object[] { typeof(MethodInfoDefaultParameters), "AllPrimitives", new MethodInfoDefaultParameters(), allPrimitives, "False, value, d, 102, -101, -103, 104, -105, 106, -107, 108, 109.1, 111.12" }; // Default parameters, all present + + object[] somePrimitives = new object[] { false, Type.Missing, 'd', Type.Missing, (sbyte)-101, Type.Missing, (ushort)104, Type.Missing, (uint)106, Type.Missing, (ulong)108, Type.Missing, 111.12 }; + yield return new object[] { typeof(MethodInfoDefaultParameters), "AllPrimitives", new MethodInfoDefaultParameters(), somePrimitives, "False, test, d, 2, -101, -3, 104, -5, 106, -7, 108, 9.1, 111.12" }; // Default parameters, some present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "String", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, "test" }; // Default string parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "String", new MethodInfoDefaultParameters(), new object[] { "value" }, "value" }; // Default string parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "Reference", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, null }; // Default reference parameter, missing + object referenceType = new MethodInfoDefaultParameters.CustomReferenceType(); + yield return new object[] { typeof(MethodInfoDefaultParameters), "Reference", new MethodInfoDefaultParameters(), new object[] { referenceType }, referenceType }; // Default reference parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "ValueType", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, new MethodInfoDefaultParameters.CustomValueType() { Id = 0 } }; // Default value type parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "ValueType", new MethodInfoDefaultParameters(), new object[] { new MethodInfoDefaultParameters.CustomValueType() { Id = 1 } }, new MethodInfoDefaultParameters.CustomValueType() { Id = 1 } }; // Default value type parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "DateTime", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, new DateTime(42) }; // Default DateTime parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "DateTime", new MethodInfoDefaultParameters(), new object[] { new DateTime(43) }, new DateTime(43) }; // Default DateTime parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "DecimalWithAttribute", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, new decimal(4, 3, 2, true, 1) }; // Default decimal parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "DecimalWithAttribute", new MethodInfoDefaultParameters(), new object[] { new decimal(12, 13, 14, true, 1) }, new decimal(12, 13, 14, true, 1) }; // Default decimal parameter, present + yield return new object[] { typeof(MethodInfoDefaultParameters), "Decimal", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, 3.14m }; // Default decimal parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "Decimal", new MethodInfoDefaultParameters(), new object[] { 103.14m }, 103.14m }; // Default decimal parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "NullableInt", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, null }; // Default nullable parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "NullableInt", new MethodInfoDefaultParameters(), new object[] { (int?)42 }, (int?)42 }; // Default nullable parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "Enum", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, PublicEnum.Case1 }; // Default enum parameter, missing + + yield return new object[] { typeof(MethodInfoDefaultParametersInterface), "InterfaceMethod", new MethodInfoDefaultParameters(), new object[] { Type.Missing, Type.Missing, Type.Missing }, "1, test, 3.14" }; // Default interface parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParametersInterface), "InterfaceMethod", new MethodInfoDefaultParameters(), new object[] { 101, "value", 103.14m }, "101, value, 103.14" }; // Default interface parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "StaticMethod", null, new object[] { Type.Missing, Type.Missing, Type.Missing }, "1, test, 3.14" }; // Default static parameter, missing + yield return new object[] { typeof(MethodInfoDefaultParameters), "StaticMethod", null, new object[] { 101, "value", 103.14m }, "101, value, 103.14" }; // Default static parameter, present + + yield return new object[] { typeof(MethodInfoDefaultParameters), "OptionalObjectParameter", new MethodInfoDefaultParameters(), new object[] { "value" }, "value" }; // Default static parameter, present + yield return new object[] { typeof(MethodInfoDefaultParameters), "String", new MethodInfoDefaultParameters(), new string[] { "value" }, "value" }; // String array + } + + [Theory] + [MemberData(nameof(Invoke_TestData))] + public void InvokeWithTestData(Type methodDeclaringType, string methodName, object obj, object[] parameters, object result) + { + MethodInfo method = GetMethod(methodDeclaringType, methodName); + Assert.Equal(result, method.Invoke(obj, parameters)); + } + + [Fact] + public void Invoke_ParameterSpecification_ArrayOfMissing() + { + InvokeWithTestData(typeof(MethodInfoDefaultParameters), "OptionalObjectParameter", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, Type.Missing); + InvokeWithTestData(typeof(MethodInfoDefaultParameters), "OptionalObjectParameter", new MethodInfoDefaultParameters(), new Missing[] { Missing.Value }, Missing.Value); + } + + [Fact] + [ActiveIssue("https://github.com/mono/mono/issues/15025", TestRuntimes.Mono)] + public static void Invoke_OptionalParameterUnassingableFromMissing_WithMissingValue_ThrowsArgumentException() + { + AssertExtensions.Throws(null, () => GetMethod(typeof(MethodInfoDefaultParameters), "OptionalStringParameter").Invoke(new MethodInfoDefaultParameters(), new object[] { Type.Missing })); + } + + [Fact] + public void Invoke_TwoParameters_CustomBinder_IncorrectTypeArguments() + { + MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt)); + var args = new object[] { "10", "100" }; + Assert.Equal(110, method.Invoke(null, BindingFlags.Default, new ConvertStringToIntBinder(), args, null)); + Assert.True(args[0] is int); + Assert.True(args[1] is int); + } + + [Fact] + public void Invoke_CustomBinder_ResultRequiringPrimitiveWidening_DoesNotCopyBackWidenedArgument() + { + MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt)); + object[] args = new object[] { "10", 100 }; + + Assert.Equal(110, method.Invoke(null, BindingFlags.Default, new ConvertStringToInt16Binder(), args, null)); + Assert.Equal("10", Assert.IsType(args[0])); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), new Type[] { typeof(int) })] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), new Type[] { typeof(string), typeof(int) })] + public void MakeGenericMethod(Type type, string name, Type[] typeArguments) + { + MethodInfo methodInfo = GetMethod(type, name); + MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeArguments); + Assert.True(genericMethodInfo.IsGenericMethod); + Assert.False(genericMethodInfo.IsGenericMethodDefinition); + + MethodInfo genericMethodDefinition = genericMethodInfo.GetGenericMethodDefinition(); + Assert.Equal(methodInfo, genericMethodDefinition); + Assert.True(genericMethodDefinition.IsGenericMethod); + Assert.True(genericMethodDefinition.IsGenericMethodDefinition); + } + + [Fact] + public void MakeGenericMethod_Invalid() + { + Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)).MakeGenericMethod(null)); // TypeArguments is null + Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2)).MakeGenericMethod(typeof(string), null)); // TypeArguments has null Type + Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt)).MakeGenericMethod(typeof(int))); // Method is non generic + + // Number of typeArguments does not match + AssertExtensions.Throws(null, () => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)).MakeGenericMethod()); + AssertExtensions.Throws(null, () => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)).MakeGenericMethod(typeof(string), typeof(int))); + AssertExtensions.Throws(null, () => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2)).MakeGenericMethod(typeof(int))); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), 1)] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), 2)] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong), 0)] + public void GetGenericArguments(Type type, string name, int expectedCount) + { + MethodInfo methodInfo = GetMethod(type, name); + Type[] genericArguments = methodInfo.GetGenericArguments(); + Assert.Equal(expectedCount, genericArguments.Length); + } + + [Fact] + public void GetGenericMethodDefinition_MethodNotGeneric_ThrowsInvalidOperationException() + { + Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt)).GetGenericMethodDefinition()); + } + + [Fact] + public void Attributes() + { + MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "ReturnVoidMethod"); + MethodAttributes attributes = methodInfo.Attributes; + Assert.True(attributes.HasFlag(MethodAttributes.Public)); + } + + [Fact] + public void CallingConvention() + { + MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "ReturnVoidMethod"); + CallingConventions callingConvention = methodInfo.CallingConvention; + Assert.True(callingConvention.HasFlag(CallingConventions.HasThis)); + } + [Theory] - [InlineData(typeof(MethodInfoBaseDefinitionBaseClass), "InterfaceMethod1", typeof(MethodInfoBaseDefinitionBaseClass))] - [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "InterfaceMethod1", typeof(MethodInfoBaseDefinitionBaseClass))] - [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "BaseClassVirtualMethod", typeof(MethodInfoBaseDefinitionBaseClass))] - [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "BaseClassMethod", typeof(MethodInfoBaseDefinitionSubClass))] - [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "ToString", typeof(object))] - [InlineData(typeof(MethodInfoBaseDefinitionSubClass), "DerivedClassMethod", typeof(MethodInfoBaseDefinitionSubClass))] - public void GetBaseDefinition(Type type1, string name, Type type2) + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + [InlineData(typeof(MI_AbstractBaseClass), nameof(MI_AbstractBaseClass.AbstractMethod), true)] + public void IsAbstract(Type type, string name, bool expected) { - MethodInfo method = GetMethod(type1, name).GetBaseDefinition(); - Assert.Equal(GetMethod(type2, name), method); - Assert.Equal(MemberTypes.Method, method.MemberType); + Assert.Equal(expected, GetMethod(type, name).IsAbstract); } [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong), new string[] { "i", "l" })] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StringArrayMethod), new string[] { "strArray" })] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Increment), new string[] { "location" })] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Decrement), new string[] { "location" })] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Exchange), new string[] { "location1", "value" })] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.CompareExchange), new string[] { "location1", "value", "comparand" })] - public void GetParameters(Type type, string name, string[] expectedParameterNames) + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsAssembly(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsAssembly); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsConstructor(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsConstructor); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsFamily(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsFamily); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsFamilyAndAssembly(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsFamilyAndAssembly); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsFamilyOrAssembly(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsFamilyOrAssembly); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + [InlineData(typeof(MI_AbstractSubClass), nameof(MI_AbstractSubClass.VirtualMethod), true)] + public void IsFinal(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsFinal); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), false)] + public void IsGenericMethodDefinition(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsGenericMethodDefinition); + } + + [Theory] + [InlineData(typeof(MI_AbstractSubClass), nameof(MI_AbstractSubClass.AbstractMethod), true)] + public void IsHideBySig(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsHideBySig); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsPrivate(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsPrivate); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), true)] + public void IsPublic(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsPublic); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + public void IsSpecialName(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsSpecialName); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), true)] + public void IsStatic(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsStatic); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VirtualReturnBoolMethod), true)] + public void IsVirtual(Type type, string name, bool expected) + { + Assert.Equal(expected, GetMethod(type, name).IsVirtual); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StringArrayMethod))] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Increment))] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Decrement))] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Exchange))] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.CompareExchange))] + public void Name(Type type, string name) + { + MethodInfo mi = GetMethod(type, name); + Assert.Equal(name, mi.Name); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), typeof(int))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), typeof(void))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt), typeof(int))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), typeof(string))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VirtualReturnStringArrayMethod), typeof(string[]))] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VirtualReturnBoolMethod), typeof(bool))] + public void ReturnType_ReturnParameter(Type type, string name, Type expected) + { + MethodInfo methodInfo = GetMethod(type, name); + Assert.Equal(expected, methodInfo.ReturnType); + + Assert.Equal(methodInfo.ReturnType, methodInfo.ReturnParameter.ParameterType); + Assert.Null(methodInfo.ReturnParameter.Name); + Assert.Equal(-1, methodInfo.ReturnParameter.Position); + } + + [Theory] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong), "Int64 VoidMethodReturningLong()")] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong), "Int64 IntLongMethodReturningLong(Int32, Int64)")] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StringArrayMethod), "Void StringArrayMethod(System.String[])")] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), "Void ReturnVoidMethod(System.DateTime)")] + [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), "Void GenericMethod2[T,U](T, U)")] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Increment), "Int32 Increment(Int32 ByRef)")] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Decrement), "Int32 Decrement(Int32 ByRef)")] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Exchange), "Int32 Exchange(Int32 ByRef, Int32)")] + [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.CompareExchange), "Int32 CompareExchange(Int32 ByRef, Int32, Int32)")] + [InlineData(typeof(MI_GenericClass<>), nameof(MI_GenericClass.GenericMethod1), "T GenericMethod1(T)")] + [InlineData(typeof(MI_GenericClass<>), nameof(MI_GenericClass.GenericMethod2), "T GenericMethod2[S](S, T, System.String)")] + [InlineData(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod1), "System.String GenericMethod1(System.String)")] + [InlineData(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2), "System.String GenericMethod2[S](S, System.String, System.String)")] + public void ToStringTest(Type type, string name, string expected) + { + MethodInfo methodInfo = GetMethod(type, name); + Assert.Equal(expected, methodInfo.ToString()); + } + + public static IEnumerable ToString_TestData() + { + MethodInfo genericMethodInfo = GetMethod(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2)).MakeGenericMethod(new Type[] { typeof(DateTime) }); + yield return new object[] { genericMethodInfo, "System.String GenericMethod2[DateTime](System.DateTime, System.String, System.String)" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToStringTest_ByMethodInfo(MethodInfo methodInfo, string expected) + { + Assert.Equal(expected, methodInfo.ToString()); + } + + public static IEnumerable MethodNameAndArguments() + { + yield return new object[] { nameof(Sample.DefaultString), "Hello", "Hi" }; + yield return new object[] { nameof(Sample.DefaultNullString), null, "Hi" }; + yield return new object[] { nameof(Sample.DefaultNullableInt), 3, 5 }; + yield return new object[] { nameof(Sample.DefaultNullableEnum), YesNo.Yes, YesNo.No }; + } + + [Theory] + [MemberData(nameof(MethodNameAndArguments))] + public static void InvokeCopiesBackMissingArgument(string methodName, object defaultValue, object passingValue) + { + MethodInfo method = typeof(Sample).GetMethod(methodName); + object[] args = new object[] { Missing.Value }; + + Assert.Equal(defaultValue, method.Invoke(null, args)); + Assert.Equal(defaultValue, args[0]); + + args[0] = passingValue; + + Assert.Equal(passingValue, method.Invoke(null, args)); + Assert.Equal(passingValue, args[0]); + + args[0] = null; + Assert.Null(method.Invoke(null, args)); + Assert.Null(args[0]); + } + + [Fact] + public static void InvokeCopiesBackMissingParameterAndArgument() + { + MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.DefaultMissing)); + object[] args = new object[] { Missing.Value }; + + Assert.Null(method.Invoke(null, args)); + Assert.Null(args[0]); + + args[0] = null; + Assert.Null(method.Invoke(null, args)); + Assert.Null(args[0]); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/50957", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))] + public static void CallStackFrame_AggressiveInlining() + { + MethodInfo mi = typeof(System.Reflection.TestAssembly.ClassToInvoke).GetMethod(nameof(System.Reflection.TestAssembly.ClassToInvoke.CallMe_AggressiveInlining), + BindingFlags.Public | BindingFlags.Static)!; + + // Although the target method has AggressiveInlining, currently reflection should not inline the target into any generated IL. + FirstCall(mi); + SecondCall(mi); + } + + [MethodImpl(MethodImplOptions.NoInlining)] // Separate non-inlineable method to aid any test failures + private static void FirstCall(MethodInfo mi) + { + Assembly asm = (Assembly)mi.Invoke(null, null); + Assert.Contains("TestAssembly", asm.ToString()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] // Separate non-inlineable method to aid any test failures + private static void SecondCall(MethodInfo mi) + { + Assembly asm = (Assembly)mi.Invoke(null, null); + Assert.Contains("TestAssembly", asm.ToString()); + } + + //Methods for Reflection Metadata + private void DummyMethod1(string str, int iValue, long lValue) { - MethodInfo method = GetMethod(type, name); - ParameterInfo[] parameters = method.GetParameters(); + } - Assert.Equal(expectedParameterNames.Length, parameters.Length); - for (int i = 0; i < parameters.Length; i++) + private void DummyMethod2() + { + } + } + + internal static class IntrinsicInvokeSelectionAssertions + { + internal const int SpecializationThreshold = 10_000; + private const string ForceEmitInvokeSwitch = "Switch.System.Reflection.ForceEmitInvoke"; + private const string ForceInterpretedInvokeSwitch = "Switch.System.Reflection.ForceInterpretedInvoke"; + private const string SharedThunkMethodName = "InvokeWithSharedThunk"; + private const BindingFlags InstanceFields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + + internal static void AssertShared(MethodBase method) + { + if (ShouldAssertSharedSelection) { - Assert.Equal(parameters[i].Name, expectedParameterNames[i]); + AssertShared(GetCachedInvoker(method)); } } - [Fact] - public void GetParameters_IsDeepCopy() + internal static void AssertShared(object invoker) { - MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong)); - ParameterInfo[] parameters = method.GetParameters(); - parameters[0] = null; - - // If GetParameters is a deep copy, then this change - // should not affect another call to GetParameters() - ParameterInfo[] parameters2 = method.GetParameters(); - for (int i = 0; i < parameters2.Length; i++) + if (!ShouldAssertSharedSelection) { - Assert.NotNull(parameters2[i]); + return; } + + Assert.Equal(SharedThunkMethodName, GetRefArgsDelegate(invoker).Method.Name); + Assert.NotEqual(IntPtr.Zero, GetInvokeStateField(invoker, "Thunk")); } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), true)] - public void ContainsGenericParameters(Type type, string name, bool expected) + internal static void AssertFallback(MethodBase method) { - Assert.Equal(expected, GetMethod(type, name).ContainsGenericParameters); + if (!ShouldAssertSharedSelection) + { + return; + } + + AssertFallback(GetCachedInvoker(method)); } - [Fact] - public void InvokeUninstantiatedGenericMethod() + internal static void AssertFallback(object invoker) { - Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticGenericMethod)).Invoke(null, [null])); + if (!ShouldAssertSharedSelection) + { + return; + } + + Assert.NotEqual(SharedThunkMethodName, GetRefArgsDelegate(invoker).Method.Name); + Assert.Equal(IntPtr.Zero, GetInvokeStateField(invoker, "Thunk")); } - [Fact] - public void InvokeUninstantiatedGenericType_GenericMethod() + internal static void AssertPromoted(object invoker) { - Assert.Throws(() => GetMethod(typeof(MI_GenericClass<>), "GenericMethod4").Invoke(null, [null])); + if (!ShouldAssertSharedSelection) + { + return; + } + + if (!ShouldAssertPromotion) + { + AssertNotPromoted(invoker, 0); + return; + } + + Assert.Equal(SpecializationThreshold, GetInvokeStateField(invoker, "InvocationCount")); + Assert.True( + GetRefArgsDelegate(invoker).Method.Name != SharedThunkMethodName || + GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args") is not null || + GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs") is not null); } - [Fact] - public void InvokeUninstantiatedGenericType_NonGenericMethod() + internal static void AssertNotPromoted(object invoker, int invocationCount) { - Assert.Throws(() => GetMethod(typeof(MI_GenericClass<>), "NonGenericMethod").Invoke(null, [null])); + if (!ShouldAssertSharedSelection) + { + return; + } + + AssertShared(invoker); + Assert.Null(GetOptionalDelegate(invoker, "_invokeFunc_Obj4Args")); + Assert.Null(GetOptionalDelegate(invoker, "_invokeFunc_ObjSpanArgs")); + Assert.Equal(ShouldAssertPromotion ? invocationCount : 0, GetInvokeStateField(invoker, "InvocationCount")); } - [Fact] - public void GetFunctionPointerFromUninstantiatedGenericMethod() + private static bool ShouldAssertSharedSelection => + PlatformDetection.IsCoreCLR && (!IsForceEmitOnly || !RuntimeFeature.IsDynamicCodeCompiled); + + private static bool ShouldAssertPromotion => + ShouldAssertSharedSelection && + RuntimeFeature.IsDynamicCodeCompiled && + !IsForceInterpretedOnly; + + private static bool IsForceEmitOnly => + IsSwitchEnabled(ForceEmitInvokeSwitch) && !IsSwitchEnabled(ForceInterpretedInvokeSwitch); + + private static bool IsForceInterpretedOnly => + IsSwitchEnabled(ForceInterpretedInvokeSwitch) && !IsSwitchEnabled(ForceEmitInvokeSwitch); + + private static bool IsSwitchEnabled(string name) => + AppContext.TryGetSwitch(name, out bool enabled) && enabled; + + private static object GetCachedInvoker(MethodBase method) { - RuntimeMethodHandle handle = typeof(MI_SubClass).GetMethod(nameof(MI_SubClass.StaticGenericMethod))!.MethodHandle; - Assert.Throws(() => handle.GetFunctionPointer()); + FieldInfo? field = method.GetType().GetField("m_invoker", InstanceFields); + Assert.NotNull(field); + object? invoker = field.GetValue(method); + Assert.NotNull(invoker); + return invoker; } - [Fact] - public void GetFunctionPointerOnUninstantiatedGenericType_GenericMethod() + private static Delegate GetRefArgsDelegate(object invoker) { - RuntimeMethodHandle handle = typeof(MI_GenericClass<>).GetMethod("GenericMethod4")!.MethodHandle; - Assert.Throws(() => handle.GetFunctionPointer()); + FieldInfo? field = invoker.GetType().GetField("_invokeFunc_RefArgs", InstanceFields); + Assert.NotNull(field); + object? value = field.GetValue(invoker); + Assert.NotNull(value); + return Assert.IsAssignableFrom(value); } - [Fact] - public void GetFunctionPointerOnUninstantiatedGenericType_NonGenericMethod() + private static Delegate? GetOptionalDelegate(object invoker, string fieldName) { - RuntimeMethodHandle handle = typeof(MI_GenericClass<>).GetMethod("NonGenericMethod")!.MethodHandle; - Assert.Throws(() => handle.GetFunctionPointer()); + FieldInfo? field = invoker.GetType().GetField(fieldName, InstanceFields); + return field?.GetValue(invoker) as Delegate; } - [Fact] - public void GetHashCodeTest() + private static T GetInvokeStateField(object invoker, string fieldName) { - MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "VoidMethodReturningInt"); - Assert.NotEqual(0, methodInfo.GetHashCode()); + FieldInfo? invokeStateField = invoker.GetType().GetField("_invokeState", InstanceFields); + Assert.NotNull(invokeStateField); + object? invokeState = invokeStateField.GetValue(invoker); + Assert.NotNull(invokeState); + FieldInfo? valueField = invokeState.GetType().GetField(fieldName, InstanceFields); + Assert.NotNull(valueField); + object? value = valueField.GetValue(invokeState); + Assert.NotNull(value); + return (T)value; } + } - [Fact] - public void GetHashCode_MultipleSubClasses_ShouldBeUnique() - { - var numberOfCollisions = 0; - var hashset = new HashSet(); + internal interface IIntrinsicInvokeReference + { + int Value { get; } + } - foreach (var type in new Type[] { typeof(MI_BaseClass), typeof(MI_SubClassA), typeof(MI_SubClassB), typeof(MI_SubClassC) }) - { - foreach (var methodInfo in type.GetMethods()) - { - if (!hashset.Add(methodInfo.GetHashCode())) - { - numberOfCollisions++; - } - } - } + internal sealed class IntrinsicInvokeReference : IIntrinsicInvokeReference + { + internal IntrinsicInvokeReference(int value) => Value = value; - // If intermittent failures are observed, it's acceptable to relax the assertion to allow some collisions. - Assert.Equal(0, numberOfCollisions); - } + public int Value { get; } + } - public static IEnumerable Invoke_TestData() - { - yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualReturnIntMethod), new MI_BaseClass(), null, 0 }; - yield return new object[] { typeof(MI_BaseClass), nameof(MI_BaseClass.VirtualReturnIntMethod), new MethodInfoDummySubClass(), null, 1 }; // From parent class + internal sealed class IntrinsicInvokeReferenceTarget + { + internal int CallCount { get; private set; } + internal object? LastValue { get; private set; } + internal object Sentinel { get; } = new object(); - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), new MI_SubClass(), new object[] { 42 }, "42" }; // Box primitive integer - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt), new MI_SubClass(), null, 3 }; // No parameters - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong), new MI_SubClass(), null, long.MaxValue }; // No parameters + public void Void0() => Record(Sentinel); - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong), new MI_SubClass(), new object[] { 200, 10000 }, 10200L }; // Primitive parameters - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), null, new object[] { 10, 100 }, 110 }; // Static primitive parameters - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), new MI_SubClass(), new object[] { 10, 100 }, 110 }; // Static primitive parameters - yield return new object[] { typeof(MI_BaseClass), nameof(MI_SubClass.StaticIntMethodReturningBool), new MI_SubClass(), new object[] { 10 }, true }; // Static from parent class + public void Void1(IIntrinsicInvokeReference value) => Record(value); - yield return new object[] { typeof(MI_SubClass), nameof(MI_SubClass.EnumMethodReturningEnum), new MI_SubClass(), new object[] { PublicEnum.Case1 }, PublicEnum.Case2 }; // Enum - yield return new object[] { typeof(MI_Interface), nameof(MI_Interface.IMethod), new MI_SubClass(), new object[0], 10 }; // Interface - yield return new object[] { typeof(MI_Interface), nameof(MI_Interface.IMethodNew), new MI_SubClass(), new object[0], 20 }; // Interface + public void Void2(object[] values, Action callback) => Record(callback, values); - yield return new object[] { typeof(MethodInfoDefaultParameters), "Integer", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, 1 }; // Default int parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "Integer", new MethodInfoDefaultParameters(), new object[] { 2 }, 2 }; // Default int parameter, present - yield return new object[] { typeof(MethodInfoDefaultParameters), "AllPrimitives", new MethodInfoDefaultParameters(), Enumerable.Repeat(Type.Missing, 13), "True, test, c, 2, -1, -3, 4, -5, 6, -7, 8, 9.1, 11.12" }; // Default parameters, all missing + public void Void3(Task task, IIntrinsicInvokeReference value, string[] values) => + Record(values, task, value); - object[] allPrimitives = new object[] { false, "value", 'd', (byte)102, (sbyte)-101, (short)-103, (ushort)104, -105, (uint)106, (long)-107, (ulong)108, 109.1f, 111.12 }; - yield return new object[] { typeof(MethodInfoDefaultParameters), "AllPrimitives", new MethodInfoDefaultParameters(), allPrimitives, "False, value, d, 102, -101, -103, 104, -105, 106, -107, 108, 109.1, 111.12" }; // Default parameters, all present + public void Void4(object value, IIntrinsicInvokeReference reference, Action callback, Task task) => + Record(task, value, reference, callback); - object[] somePrimitives = new object[] { false, Type.Missing, 'd', Type.Missing, (sbyte)-101, Type.Missing, (ushort)104, Type.Missing, (uint)106, Type.Missing, (ulong)108, Type.Missing, 111.12 }; - yield return new object[] { typeof(MethodInfoDefaultParameters), "AllPrimitives", new MethodInfoDefaultParameters(), somePrimitives, "False, test, d, 2, -101, -3, 104, -5, 106, -7, 108, 9.1, 111.12" }; // Default parameters, some present + public object Return0() => CollectAndReturn(Sentinel); - yield return new object[] { typeof(MethodInfoDefaultParameters), "String", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, "test" }; // Default string parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "String", new MethodInfoDefaultParameters(), new object[] { "value" }, "value" }; // Default string parameter, present + public IIntrinsicInvokeReference Return1(IIntrinsicInvokeReference value) => CollectAndReturn(value); - yield return new object[] { typeof(MethodInfoDefaultParameters), "Reference", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, null }; // Default reference parameter, missing - object referenceType = new MethodInfoDefaultParameters.CustomReferenceType(); - yield return new object[] { typeof(MethodInfoDefaultParameters), "Reference", new MethodInfoDefaultParameters(), new object[] { referenceType }, referenceType }; // Default reference parameter, present + public object[] Return2(object[] values, Action callback) => CollectAndReturn(values, callback); - yield return new object[] { typeof(MethodInfoDefaultParameters), "ValueType", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, new MethodInfoDefaultParameters.CustomValueType() { Id = 0 } }; // Default value type parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "ValueType", new MethodInfoDefaultParameters(), new object[] { new MethodInfoDefaultParameters.CustomValueType() { Id = 1 } }, new MethodInfoDefaultParameters.CustomValueType() { Id = 1 } }; // Default value type parameter, present + public Func Return3(Task task, Func callback, IIntrinsicInvokeReference value) => + CollectAndReturn(callback, task, value); - yield return new object[] { typeof(MethodInfoDefaultParameters), "DateTime", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, new DateTime(42) }; // Default DateTime parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "DateTime", new MethodInfoDefaultParameters(), new object[] { new DateTime(43) }, new DateTime(43) }; // Default DateTime parameter, present + public Task Return4( + Task task, + object[] values, + IIntrinsicInvokeReference reference, + Action callback) => + CollectAndReturn(task, values, reference, callback); - yield return new object[] { typeof(MethodInfoDefaultParameters), "DecimalWithAttribute", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, new decimal(4, 3, 2, true, 1) }; // Default decimal parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "DecimalWithAttribute", new MethodInfoDefaultParameters(), new object[] { new decimal(12, 13, 14, true, 1) }, new decimal(12, 13, 14, true, 1) }; // Default decimal parameter, present - yield return new object[] { typeof(MethodInfoDefaultParameters), "Decimal", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, 3.14m }; // Default decimal parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "Decimal", new MethodInfoDefaultParameters(), new object[] { 103.14m }, 103.14m }; // Default decimal parameter, present + [MethodImpl(MethodImplOptions.NoInlining)] + private void Record(object? value, params object?[] keepAlive) + { + GC.Collect(); + CallCount++; + LastValue = value; + GC.KeepAlive(keepAlive); + } - yield return new object[] { typeof(MethodInfoDefaultParameters), "NullableInt", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, null }; // Default nullable parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "NullableInt", new MethodInfoDefaultParameters(), new object[] { (int?)42 }, (int?)42 }; // Default nullable parameter, present + [MethodImpl(MethodImplOptions.NoInlining)] + private static T CollectAndReturn(T value, params object?[] keepAlive) + { + GC.Collect(); + GC.KeepAlive(keepAlive); + return value; + } + } - yield return new object[] { typeof(MethodInfoDefaultParameters), "Enum", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, PublicEnum.Case1 }; // Default enum parameter, missing + internal sealed class IntrinsicInvokePrimitiveReturnTarget + { + public T Value { get; set; } - yield return new object[] { typeof(MethodInfoDefaultParametersInterface), "InterfaceMethod", new MethodInfoDefaultParameters(), new object[] { Type.Missing, Type.Missing, Type.Missing }, "1, test, 3.14" }; // Default interface parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParametersInterface), "InterfaceMethod", new MethodInfoDefaultParameters(), new object[] { 101, "value", 103.14m }, "101, value, 103.14" }; // Default interface parameter, present + public T GetValue() + { + GC.Collect(); + return Value; + } + } - yield return new object[] { typeof(MethodInfoDefaultParameters), "StaticMethod", null, new object[] { Type.Missing, Type.Missing, Type.Missing }, "1, test, 3.14" }; // Default static parameter, missing - yield return new object[] { typeof(MethodInfoDefaultParameters), "StaticMethod", null, new object[] { 101, "value", 103.14m }, "101, value, 103.14" }; // Default static parameter, present + internal sealed class IntrinsicInvokePrimitiveArgumentTarget + { + public object? Value; - yield return new object[] { typeof(MethodInfoDefaultParameters), "OptionalObjectParameter", new MethodInfoDefaultParameters(), new object[] { "value" }, "value" }; // Default static parameter, present - yield return new object[] { typeof(MethodInfoDefaultParameters), "String", new MethodInfoDefaultParameters(), new string[] { "value" }, "value" }; // String array + public void SetValue(T value) + { + GC.Collect(); + Value = value; } + } - [Theory] - [MemberData(nameof(Invoke_TestData))] - public void InvokeWithTestData(Type methodDeclaringType, string methodName, object obj, object[] parameters, object result) + internal sealed class IntrinsicInvokeInstancePatternTarget + { + internal float X { get; private set; } + internal float Y { get; private set; } + internal float Z { get; private set; } + internal int W { get; private set; } + + public int Sum(IIntrinsicInvokeReference reference, object[] values) { - MethodInfo method = GetMethod(methodDeclaringType, methodName); - Assert.Equal(result, method.Invoke(obj, parameters)); + GC.Collect(); + return reference.Value + values.Length; } - [Fact] - public void Invoke_ParameterSpecification_ArrayOfMissing() + public void SetVector(float x, float y, float z, int w) { - InvokeWithTestData(typeof(MethodInfoDefaultParameters), "OptionalObjectParameter", new MethodInfoDefaultParameters(), new object[] { Type.Missing }, Type.Missing); - InvokeWithTestData(typeof(MethodInfoDefaultParameters), "OptionalObjectParameter", new MethodInfoDefaultParameters(), new Missing[] { Missing.Value }, Missing.Value); + GC.Collect(); + X = x; + Y = y; + Z = z; + W = w; } - [Fact] - [ActiveIssue("https://github.com/mono/mono/issues/15025", TestRuntimes.Mono)] - public static void Invoke_OptionalParameterUnassingableFromMissing_WithMissingValue_ThrowsArgumentException() + public object FiveReferenceArguments(object first, object second, object third, object fourth, object fifth) { - AssertExtensions.Throws(null, () => GetMethod(typeof(MethodInfoDefaultParameters), "OptionalStringParameter").Invoke(new MethodInfoDefaultParameters(), new object[] { Type.Missing })); + GC.Collect(); + GC.KeepAlive(first); + GC.KeepAlive(second); + GC.KeepAlive(third); + GC.KeepAlive(fourth); + return fifth; } + } - [Fact] - public void Invoke_TwoParameters_CustomBinder_IncorrectTypeArguments() + internal static class IntrinsicInvokeStaticIntReturnTarget + { + public static string ReturnString(int value) { - MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt)); - var args = new object[] { "10", "100" }; - Assert.Equal(110, method.Invoke(null, BindingFlags.Default, new ConvertStringToIntBinder(), args, null)); - Assert.True(args[0] is int); - Assert.True(args[1] is int); + GC.Collect(); + return value.ToString(); } - [Fact] - public void Invoke_CustomBinder_ResultRequiringPrimitiveWidening_DoesNotCopyBackWidenedArgument() + public static Type? ReturnGeneric(int value) { - MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt)); - object[] args = new object[] { "10", 100 }; + GC.Collect(); + return value == 42 ? typeof(T) : null; + } + } - Assert.Equal(110, method.Invoke(null, BindingFlags.Default, new ConvertStringToInt16Binder(), args, null)); - Assert.Equal("10", Assert.IsType(args[0])); + internal static class IntrinsicInvokeGenericStaticIntReturnTarget where T : IComparable + { + public static IComparable ReturnComparable(int value) + { + T result = (T)(object)value; + GC.Collect(); + return result; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), new Type[] { typeof(int) })] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), new Type[] { typeof(string), typeof(int) })] - public void MakeGenericMethod(Type type, string name, Type[] typeArguments) + internal static class IntrinsicInvokeByRefTarget + { + public static bool ReturnFalse(IIntrinsicInvokeReference input, out IIntrinsicInvokeReference output) { - MethodInfo methodInfo = GetMethod(type, name); - MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(typeArguments); - Assert.True(genericMethodInfo.IsGenericMethod); - Assert.False(genericMethodInfo.IsGenericMethodDefinition); + GC.Collect(); + output = input; + return false; + } - MethodInfo genericMethodDefinition = genericMethodInfo.GetGenericMethodDefinition(); - Assert.Equal(methodInfo, genericMethodDefinition); - Assert.True(genericMethodDefinition.IsGenericMethod); - Assert.True(genericMethodDefinition.IsGenericMethodDefinition); + public static bool ReturnNull(object[] input, out Action? output) + { + GC.Collect(); + output = null; + GC.KeepAlive(input); + return true; } - [Fact] - public void MakeGenericMethod_Invalid() + public static bool ReturnTrue(Task input, out Task output) { - Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)).MakeGenericMethod(null)); // TypeArguments is null - Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2)).MakeGenericMethod(typeof(string), null)); // TypeArguments has null Type - Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt)).MakeGenericMethod(typeof(int))); // Method is non generic + GC.Collect(); + output = input; + return true; + } - // Number of typeArguments does not match - AssertExtensions.Throws(null, () => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)).MakeGenericMethod()); - AssertExtensions.Throws(null, () => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1)).MakeGenericMethod(typeof(string), typeof(int))); - AssertExtensions.Throws(null, () => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2)).MakeGenericMethod(typeof(int))); + public static bool ThrowAfterWrite(IIntrinsicInvokeReference input, out IIntrinsicInvokeReference output) + { + output = input; + GC.Collect(); + throw new InvalidOperationException(); } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), 1)] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), 2)] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong), 0)] - public void GetGenericArguments(Type type, string name, int expectedCount) + internal static class IntrinsicInvokeStaticReferenceTarget + { + public static object[] Return2(IIntrinsicInvokeReference reference, object[] values) { - MethodInfo methodInfo = GetMethod(type, name); - Type[] genericArguments = methodInfo.GetGenericArguments(); - Assert.Equal(expectedCount, genericArguments.Length); + GC.Collect(); + GC.KeepAlive(reference); + return values; } - [Fact] - public void GetGenericMethodDefinition_MethodNotGeneric_ThrowsInvalidOperationException() + public static Task Return3(Task task, Action callback, IIntrinsicInvokeReference reference) + { + GC.Collect(); + GC.KeepAlive(callback); + GC.KeepAlive(reference); + return task; + } + + public static Action Return4( + Action callback, + object[] values, + IIntrinsicInvokeReference reference, + Task task) + { + GC.Collect(); + GC.KeepAlive(values); + GC.KeepAlive(reference); + GC.KeepAlive(task); + return callback; + } + + public static void Void2(Action callback, IIntrinsicInvokeReference reference) + { + GC.Collect(); + callback(reference); + } + + public static void Void3(Task task, Action callback, IIntrinsicInvokeReference reference) + { + GC.Collect(); + callback(); + GC.KeepAlive(task); + GC.KeepAlive(reference); + } + + public static void Void4( + object[] values, + IIntrinsicInvokeReference reference, + Action callback, + Task task) + { + GC.Collect(); + callback(); + GC.KeepAlive(values); + GC.KeepAlive(reference); + GC.KeepAlive(task); + } + } + + internal sealed class IntrinsicInvokeActionTracker + { + internal int CallCount { get; private set; } + internal Action Callback => Invoke; + + private void Invoke() => CallCount++; + } + + internal class IntrinsicInvokeVirtualDispatchBase + { + public virtual object Dispatch(object value) + { + GC.Collect(); + GC.KeepAlive(value); + return "virtual-base"; + } + } + + internal sealed class IntrinsicInvokeVirtualDispatchA : IntrinsicInvokeVirtualDispatchBase + { + public override object Dispatch(object value) { - Assert.Throws(() => GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt)).GetGenericMethodDefinition()); + GC.Collect(); + GC.KeepAlive(value); + return "virtual-a"; } + } - [Fact] - public void Attributes() + internal sealed class IntrinsicInvokeVirtualDispatchB : IntrinsicInvokeVirtualDispatchBase + { + public override object Dispatch(object value) { - MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "ReturnVoidMethod"); - MethodAttributes attributes = methodInfo.Attributes; - Assert.True(attributes.HasFlag(MethodAttributes.Public)); + GC.Collect(); + GC.KeepAlive(value); + return "virtual-b"; } + } - [Fact] - public void CallingConvention() + internal abstract class IntrinsicInvokeAbstractDispatchBase + { + public abstract object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeAbstractDispatchA : IntrinsicInvokeAbstractDispatchBase + { + public override object Dispatch(object value) { - MethodInfo methodInfo = GetMethod(typeof(MI_SubClass), "ReturnVoidMethod"); - CallingConventions callingConvention = methodInfo.CallingConvention; - Assert.True(callingConvention.HasFlag(CallingConventions.HasThis)); + GC.Collect(); + GC.KeepAlive(value); + return "abstract-a"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - [InlineData(typeof(MI_AbstractBaseClass), nameof(MI_AbstractBaseClass.AbstractMethod), true)] - public void IsAbstract(Type type, string name, bool expected) + internal sealed class IntrinsicInvokeAbstractDispatchB : IntrinsicInvokeAbstractDispatchBase + { + public override object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsAbstract); + GC.Collect(); + GC.KeepAlive(value); + return "abstract-b"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsAssembly(Type type, string name, bool expected) + internal interface IIntrinsicInvokeInterfaceDispatch + { + object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeInterfaceDispatchA : IIntrinsicInvokeInterfaceDispatch + { + public object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsAssembly); + GC.Collect(); + GC.KeepAlive(value); + return "interface-a"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsConstructor(Type type, string name, bool expected) + internal sealed class IntrinsicInvokeInterfaceDispatchB : IIntrinsicInvokeInterfaceDispatch + { + public object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsConstructor); + GC.Collect(); + GC.KeepAlive(value); + return "interface-b"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsFamily(Type type, string name, bool expected) + internal interface IIntrinsicInvokeDefaultInterfaceDispatch + { + object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsFamily); + GC.Collect(); + GC.KeepAlive(value); + return "default"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsFamilyAndAssembly(Type type, string name, bool expected) + internal sealed class IntrinsicInvokeDefaultInterfaceDispatch : IIntrinsicInvokeDefaultInterfaceDispatch + { + } + + internal sealed class IntrinsicInvokeDefaultInterfaceOverride : IIntrinsicInvokeDefaultInterfaceDispatch + { + public object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsFamilyAndAssembly); + GC.Collect(); + GC.KeepAlive(value); + return "override"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsFamilyOrAssembly(Type type, string name, bool expected) + internal delegate object? IntrinsicInvokeStaticDynamicCallback(object? value); + internal delegate object? IntrinsicInvokeInstanceDynamicCallback(object? value); + internal delegate object? IntrinsicInvokeMulticastDynamicCallback(object? value); + + internal abstract class IntrinsicInvokeGenericVirtualDispatch + { + public abstract object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeGenericVirtualA : IntrinsicInvokeGenericVirtualDispatch + { + public override object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsFamilyOrAssembly); + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-generic-a"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - [InlineData(typeof(MI_AbstractSubClass), nameof(MI_AbstractSubClass.VirtualMethod), true)] - public void IsFinal(Type type, string name, bool expected) + internal sealed class IntrinsicInvokeGenericVirtualB : IntrinsicInvokeGenericVirtualDispatch + { + public override object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsFinal); + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-generic-b"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), false)] - public void IsGenericMethodDefinition(Type type, string name, bool expected) + internal interface IIntrinsicInvokeGenericInterfaceDispatch + { + object Dispatch(object value); + } + + internal sealed class IntrinsicInvokeGenericInterfaceA : IIntrinsicInvokeGenericInterfaceDispatch + { + public object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsGenericMethodDefinition); + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-interface-a"; } + } - [Theory] - [InlineData(typeof(MI_AbstractSubClass), nameof(MI_AbstractSubClass.AbstractMethod), true)] - public void IsHideBySig(Type type, string name, bool expected) + internal sealed class IntrinsicInvokeGenericInterfaceB : IIntrinsicInvokeGenericInterfaceDispatch + { + public object Dispatch(object value) { - Assert.Equal(expected, GetMethod(type, name).IsHideBySig); + GC.Collect(); + GC.KeepAlive(value); + return typeof(T).Name + "-interface-b"; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsPrivate(Type type, string name, bool expected) + internal sealed class IntrinsicInvokeDelegatePayload + { + internal List Log { get; } = new List(); + } + + internal sealed class IntrinsicInvokeDelegateCallbacks + { + private readonly string _label; + + internal IntrinsicInvokeDelegateCallbacks(string label) => _label = label; + + internal static object? Static(object? value) => Record(value, "static"); + + internal object? Instance(object? value) => Record(value, _label); + + internal static object? MulticastStatic(object? value) => Record(value, "static"); + + internal object? MulticastInstance(object? value) => Record(value, _label); + + private static object? Record(object? value, string label) { - Assert.Equal(expected, GetMethod(type, name).IsPrivate); + GC.Collect(); + ((IntrinsicInvokeDelegatePayload)value!).Log.Add(label); + return value; } + } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), true)] - public void IsPublic(Type type, string name, bool expected) + internal static class IntrinsicInvokeExcludedMethodTarget + { + public static object DateTimeArgument(DateTime value) { - Assert.Equal(expected, GetMethod(type, name).IsPublic); + GC.Collect(); + return value.Day; } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - public void IsSpecialName(Type type, string name, bool expected) + public static DateTime DateTimeResult() { - Assert.Equal(expected, GetMethod(type, name).IsSpecialName); + GC.Collect(); + return new DateTime(2026, 9, 10); } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), true)] - public void IsStatic(Type type, string name, bool expected) + public static object NullableArgument(int? value) { - Assert.Equal(expected, GetMethod(type, name).IsStatic); + GC.Collect(); + return value.GetValueOrDefault(); } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), false)] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VirtualReturnBoolMethod), true)] - public void IsVirtual(Type type, string name, bool expected) + public static int? NullableResult() { - Assert.Equal(expected, GetMethod(type, name).IsVirtual); + GC.Collect(); + return 43; } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StringArrayMethod))] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Increment))] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Decrement))] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Exchange))] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.CompareExchange))] - public void Name(Type type, string name) + public static object ValueTaskArgument(ValueTask value) { - MethodInfo mi = GetMethod(type, name); - Assert.Equal(name, mi.Name); + GC.Collect(); + return value.Result; } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt), typeof(int))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), typeof(void))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningInt), typeof(int))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ObjectMethodReturningString), typeof(string))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VirtualReturnStringArrayMethod), typeof(string[]))] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VirtualReturnBoolMethod), typeof(bool))] - public void ReturnType_ReturnParameter(Type type, string name, Type expected) + public static ValueTask ValueTaskResult() { - MethodInfo methodInfo = GetMethod(type, name); - Assert.Equal(expected, methodInfo.ReturnType); - - Assert.Equal(methodInfo.ReturnType, methodInfo.ReturnParameter.ParameterType); - Assert.Null(methodInfo.ReturnParameter.Name); - Assert.Equal(-1, methodInfo.ReturnParameter.Position); + GC.Collect(); + return new ValueTask(45); } - [Theory] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.VoidMethodReturningLong), "Int64 VoidMethodReturningLong()")] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.IntLongMethodReturningLong), "Int64 IntLongMethodReturningLong(Int32, Int64)")] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.StringArrayMethod), "Void StringArrayMethod(System.String[])")] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.ReturnVoidMethod), "Void ReturnVoidMethod(System.DateTime)")] - [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), "Void GenericMethod2[T,U](T, U)")] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Increment), "Int32 Increment(Int32 ByRef)")] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Decrement), "Int32 Decrement(Int32 ByRef)")] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.Exchange), "Int32 Exchange(Int32 ByRef, Int32)")] - [InlineData(typeof(MI_Interlocked), nameof(MI_Interlocked.CompareExchange), "Int32 CompareExchange(Int32 ByRef, Int32, Int32)")] - [InlineData(typeof(MI_GenericClass<>), nameof(MI_GenericClass.GenericMethod1), "T GenericMethod1(T)")] - [InlineData(typeof(MI_GenericClass<>), nameof(MI_GenericClass.GenericMethod2), "T GenericMethod2[S](S, T, System.String)")] - [InlineData(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod1), "System.String GenericMethod1(System.String)")] - [InlineData(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2), "System.String GenericMethod2[S](S, System.String, System.String)")] - public void ToStringTest(Type type, string name, string expected) + public static object CancellationTokenArgument(CancellationToken value) { - MethodInfo methodInfo = GetMethod(type, name); - Assert.Equal(expected, methodInfo.ToString()); + GC.Collect(); + return value.IsCancellationRequested; } - public static IEnumerable ToString_TestData() + public static CancellationToken CancellationTokenResult() { - MethodInfo genericMethodInfo = GetMethod(typeof(MI_GenericClass), nameof(MI_GenericClass.GenericMethod2)).MakeGenericMethod(new Type[] { typeof(DateTime) }); - yield return new object[] { genericMethodInfo, "System.String GenericMethod2[DateTime](System.DateTime, System.String, System.String)" }; + GC.Collect(); + return new CancellationToken(canceled: true); } - [Theory] - [MemberData(nameof(ToString_TestData))] - public void ToStringTest_ByMethodInfo(MethodInfo methodInfo, string expected) + public static bool ByRefValue(ref int value) { - Assert.Equal(expected, methodInfo.ToString()); + GC.Collect(); + value++; + return true; } - public static IEnumerable MethodNameAndArguments() + public static object TwoPrimitiveArguments(int first, int second) { - yield return new object[] { nameof(Sample.DefaultString), "Hello", "Hi" }; - yield return new object[] { nameof(Sample.DefaultNullString), null, "Hi" }; - yield return new object[] { nameof(Sample.DefaultNullableInt), 3, 5 }; - yield return new object[] { nameof(Sample.DefaultNullableEnum), YesNo.Yes, YesNo.No }; + GC.Collect(); + return first + second; } - [Theory] - [MemberData(nameof(MethodNameAndArguments))] - public static void InvokeCopiesBackMissingArgument(string methodName, object defaultValue, object passingValue) + public static object FiveReferenceArguments( + object first, + object second, + object third, + object fourth, + object fifth) { - MethodInfo method = typeof(Sample).GetMethod(methodName); - object[] args = new object[] { Missing.Value }; + GC.Collect(); + GC.KeepAlive(first); + GC.KeepAlive(second); + GC.KeepAlive(third); + GC.KeepAlive(fourth); + return fifth; + } + } - Assert.Equal(defaultValue, method.Invoke(null, args)); - Assert.Equal(defaultValue, args[0]); + internal interface IIntrinsicInvokeStructReceiver + { + object GetValue(); + } - args[0] = passingValue; + internal readonly struct IntrinsicInvokeStructReceiver : IIntrinsicInvokeStructReceiver + { + internal IntrinsicInvokeStructReceiver(int value) => Value = value; - Assert.Equal(passingValue, method.Invoke(null, args)); - Assert.Equal(passingValue, args[0]); + internal int Value { get; } - args[0] = null; - Assert.Null(method.Invoke(null, args)); - Assert.Null(args[0]); + public object GetValue() + { + GC.Collect(); + return Value; } - [Fact] - public static void InvokeCopiesBackMissingParameterAndArgument() + public override string ToString() { - MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.DefaultMissing)); - object[] args = new object[] { Missing.Value }; - - Assert.Null(method.Invoke(null, args)); - Assert.Null(args[0]); - - args[0] = null; - Assert.Null(method.Invoke(null, args)); - Assert.Null(args[0]); + GC.Collect(); + return Value.ToString(); } + } - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/50957", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))] - public static void CallStackFrame_AggressiveInlining() + internal sealed class IntrinsicInvokeEnumResultTarget + { + public IntrinsicInvokeInt32Enum InstanceResult() { - MethodInfo mi = typeof(System.Reflection.TestAssembly.ClassToInvoke).GetMethod(nameof(System.Reflection.TestAssembly.ClassToInvoke.CallMe_AggressiveInlining), - BindingFlags.Public | BindingFlags.Static)!; - - // Although the target method has AggressiveInlining, currently reflection should not inline the target into any generated IL. - FirstCall(mi); - SecondCall(mi); + GC.Collect(); + return IntrinsicInvokeInt32Enum.Value; } - [MethodImpl(MethodImplOptions.NoInlining)] // Separate non-inlineable method to aid any test failures - private static void FirstCall(MethodInfo mi) + public static IntrinsicInvokeInt64Enum StaticResult() { - Assembly asm = (Assembly)mi.Invoke(null, null); - Assert.Contains("TestAssembly", asm.ToString()); + GC.Collect(); + return IntrinsicInvokeInt64Enum.Value; } + } - [MethodImpl(MethodImplOptions.NoInlining)] // Separate non-inlineable method to aid any test failures - private static void SecondCall(MethodInfo mi) + internal sealed class IntrinsicInvokeArgumentValidationTarget + { + public void Reference(IIntrinsicInvokeReference value) { - Assembly asm = (Assembly)mi.Invoke(null, null); - Assert.Contains("TestAssembly", asm.ToString()); + GC.Collect(); + GC.KeepAlive(value); } - //Methods for Reflection Metadata - private void DummyMethod1(string str, int iValue, long lValue) + public void Primitive(int value) { + GC.Collect(); + GC.KeepAlive(value); } - private void DummyMethod2() + public void Enum(IntrinsicInvokeInt32Enum value) { + GC.Collect(); + GC.KeepAlive(value); } } + internal enum IntrinsicInvokeByteEnum : byte + { + Value = 211 + } + + internal enum IntrinsicInvokeSByteEnum : sbyte + { + Value = -91 + } + + internal enum IntrinsicInvokeInt16Enum : short + { + Value = -30001 + } + + internal enum IntrinsicInvokeUInt16Enum : ushort + { + Value = 60001 + } + + internal enum IntrinsicInvokeInt32Enum : int + { + Value = -123456789 + } + + internal enum IntrinsicInvokeUInt32Enum : uint + { + Value = 0xFEDCBA98 + } + + internal enum IntrinsicInvokeInt64Enum : long + { + Value = -1234567890123456789 + } + + internal enum IntrinsicInvokeUInt64Enum : ulong + { + Value = 0xFEDCBA9876543210 + } + #pragma warning disable 0414 public interface MI_Interface { diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs index 94d701507eac1d..731df6fa96f510 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInvokerTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.CompilerServices; using Xunit; namespace System.Reflection.Tests @@ -18,6 +19,87 @@ public class MethodInvokerTests : MethodCommonTests protected override bool SupportsMissing => false; + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SharedThunk_CachedInvokerPromotes(bool useByRef) + { + MethodInfo method = typeof(CachedInvokerTarget).GetMethod( + useByRef ? nameof(CachedInvokerTarget.TryGetValue) : nameof(CachedInvokerTarget.Echo))!; + MethodInvoker invoker = MethodInvoker.Create(method); + var target = new CachedInvokerTarget(); + object argument = new object(); + object?[] arguments = { target, null }; + + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + { + if (useByRef) + { + Assert.Equal(true, invoker.Invoke(null, arguments.AsSpan())); + Assert.Same(target, arguments[1]); + } + else + { + Assert.Same(argument, invoker.Invoke(target, argument)); + } + + if (i == 0 || i == IntrinsicInvokeSelectionAssertions.SpecializationThreshold - 1) + { + IntrinsicInvokeSelectionAssertions.AssertNotPromoted(invoker, i + 1); + } + } + + Assert.Equal(IntrinsicInvokeSelectionAssertions.SpecializationThreshold + 1, target.CallCount); + IntrinsicInvokeSelectionAssertions.AssertPromoted(invoker); + } + + [Fact] + public void SharedThunk_ObjectMethodOnBoxedValueReceiverFallsBack() + { + MethodInfo method = typeof(object).GetMethod(nameof(object.ToString))!; + MethodInvoker invoker = MethodInvoker.Create(method); + + Assert.Equal("50", invoker.Invoke(new IntrinsicInvokeStructReceiver(50))); + IntrinsicInvokeSelectionAssertions.AssertFallback(invoker); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Constructor_ExistingInstanceAcrossTiers(bool useSpan) + { + ConstructorInfo constructor = typeof(RefConstructorTarget).GetConstructor(new[] { typeof(int).MakeByRefType() }); + MethodInvoker invoker = MethodInvoker.Create(constructor); + var target = (RefConstructorTarget)RuntimeHelpers.GetUninitializedObject(typeof(RefConstructorTarget)); + + for (int i = 0; i <= IntrinsicInvokeSelectionAssertions.SpecializationThreshold; i++) + { + if (useSpan) + { + object[] arguments = { i }; + Assert.Null(invoker.Invoke(target, arguments.AsSpan())); + Assert.Equal(i + 1, arguments[0]); + } + else + { + Assert.Null(invoker.Invoke(target, i)); + } + + Assert.Equal(i, target.Value); + } + } + + public sealed class RefConstructorTarget + { + public int Value; + + public RefConstructorTarget(ref int value) + { + Value = value; + value++; + } + } + [Fact] public void NullTypeValidation() { @@ -297,6 +379,27 @@ public void VerifyThisObj_Null() public static IEnumerable Invoke_TestData() => MethodInfoTests.Invoke_TestData(); + private sealed class CachedInvokerTarget + { + internal int CallCount { get; private set; } + + public static bool TryGetValue(CachedInvokerTarget target, out object result) + { + result = target.Echo(target); + return true; + } + + public object Echo(object value) + { + if (CallCount++ == 0) + { + GC.Collect(); + } + + return value; + } + } + private class TestClass { private int _i = 42;