From 5195418426468d13b042ae079c65aa05595295b4 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 9 May 2022 09:11:28 -0500 Subject: [PATCH] Add IL Emit support for MethodInfo.Invoke() and friends (#67917) --- .../System.Private.CoreLib.csproj | 5 +- .../Reflection/ConstructorInvoker.CoreCLR.cs | 18 ++ .../System/Reflection/Emit/DynamicMethod.cs | 74 +++---- .../Reflection/Emit/DynamicMethodInvoker.cs | 24 --- .../Reflection/MethodInvoker.CoreCLR.cs | 33 +++ .../src/System/Reflection/RtFieldInfo.cs | 2 +- .../RuntimeConstructorInfo.CoreCLR.cs | 21 -- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 31 +-- .../src/System/RuntimeHandles.cs | 19 ++ .../src/System/RuntimeType.CoreCLR.cs | 201 +++++++++--------- src/coreclr/vm/appdomain.cpp | 12 +- src/coreclr/vm/corelib.h | 1 - src/coreclr/vm/ecalllist.h | 2 + src/coreclr/vm/invokeutil.cpp | 26 +-- src/coreclr/vm/methodtable.h | 1 - src/coreclr/vm/methodtable.inl | 25 --- src/coreclr/vm/object.cpp | 108 +--------- src/coreclr/vm/object.h | 1 - src/coreclr/vm/reflectioninvocation.cpp | 188 +++++++--------- src/coreclr/vm/reflectioninvocation.h | 2 +- src/coreclr/vm/runtimehandles.h | 3 + .../tests/StackFrameTests.cs | 5 +- .../System.Private.CoreLib.Shared.projitems | 2 + .../System/Reflection/ConstructorInvoker.cs | 88 +++++++- .../src/System/Reflection/InvokeUtils.cs | 4 +- .../src/System/Reflection/InvokerEmitUtil.cs | 187 ++++++++++++++++ .../src/System/Reflection/MethodBase.cs | 57 +++-- .../src/System/Reflection/MethodInvoker.cs | 73 ++++++- .../Reflection/ParameterCopyBackAction.cs | 15 ++ .../Reflection/RuntimeConstructorInfo.cs | 108 +++++++--- .../System/Reflection/RuntimeMethodInfo.cs | 54 +++-- .../tests/MethodInfoTests.cs | 98 +++++++++ .../tests/BinaryFormatterTests.cs | 15 +- .../tests/System/Reflection/PointerTests.cs | 74 +++++++ .../System.Private.CoreLib.csproj | 4 +- .../Reflection/ConstructorInvoker.Mono.cs | 46 ++++ .../System/Reflection/MethodInvoker.Mono.cs | 66 ++++++ .../src/System/Reflection/RuntimeFieldInfo.cs | 2 +- .../Reflection/RuntimeMethodInfo.Mono.cs | 80 ------- .../src/System/RuntimeMethodHandle.cs | 3 + .../src/System/RuntimeType.Mono.cs | 24 +-- .../BinderTracingTest.EventHandlers.cs | 4 +- .../BinderTracingTest.ResolutionFlow.cs | 19 +- .../binding/tracing/BinderTracingTest.cs | 13 +- src/tests/issues.targets | 12 ++ 45 files changed, 1196 insertions(+), 654 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs create mode 100644 src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs create mode 100644 src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 36611ae871642..28ad43af18b66 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + false @@ -155,12 +155,12 @@ + - @@ -187,6 +187,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 new file mode 100644 index 0000000000000..05f69d2670e3e --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + internal partial class ConstructorInvoker + { + public InvocationFlags _invocationFlags; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe object? InterpretedInvoke(object? obj, IntPtr* arguments) + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _method.Signature, isConstructor: obj is null)!; + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs index 24641e490a1ad..bc1966d1e3bfa 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs @@ -23,7 +23,7 @@ public sealed class DynamicMethod : MethodInfo private RuntimeModule m_module = null!; internal bool m_skipVisibility; internal RuntimeType? m_typeOwner; // can be null - private DynamicMethodInvoker? _invoker; + private MethodInvoker? _invoker; private Signature? _signature; // We want the creator of the DynamicMethod to control who has access to the @@ -434,13 +434,12 @@ internal RuntimeMethodHandle GetMethodDescriptor() public override bool IsSecurityTransparent => false; - private DynamicMethodInvoker Invoker + private MethodInvoker Invoker { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - _invoker ??= new DynamicMethodInvoker(this); - + _invoker ??= new MethodInvoker(this, Signature); return _invoker; } } @@ -490,7 +489,7 @@ Signature LazyCreateSignature() { if (argCount == 0) { - retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr); + retValue = Invoker.InlinedInvoke(obj, args: default, invokeAttr); } else if (argCount > MaxStackAllocArgCount) { @@ -501,8 +500,8 @@ Signature LazyCreateSignature() { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new Span(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + Span copyOfParameters = new(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; @@ -517,14 +516,25 @@ Signature LazyCreateSignature() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr); + retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } } @@ -546,15 +556,15 @@ Signature LazyCreateSignature() CultureInfo? culture) { object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + Span copyOfParameters = new(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which // naturally requires a stack size that is dependent on the arg count\size. IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; Buffer.ZeroMemory((byte*)pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, argCount); + ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; + Span shouldCopyBackParameters = new(copyBackActions, argCount); GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); @@ -572,7 +582,7 @@ Signature LazyCreateSignature() culture, invokeAttr); - retValue = mi.Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr); + retValue = mi.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); } finally { @@ -582,36 +592,26 @@ Signature LazyCreateSignature() // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } return retValue; } - [DebuggerHidden] - [DebuggerStepThrough] - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); - } - catch (Exception e) - { - throw new TargetInvocationException(e); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); - } - } - public override object[] GetCustomAttributes(Type attributeType, bool inherit) { return m_dynMethod.GetCustomAttributes(attributeType, inherit); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs deleted file mode 100644 index 9e5a5e17be4e8..0000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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; -using System.Runtime.CompilerServices; - -namespace System.Reflection.Emit -{ - internal sealed partial class DynamicMethodInvoker - { - private readonly DynamicMethod _dynamicMethod; - - public DynamicMethodInvoker(DynamicMethod dynamicMethod) - { - _dynamicMethod = dynamicMethod; - } - - public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) - { - // Todo: add strategy for calling IL Emit-based version - return _dynamicMethod.InvokeNonEmitUnsafe(obj, args, invokeAttr); - } - } -} 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 new file mode 100644 index 0000000000000..6acf77be549c0 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + internal partial class MethodInvoker + { + private readonly Signature _signature; + internal InvocationFlags _invocationFlags; + + public MethodInvoker(MethodBase method, Signature signature) + { + _method = method; + _signature = signature; + +#if USE_NATIVE_INVOKE + // Always use the native invoke; useful for testing. + _strategyDetermined = true; +#elif USE_EMIT_INVOKE + // Always use emit invoke (if IsDynamicCodeCompiled == true); useful for testing. + _invoked = true; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe object? InterpretedInvoke(object? obj, IntPtr* arguments) + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _signature, isConstructor: false); + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index 8eb09a7756e8f..5831eca455adc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -236,7 +236,7 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); - bool _ref = false; + ParameterCopyBackAction _ref = default; RuntimeType fieldType = (RuntimeType)FieldType; if (value is null) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs index 5b6efa638e9b2..51ee0061420e0 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs @@ -95,27 +95,6 @@ Signature LazyCreateSignature() internal BindingFlags BindingFlags => m_bindingFlags; - - [DebuggerStepThrough] - [DebuggerHidden] - internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, Signature, isConstructor: obj is null)!; - } - catch (Exception ex) - { - throw new TargetInvocationException(ex); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, Signature, isConstructor: obj is null)!; - } - } #endregion #region Object Overrides diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index c80aa9af08a76..229e2981bb6f2 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -48,7 +48,7 @@ private MethodInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - m_invoker ??= new MethodInvoker(this); + m_invoker ??= new MethodInvoker(this, Signature); return m_invoker; } } @@ -351,7 +351,7 @@ public override MethodImplAttributes GetMethodImplementationFlags() StackAllocedArguments argStorage = default; Span copyOfParameters = new(ref argStorage._arg0, 1); ReadOnlySpan parameters = new(in parameter); - Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); StackAllocatedByRefs byrefStorage = default; IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; @@ -366,33 +366,16 @@ public override MethodImplAttributes GetMethodImplementationFlags() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + retValue = Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr); +#else + retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); +#endif } return retValue; } - [DebuggerHidden] - [DebuggerStepThrough] - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); - } - catch (Exception e) - { - throw new TargetInvocationException(e); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); - } - } - #endregion #region MethodInfo Overrides diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 166ecd396186f..fe506be715395 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -138,6 +138,19 @@ internal static bool IsByRef(RuntimeType type) return corElemType == CorElementType.ELEMENT_TYPE_BYREF; } + internal static bool TryGetByRefElementType(RuntimeType type, [NotNullWhen(true)] out RuntimeType? elementType) + { + CorElementType corElemType = GetCorElementType(type); + if (corElemType == CorElementType.ELEMENT_TYPE_BYREF) + { + elementType = GetElementType(type); + return true; + } + + elementType = null; + return false; + } + internal static bool IsPointer(RuntimeType type) { CorElementType corElemType = GetCorElementType(type); @@ -981,6 +994,12 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method) [MethodImpl(MethodImplOptions.InternalCall)] internal static extern object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern object? ReboxFromNullable(object? src); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern object ReboxToNullable(object? src, RuntimeType destNullableType); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetMethodInstantiation")] private static partial void GetMethodInstantiation(RuntimeMethodHandleInternal method, ObjectHandleOnStack types, Interop.BOOL fAsRuntimeTypeArray); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index ed4c5630b1e05..374cdcf662b84 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3495,7 +3495,7 @@ public override Type MakeArrayType(int rank) private static extern bool CanValueSpecialCast(RuntimeType valueType, RuntimeType targetType); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object AllocateValueType(RuntimeType type, object? value, bool fForceTypeChange); + private static extern object AllocateValueType(RuntimeType type, object? value); private enum CheckValueStatus { @@ -3504,26 +3504,13 @@ private enum CheckValueStatus NotSupported_ByRefLike } -#if DEBUG - internal void VerifyValueType(object? value) - { - Debug.Assert(value != null); - Debug.Assert( - value.GetType() == this || - (IsPointer && value.GetType() == typeof(IntPtr)) || - (IsByRef && value.GetType() == RuntimeTypeHandle.GetElementType(this)) || - (value.GetType().IsEnum && GetUnderlyingType((RuntimeType)value.GetType()) == GetUnderlyingType(this)) || - (IsEnum && GetUnderlyingType((RuntimeType)value.GetType()) == GetUnderlyingType(this))); - } -#endif - /// /// Verify and optionally convert the value for special cases. /// - /// True if the value should be considered a value type, False otherwise + /// True if is a value type, False otherwise internal bool CheckValue( ref object? value, - ref bool copyBack, + ref ParameterCopyBackAction copyBack, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) @@ -3531,21 +3518,23 @@ internal bool CheckValue( // Already fast-pathed by the caller. Debug.Assert(!ReferenceEquals(value?.GetType(), this)); + // Since this cannot be a generic parameter, we use RuntimeTypeHandle.IsValueType here + // because it is faster than IsValueType + Debug.Assert(!IsGenericParameter); + // Fast path to whether a value can be assigned without conversion. if (IsInstanceOfType(value)) { - // Since this cannot be a generic parameter, we use RuntimeTypeHandle.IsValueType here - // because it is faster than IsValueType - Debug.Assert(!IsGenericParameter); - - if (RuntimeTypeHandle.IsValueType(this)) + if (IsNullableOfT) { - // Nullable is the only value type that will get here. - Debug.Assert(IsNullableOfT); - - // Fall through and treat as a reference type. + // Pass as a true boxed Nullable, not as a T or null. + value = RuntimeMethodHandle.ReboxToNullable(value, this); + return true; } + // Other value types won't get here since Type equality was previous checked. + Debug.Assert(!RuntimeTypeHandle.IsValueType(this)); + return false; } @@ -3566,8 +3555,18 @@ internal bool CheckValue( value = binder.ChangeType(value, this, culture); if (IsInstanceOfType(value)) { - copyBack = true; - return IsValueType; + if (IsNullableOfT) + { + // Pass as a true boxed Nullable, not as a T or null. + value = RuntimeMethodHandle.ReboxToNullable(value, this); + copyBack = ParameterCopyBackAction.CopyNullable; + } + else + { + copyBack = ParameterCopyBackAction.Copy; + } + + return IsValueType; // Note the call to IsValueType, not the variable. } result = TryChangeType(ref value, out copyBack, out isValueType); @@ -3592,116 +3591,94 @@ internal bool CheckValue( private CheckValueStatus TryChangeType( ref object? value, - out bool copyBack, + out ParameterCopyBackAction copyBack, out bool isValueType) { - // If this is a ByRef get the element type and check if it's compatible - if (IsByRef) + RuntimeType? sigElementType; + if (RuntimeTypeHandle.TryGetByRefElementType(this, out sigElementType)) { - RuntimeType sigElementType = RuntimeTypeHandle.GetElementType(this); - - // If a nullable, pass the object even though it's a value type. - if (sigElementType.IsNullableOfT) - { - // Treat as a reference type since a null value may be replaced with T or vise-versa. - isValueType = false; - copyBack = true; - return CheckValueStatus.Success; - } + copyBack = ParameterCopyBackAction.Copy; + Debug.Assert(!sigElementType.IsGenericParameter); if (sigElementType.IsInstanceOfType(value)) { - // Need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type - value = AllocateValueType(sigElementType, value, fForceTypeChange: false); - isValueType = sigElementType.IsValueType; - copyBack = true; - return CheckValueStatus.Success; - } - - if (value == null) - { - if (IsByRefLike) + isValueType = RuntimeTypeHandle.IsValueType(sigElementType); + if (isValueType) { - isValueType = copyBack = default; - return CheckValueStatus.NotSupported_ByRefLike; + if (sigElementType.IsNullableOfT) + { + // Pass as a true boxed Nullable, not as a T or null. + value = RuntimeMethodHandle.ReboxToNullable(value, sigElementType); + copyBack = ParameterCopyBackAction.CopyNullable; + } + else + { + // Make a copy to prevent the boxed instance from being directly modified by the method. + value = AllocateValueType(sigElementType, value); + } } - value = AllocateValueType(sigElementType, value, fForceTypeChange: false); - isValueType = sigElementType.IsValueType; - copyBack = true; return CheckValueStatus.Success; } - if (NeedsSpecialCast()) + if (value == null) { - if (SpecialCast(sigElementType, ref value) == CheckValueStatus.Success) + isValueType = RuntimeTypeHandle.IsValueType(sigElementType); + if (!isValueType) { - isValueType = true; - copyBack = false; + // Normally we don't get here since 'null' was previosuly checked, but due to binders we can. return CheckValueStatus.Success; } + + if (sigElementType.IsByRefLike) + { + return CheckValueStatus.NotSupported_ByRefLike; + } + + // Allocate default. + value = AllocateValueType(sigElementType, value: null); + copyBack = sigElementType.IsNullableOfT ? ParameterCopyBackAction.CopyNullable : ParameterCopyBackAction.Copy; + return CheckValueStatus.Success; } - isValueType = copyBack = default; + isValueType = false; return CheckValueStatus.ArgumentException; } if (value == null) { - if (!RuntimeTypeHandle.IsValueType(this)) - { - isValueType = false; - copyBack = false; - return CheckValueStatus.Success; - } - - if (IsNullableOfT) + copyBack = ParameterCopyBackAction.None; + isValueType = RuntimeTypeHandle.IsValueType(this); + if (!isValueType) { - // Treat as a boxed value. - isValueType = false; - copyBack = false; + // Normally we don't get here since 'null' was previosuly checked, but due to binders we can. return CheckValueStatus.Success; } - if (RuntimeTypeHandle.IsByRefLike(this)) + if (IsByRefLike) { - isValueType = copyBack = default; return CheckValueStatus.NotSupported_ByRefLike; } - // Need to create a default instance of the value type. - value = AllocateValueType(this, value: null, fForceTypeChange: false); - isValueType = true; - copyBack = false; + // Allocate default. + value = AllocateValueType(this, value: null); return CheckValueStatus.Success; } - if (NeedsSpecialCast()) - { - if (SpecialCast(this, ref value) == CheckValueStatus.Success) - { - isValueType = true; - copyBack = false; - return CheckValueStatus.Success; - } - } - - isValueType = copyBack = default; - return CheckValueStatus.ArgumentException; - // Check the strange ones courtesy of reflection: - // - implicit cast between primitives - // - enum treated as underlying type - // - IntPtr and System.Reflection.Pointer to pointer types - bool NeedsSpecialCast() => IsPointer || IsEnum || IsPrimitive; - - static CheckValueStatus SpecialCast(RuntimeType type, ref object value) + // - Implicit cast between primitives + // - Enum treated as underlying type + // - Pointer (*) types to IntPtr (if dest is IntPtr) + // - System.Reflection.Pointer to appropriate pointer (*) type (if dest is pointer type) + if (IsPointer || IsEnum || IsPrimitive) { Pointer? pointer = value as Pointer; RuntimeType srcType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); - if (!CanValueSpecialCast(srcType, type)) + if (!CanValueSpecialCast(srcType, this)) { + isValueType = false; + copyBack = ParameterCopyBackAction.None; return CheckValueStatus.ArgumentException; } @@ -3712,18 +3689,42 @@ static CheckValueStatus SpecialCast(RuntimeType type, ref object value) else { CorElementType srcElementType = GetUnderlyingType(srcType); - CorElementType dstElementType = GetUnderlyingType(type); + CorElementType dstElementType = GetUnderlyingType(this); if (dstElementType != srcElementType) { - value = InvokeUtils.ConvertOrWiden(srcType, srcElementType, value, type, dstElementType); + value = InvokeUtils.ConvertOrWiden(srcType, srcElementType, value, this, dstElementType); } } + isValueType = true; + copyBack = ParameterCopyBackAction.None; return CheckValueStatus.Success; } + + isValueType = false; + copyBack = ParameterCopyBackAction.None; + return CheckValueStatus.ArgumentException; + } + + internal bool TryByRefFastPath(ref object arg, ref bool isValueType) + { + if (RuntimeTypeHandle.TryGetByRefElementType(this, out RuntimeType? sigElementType) && + ReferenceEquals(sigElementType, arg.GetType())) + { + isValueType = sigElementType.IsValueType; + if (isValueType) + { + // Make a copy to prevent the boxed instance from being directly modified by the method. + arg = AllocateValueType(sigElementType, arg); + } + + return true; + } + + return false; } - private static CorElementType GetUnderlyingType(RuntimeType type) + internal static CorElementType GetUnderlyingType(RuntimeType type) { if (type.IsEnum) { diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index e8531d7bc42ce..c0741f5c50ee6 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1557,7 +1557,6 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) CLASS__MULTICAST_DELEGATE, CLASS__METHOD_INVOKER, CLASS__CONSTRUCTOR_INVOKER, - CLASS__DYNAMIC_METHOD_INVOKER }; static bool fInited = false; @@ -1580,6 +1579,13 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) if (CoreLibBinder::GetExistingClass(reflectionInvocationTypes[i]) == pCaller) return true; } + + // Check for dynamically generated Invoke methods. + if (pMeth->IsDynamicMethod()) + { + if (strncmp(pMeth->GetName(), "InvokeStub_", ARRAY_SIZE("InvokeStub_") - 1) == 0) + return true; + } } return false; @@ -1744,8 +1750,6 @@ StackWalkAction SystemDomain::CallersMethodCallbackWithStackMark(CrawlFrame* pCf Frame* frame = pCf->GetFrame(); _ASSERTE(pCf->IsFrameless() || frame); - - // Skipping reflection frames. We don't need to be quite as exhaustive here // as the security or reflection stack walking code since we know this logic // is only invoked for selected methods in CoreLib itself. So we're @@ -1819,10 +1823,8 @@ StackWalkAction SystemDomain::CallersMethodCallback(CrawlFrame* pCf, VOID* data) pCaller->skip--; return SWA_CONTINUE; } - } - void AppDomain::Create() { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 5ad8c85bc862b..7000bd2447967 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -485,7 +485,6 @@ DEFINE_CLASS(MEMBER, Reflection, MemberInfo) DEFINE_CLASS(METHOD_INVOKER, Reflection, MethodInvoker) DEFINE_CLASS(CONSTRUCTOR_INVOKER, Reflection, ConstructorInvoker) -DEFINE_CLASS(DYNAMIC_METHOD_INVOKER,ReflectionEmit, DynamicMethodInvoker) DEFINE_CLASS_U(Reflection, RuntimeMethodInfo, NoClass) DEFINE_FIELD_U(m_handle, ReflectMethodObject, m_pMD) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 593284165f165..ccb31d35cd8f7 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -217,6 +217,8 @@ FCFuncEnd() FCFuncStart(gRuntimeMethodHandle) FCFuncElement("_GetCurrentMethod", RuntimeMethodHandle::GetCurrentMethod) FCFuncElement("InvokeMethod", RuntimeMethodHandle::InvokeMethod) + FCFuncElement("ReboxFromNullable", RuntimeMethodHandle::ReboxFromNullable) + FCFuncElement("ReboxToNullable", RuntimeMethodHandle::ReboxToNullable) FCFuncElement("GetImplAttributes", RuntimeMethodHandle::GetImplAttributes) FCFuncElement("GetAttributes", RuntimeMethodHandle::GetAttributes) FCFuncElement("GetDeclaringType", RuntimeMethodHandle::GetDeclaringType) diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index e50cdc7f1511d..944490f280b72 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -180,20 +180,8 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest) { case ELEMENT_TYPE_VALUETYPE: { - if (Nullable::IsNullableType(th)) - { - // ASSUMPTION: we only receive T or NULL values, not Nullable values - // and the values are boxed, unlike other value types. - MethodTable* pMT = th.AsMethodTable(); - OBJECTREF src = (OBJECTREF)(Object*)*(PVOID*)argRef; - if (!pMT->UnBoxIntoArg(argDest, src)) - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); - } - else - { - MethodTable* pMT = th.GetMethodTable(); - CopyValueClassArg(argDest, argRef, pMT, 0); - } + MethodTable* pMT = th.GetMethodTable(); + CopyValueClassArg(argDest, argRef, pMT, 0); break; } @@ -213,10 +201,6 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest) { case ELEMENT_TYPE_BYREF: { - // We should never get here for nullable types. Instead invoke - // heads these off and morphs the type handle to not be byref anymore - _ASSERTE(!Nullable::IsNullableType(th.AsTypeDesc()->GetTypeParam())); - *(PVOID *)pArgDst = argRef; break; } @@ -1076,8 +1060,8 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ CopyValueClass(obj->GetData(), p, fieldType.AsMethodTable()); } - // If it is a Nullable, box it using Nullable conventions. - // TODO: this double allocates on constructions which is wastefull + // If it is a Nullable, box it using Nullable conventions. + // TODO: this double allocates on constructions which is wastefull obj = Nullable::NormalizeBox(obj); break; } @@ -1115,5 +1099,3 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ return obj; } - - diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 38e2d9c71e7ad..52eb8f9095569 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -2390,7 +2390,6 @@ class MethodTable OBJECTREF FastBox(void** data); #ifndef DACCESS_COMPILE BOOL UnBoxInto(void *dest, OBJECTREF src); - BOOL UnBoxIntoArg(ArgDestination *argDest, OBJECTREF src); void UnBoxIntoUnchecked(void *dest, OBJECTREF src); #endif diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index 72b628dae9545..049e4da57f3e1 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -1282,31 +1282,6 @@ inline BOOL MethodTable::UnBoxInto(void *dest, OBJECTREF src) return TRUE; } -//========================================================================================== -// unbox src into argument, making sure src is of the correct type. - -inline BOOL MethodTable::UnBoxIntoArg(ArgDestination *argDest, OBJECTREF src) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - if (Nullable::IsNullableType(TypeHandle(this))) - return Nullable::UnBoxIntoArgNoGC(argDest, src, this); - else - { - if (src == NULL || src->GetMethodTable() != this) - return FALSE; - - CopyValueClassArg(argDest, src->UnBox(), this, 0); - } - return TRUE; -} - //========================================================================================== // unbox src into dest, No checks are done diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 451bb1e5dbea9..83ac8e718c337 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -929,7 +929,6 @@ BOOL StringObject::CaseInsensitiveCompHelper(_In_reads_(aLength) WCHAR *strAChar // Next char strAChars++; strBChars++; } - } /*============================InternalTrailByteCheck============================ @@ -1617,7 +1616,7 @@ void* Nullable::ValueAddr(MethodTable* nullableMT) { } //=============================================================================== -// Special Logic to box a nullable as a boxed +// Special logic to box a nullable as a boxed OBJECTREF Nullable::Box(void* srcPtr, MethodTable* nullableMT) { @@ -1634,7 +1633,7 @@ OBJECTREF Nullable::Box(void* srcPtr, MethodTable* nullableMT) Nullable* src = (Nullable*) srcPtr; _ASSERTE(IsNullableType(nullableMT)); - // We better have a concrete instantiation, or our field offset asserts are not useful + // We better have a concrete instantiation, or our field offset asserts are not useful _ASSERTE(!nullableMT->ContainsGenericVariables()); if (!*src->HasValueAddr(nullableMT)) @@ -1665,10 +1664,10 @@ BOOL Nullable::UnBox(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) Nullable* dest = (Nullable*) destPtr; BOOL fRet = TRUE; - // We should only get here if we are unboxing a T as a Nullable + // We should only get here if we are unboxing a T as a Nullable _ASSERTE(IsNullableType(destMT)); - // We better have a concrete instantiation, or our field offset asserts are not useful + // We better have a concrete instantiation, or our field offset asserts are not useful _ASSERTE(!destMT->ContainsGenericVariables()); if (boxedVal == NULL) @@ -1754,101 +1753,6 @@ BOOL Nullable::UnBoxNoGC(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) return TRUE; } -//=============================================================================== -// Special Logic to unbox a boxed T as a nullable into an argument -// specified by the argDest. -// Does not handle type equivalence (may conservatively return FALSE) -BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, MethodTable* destMT) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - -#if defined(UNIX_AMD64_ABI) - if (argDest->IsStructPassedInRegs()) - { - // We should only get here if we are unboxing a T as a Nullable - _ASSERTE(IsNullableType(destMT)); - - // We better have a concrete instantiation, or our field offset asserts are not useful - _ASSERTE(!destMT->ContainsGenericVariables()); - - if (boxedVal == NULL) - { - // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure because it may contain GC references - // and these need to be initialized to zero. (could optimize in the non-GC case) - InitValueClassArg(argDest, destMT); - } - else - { - if (!IsNullableForTypeNoGC(destMT, boxedVal->GetMethodTable())) - { - // For safety's sake, also allow true nullables to be unboxed normally. - // This should not happen normally, but we want to be robust - if (destMT == boxedVal->GetMethodTable()) - { - CopyValueClassArg(argDest, boxedVal->GetData(), destMT, 0); - return TRUE; - } - return FALSE; - } - - Nullable* dest = (Nullable*)argDest->GetStructGenRegDestinationAddress(); - *dest->HasValueAddr(destMT) = true; - int destOffset = (BYTE*)dest->ValueAddr(destMT) - (BYTE*)dest; - CopyValueClassArg(argDest, boxedVal->UnBox(), boxedVal->GetMethodTable(), destOffset); - } - return TRUE; - } - -#endif // UNIX_AMD64_ABI - -#if defined(TARGET_LOONGARCH64) - if (argDest->IsStructPassedInRegs()) - { - // We should only get here if we are unboxing a T as a Nullable - _ASSERTE(IsNullableType(destMT)); - - // We better have a concrete instantiation, or our field offset asserts are not useful - _ASSERTE(!destMT->ContainsGenericVariables()); - - if (boxedVal == NULL) - { - // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure becasue it may contain GC references - // and these need to be initialized to zero. (could optimize in the non-GC case) - InitValueClassArg(argDest, destMT); - } - else - { - if (!IsNullableForTypeNoGC(destMT, boxedVal->GetMethodTable())) - { - // For safety's sake, also allow true nullables to be unboxed normally. - // This should not happen normally, but we want to be robust - if (destMT == boxedVal->GetMethodTable()) - { - CopyValueClassArg(argDest, boxedVal->GetData(), destMT, 0); - return TRUE; - } - return FALSE; - } - - CopyValueClassArg(argDest, boxedVal->UnBox(), boxedVal->GetMethodTable(), 0); - *(UINT64*)(argDest->GetStructGenRegDestinationAddress()) = 1; - } - return TRUE; - } - -#endif - - return UnBoxNoGC(argDest->GetDestinationAddress(), boxedVal, destMT); -} - //=============================================================================== // Special Logic to unbox a boxed T as a nullable // Does not do any type checks. @@ -1863,10 +1767,10 @@ void Nullable::UnBoxNoCheck(void* destPtr, OBJECTREF boxedVal, MethodTable* dest CONTRACTL_END; Nullable* dest = (Nullable*) destPtr; - // We should only get here if we are unboxing a T as a Nullable + // We should only get here if we are unboxing a T as a Nullable _ASSERTE(IsNullableType(destMT)); - // We better have a concrete instantiation, or our field offset asserts are not useful + // We better have a concrete instantiation, or our field offset asserts are not useful _ASSERTE(!destMT->ContainsGenericVariables()); if (boxedVal == NULL) diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 7d5e4b121f136..ac0798045bd22 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -2614,7 +2614,6 @@ class Nullable { static OBJECTREF Box(void* src, MethodTable* nullable); static BOOL UnBox(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static BOOL UnBoxNoGC(void* dest, OBJECTREF boxedVal, MethodTable* destMT); - static BOOL UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, MethodTable* destMT); static void UnBoxNoCheck(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static OBJECTREF BoxedNullableNull(TypeHandle nullableType) { return 0; } diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 197d4ac026232..2b6e75b6817fb 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -25,29 +25,6 @@ #include "dbginterface.h" #include "argdestination.h" -/**************************************************************************/ -/* if the type handle 'th' is a byref to a nullable type, return the - type handle to the nullable type in the byref. Otherwise return - the null type handle */ -static TypeHandle NullableTypeOfByref(TypeHandle th) { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - if (th.GetVerifierCorElementType() != ELEMENT_TYPE_BYREF) - return TypeHandle(); - - TypeHandle subType = th.AsTypeDesc()->GetTypeParam(); - if (!Nullable::IsNullableType(subType)) - return TypeHandle(); - - return subType; -} - FCIMPL5(Object*, RuntimeFieldHandle::GetValue, ReflectFieldObject *pFieldUNSAFE, Object *instanceUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pDomainInitialized) { CONTRACTL { FCALL_CHECK; @@ -145,7 +122,10 @@ FCIMPL2(FC_BOOL_RET, ReflectionInvocation::CanValueSpecialCast, ReflectClassBase } FCIMPLEND -FCIMPL3(Object*, ReflectionInvocation::AllocateValueType, ReflectClassBaseObject *pTargetTypeUNSAFE, Object *valueUNSAFE, CLR_BOOL fForceTypeChange) { +/// +/// Allocate the value type and copy the optional value into it. +/// +FCIMPL2(Object*, ReflectionInvocation::AllocateValueType, ReflectClassBaseObject *pTargetTypeUNSAFE, Object *valueUNSAFE) { CONTRACTL { FCALL_CHECK; PRECONDITION(CheckPointer(pTargetTypeUNSAFE)); @@ -164,41 +144,23 @@ FCIMPL3(Object*, ReflectionInvocation::AllocateValueType, ReflectClassBaseObject gc.obj = gc.value; gc.refTargetType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTargetTypeUNSAFE); + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + TypeHandle targetType = gc.refTargetType->GetType(); - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); - CorElementType targetElementType = targetType.GetSignatureCorElementType(); - if (InvokeUtil::IsPrimitiveType(targetElementType) || targetElementType == ELEMENT_TYPE_VALUETYPE) - { - MethodTable* allocMT = targetType.AsMethodTable(); + // This method is only intended for value types; it is not called directly by any public APIs + // so we don't expect validation issues here. + _ASSERTE(targetType.IsValueType()); - if (allocMT->IsByRefLike()) { - COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefLike")); - } + MethodTable* allocMT = targetType.AsMethodTable(); + _ASSERTE(!allocMT->IsByRefLike()); - if (gc.value != NULL) - { - // ignore the type of the incoming box if fForceTypeChange is set - // and the target type is not nullable - if (!fForceTypeChange || Nullable::IsNullableType(targetType)) - allocMT = gc.value->GetMethodTable(); - } + gc.obj = allocMT->Allocate(); + _ASSERTE(gc.obj != NULL); - // for null Nullable we don't want a default value being created. - // just allow the null value to be passed, as it will be converted to - // a true nullable - if (!(gc.value == NULL && Nullable::IsNullableType(targetType))) - { - // boxed value type are 'read-only' in the sence that you can't - // only the implementor of the value type can expose mutators. - // To insure byrefs don't mutate value classes in place, we make - // a copy (and if we were not given one, we create a null value type - // instance. - gc.obj = allocMT->Allocate(); - - if (gc.value != NULL) - CopyValueClass(gc.obj->UnBox(), gc.value->UnBox(), allocMT); - } + if (gc.value != NULL) { + _ASSERTE(allocMT->IsEquivalentTo(gc.value->GetMethodTable())); + CopyValueClass(gc.obj->UnBox(), gc.value->UnBox(), allocMT); } HELPER_METHOD_FRAME_END(); @@ -346,29 +308,6 @@ FCIMPL2(FC_BOOL_RET, RuntimeTypeHandle::IsInstanceOfType, ReflectClassBaseObject } FCIMPLEND -/****************************************************************************/ -/* boxed Nullable are represented as a boxed T, so there is no unboxed - Nullable inside to point at by reference. Because of this a byref - parameters of type Nullable are copied out of the boxed instance - (to a place on the stack), before the call is made (and this copy is - pointed at). After the call returns, this copy must be copied back to - the original argument array. ByRefToNullable, is a simple linked list - that remembers what copy-backs are needed */ - -struct ByRefToNullable { - unsigned argNum; // The argument number for this byrefNullable argument - void* data; // The data to copy back to the ByRefNullable. This points to the stack - TypeHandle type; // The type of Nullable for this argument - ByRefToNullable* next; // list of these - - ByRefToNullable(unsigned aArgNum, void* aData, TypeHandle aType, ByRefToNullable* aNext) { - argNum = aArgNum; - data = aData; - type = aType; - next = aNext; - } -}; - static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) { CONTRACTL { @@ -641,7 +580,6 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, FrameWithCookie *pProtectValueClassFrame = NULL; ValueClassInfo *pValueClasses = NULL; - ByRefToNullable* byRefToNullables = NULL; // if we have the magic Value Class return, we need to allocate that class // and place a pointer to it on the stack. @@ -685,8 +623,7 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, else pThisPtr = OBJECTREFToObject(gc.retVal); } - else - if (!pMeth->GetMethodTable()->IsValueType()) + else if (!pMeth->GetMethodTable()->IsValueType()) pThisPtr = OBJECTREFToObject(gc.target); else { if (pMeth->IsUnboxingStub()) @@ -761,23 +698,8 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, bool needsStackCopy = false; ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - TypeHandle nullableType = NullableTypeOfByref(th); - if (!nullableType.IsNull()) { - // A boxed Nullable is represented as boxed T. So to pass a Nullable by reference, - // we have to create a Nullable on stack, copy the T into it, then pass it to the callee and - // after returning from the call, copy the T out of the Nullable back to the boxed T. - th = nullableType; - structSize = th.GetSize(); - needsStackCopy = true; - } #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE - else if (argit.IsArgPassedByRef()) - { - needsStackCopy = true; - } -#endif - - if (needsStackCopy) + if (argit.IsArgPassedByRef()) { MethodTable* pMT = th.GetMethodTable(); _ASSERTE(pMT && pMT->IsValueType()); @@ -787,11 +709,6 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, PVOID pStackCopy = _alloca(structSize); *(PVOID *)pArgDst = pStackCopy; - if (!nullableType.IsNull()) - { - byRefToNullables = new(_alloca(sizeof(ByRefToNullable))) ByRefToNullable(i, pStackCopy, nullableType, byRefToNullables); - } - // save the info into ValueClassInfo if (pMT->ContainsPointers()) { @@ -801,6 +718,7 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, // We need a new ArgDestination that points to the stack copy argDest = ArgDestination(pStackCopy, 0, NULL); } +#endif InvokeUtil::CopyArg(th, args[i], &argDest); } @@ -874,12 +792,6 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, gc.retVal = InvokeUtil::CreateObjectAfterInvoke(retTH, &callDescrData.returnValue); } - while (byRefToNullables != NULL) { - OBJECTREF obj = Nullable::Box(byRefToNullables->data, byRefToNullables->type.GetMethodTable()); - SetObjectReference((OBJECTREF*)args[byRefToNullables->argNum], obj); - byRefToNullables = byRefToNullables->next; - } - if (pProtectValueClassFrame != NULL) pProtectValueClassFrame->Pop(pThread); @@ -893,6 +805,68 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, } FCIMPLEND +/// +/// Convert a boxed value of {T} (which is either {T} or null) to a true boxed Nullable{T}. +/// +FCIMPL2(Object*, RuntimeMethodHandle::ReboxToNullable, Object* pBoxedValUNSAFE, ReflectClassBaseObject *pDestUNSAFE) +{ + FCALL_CONTRACT; + + struct { + OBJECTREF pBoxed; + REFLECTCLASSBASEREF destType; + OBJECTREF retVal; + } gc; + + gc.pBoxed = ObjectToOBJECTREF(pBoxedValUNSAFE); + gc.destType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pDestUNSAFE); + gc.retVal = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + MethodTable* destMT = gc.destType->GetType().AsMethodTable(); + + gc.retVal = destMT->Allocate(); + void* buffer = gc.retVal->GetData(); + BOOL result = Nullable::UnBox(buffer, gc.pBoxed, destMT); + _ASSERTE(result == TRUE); + + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.retVal); +} +FCIMPLEND + +/// +/// For a true boxed Nullable{T}, re-box to a boxed {T} or null, otherwise just return the input. +/// +FCIMPL1(Object*, RuntimeMethodHandle::ReboxFromNullable, Object* pBoxedValUNSAFE) +{ + FCALL_CONTRACT; + + struct { + OBJECTREF pBoxed; + OBJECTREF retVal; + } gc; + + if (pBoxedValUNSAFE == NULL) + return NULL; + + gc.pBoxed = ObjectToOBJECTREF(pBoxedValUNSAFE); + MethodTable* retMT = gc.pBoxed->GetMethodTable(); + if (!Nullable::IsNullableType(retMT)) + return pBoxedValUNSAFE; + + gc.retVal = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + gc.retVal = Nullable::Box(gc.pBoxed->GetData(), retMT); + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.retVal); +} +FCIMPLEND + struct SkipStruct { StackCrawlMark* pStackMark; MethodDesc* pMeth; diff --git a/src/coreclr/vm/reflectioninvocation.h b/src/coreclr/vm/reflectioninvocation.h index 76eef427a7969..5e909b6b4c51e 100644 --- a/src/coreclr/vm/reflectioninvocation.h +++ b/src/coreclr/vm/reflectioninvocation.h @@ -62,7 +62,7 @@ class ReflectionInvocation { // helper fcalls for invocation static FCDECL2(FC_BOOL_RET, CanValueSpecialCast, ReflectClassBaseObject *valueType, ReflectClassBaseObject *targetType); - static FCDECL3(Object*, AllocateValueType, ReflectClassBaseObject *targetType, Object *valueUNSAFE, CLR_BOOL fForceTypeChange); + static FCDECL2(Object*, AllocateValueType, ReflectClassBaseObject *targetType, Object *valueUNSAFE); }; extern "C" void QCALLTYPE ReflectionInvocation_CompileMethod(MethodDesc * pMD); diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 29bdfa08a846f..02ec1e078f4c9 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -224,6 +224,9 @@ class RuntimeMethodHandle { static FCDECL4(Object*, InvokeMethod, Object *target, PVOID* args, SignatureNative* pSig, CLR_BOOL fConstructor); + static FCDECL2(Object*, ReboxToNullable, Object *pBoxedValUNSAFE, ReflectClassBaseObject *pDestUNSAFE); + static FCDECL1(Object*, ReboxFromNullable, Object *pBoxedValUNSAFE); + struct StreamingContextData { Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a INT32 contextStates; // constructor in this struct. GCC doesn't allow structs with constructors to be diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackFrameTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackFrameTests.cs index 2f541d9e2a851..67ba8fd483eb1 100644 --- a/src/libraries/System.Diagnostics.StackTrace/tests/StackFrameTests.cs +++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackFrameTests.cs @@ -186,7 +186,8 @@ private static void VerifyStackFrameSkipFrames(StackFrame stackFrame, bool isFil } // GetNativeOffset returns StackFrame.OFFSET_UNKNOWN for unknown frames. - // GetNativeOffset returns 0 for known frames with a positive skipFrames. + // For a positive skipFrame, the GetNativeOffset return value is dependent upon the implementation of reflection + // Invoke() which can be native (where the value would be zero) or managed (where the value is likely non-zero). if (skipFrames == int.MaxValue || skipFrames == int.MinValue) { Assert.Equal(StackFrame.OFFSET_UNKNOWN, stackFrame.GetNativeOffset()); @@ -198,7 +199,7 @@ private static void VerifyStackFrameSkipFrames(StackFrame stackFrame, bool isFil } else { - Assert.Equal(0, stackFrame.GetNativeOffset()); + Assert.True(stackFrame.GetNativeOffset() >= 0); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1a09997db8943..48c1fbecb3fb4 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -635,6 +635,7 @@ + @@ -643,6 +644,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index 48fe08e491026..c79036380b8e7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -2,25 +2,101 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Reflection { internal sealed partial class ConstructorInvoker { - private readonly RuntimeConstructorInfo _constructorInfo; - public InvocationFlags _invocationFlags; + private readonly RuntimeConstructorInfo _method; + +#if !MONO // Temporary until Mono is updated. + private bool _invoked; + private bool _strategyDetermined; + private InvokerEmitUtil.InvokeFunc? _invokeFunc; +#endif public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) { - _constructorInfo = constructorInfo; + _method = constructorInfo; + +#if USE_NATIVE_INVOKE + // Always use the native invoke; useful for testing. + _strategyDetermined = true; +#elif USE_EMIT_INVOKE + // Always use emit invoke (if IsDynamicCodeCompiled == true); useful for testing. + _invoked = true; +#endif } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if MONO // Temporary until Mono is updated. + public unsafe object? InlinedInvoke(object? obj, Span args, BindingFlags invokeAttr) => InterpretedInvoke(obj, args, invokeAttr); +#else + public unsafe object? InlinedInvoke(object? obj, IntPtr* args, BindingFlags invokeAttr) + { + if (_invokeFunc != null && (invokeAttr & BindingFlags.DoNotWrapExceptions) != 0 && obj == null) + { + return _invokeFunc(target: null, args); + } + return Invoke(obj, args, invokeAttr); + } +#endif + +#if !MONO // Temporary until Mono is updated. [DebuggerStepThrough] [DebuggerHidden] - public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) + private unsafe object? Invoke(object? obj, IntPtr* args, BindingFlags invokeAttr) { - // Todo: add strategy for calling IL Emit-based version - return _constructorInfo.InvokeNonEmitUnsafe(obj, args, argsForTemporaryMonoSupport, invokeAttr); + if (!_strategyDetermined) + { + if (!_invoked) + { + // The first time, ignoring race conditions, use the slow path. + _invoked = true; + } + else + { + if (RuntimeFeature.IsDynamicCodeCompiled) + { + _invokeFunc = InvokerEmitUtil.CreateInvokeDelegate(_method); + } + _strategyDetermined = true; + } + } + + object? ret; + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + try + { + // For the rarely used scenario of calling the constructor directly through MethodBase.Invoke() + // with a non-null 'obj', we use the slow path to avoid having two emit-based delegates. + if (_invokeFunc != null && obj == null) + { + ret = _invokeFunc(target: null, args); + } + else + { + ret = InterpretedInvoke(obj, args); + } + } + catch (Exception e) + { + throw new TargetInvocationException(e); + } + } + else if (_invokeFunc != null && obj == null) + { + ret = _invokeFunc(target: null, args); + } + else + { + ret = InterpretedInvoke(obj, args); + } + + return ret; } +#endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs index 74cf3c8181781..c5b26694eebb1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -16,7 +16,7 @@ public static object ConvertOrWiden(Type srcType, CorElementType srcElementType, if (dstType.IsPointer) { - if (TryConvertPointer(srcObject, srcElementType, dstElementType, out IntPtr dstIntPtr)) + if (TryConvertPointer(srcObject, out IntPtr dstIntPtr)) { return dstIntPtr; } @@ -109,7 +109,7 @@ public static object ConvertOrWiden(Type srcType, CorElementType srcElementType, return dstObject; } - private static bool TryConvertPointer(object srcObject, CorElementType srcEEType, CorElementType dstEEType, out IntPtr dstIntPtr) + private static bool TryConvertPointer(object srcObject, out IntPtr dstIntPtr) { if (srcObject is IntPtr srcIntPtr) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs new file mode 100644 index 0000000000000..f79b802b62cd4 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokerEmitUtil.cs @@ -0,0 +1,187 @@ +// 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; +using System.Diagnostics.CodeAnalysis; +using System.Reflection.Emit; + +namespace System.Reflection +{ + internal static class InvokerEmitUtil + { + // If changed, update native stack walking code that also uses this prefix to ignore reflection frames. + private const string InvokeStubPrefix = "InvokeStub_"; + + internal unsafe delegate object? InvokeFunc(object? target, IntPtr* arguments); + + public static unsafe InvokeFunc CreateInvokeDelegate(MethodBase method) + { + Debug.Assert(!method.ContainsGenericParameters); + + bool emitNew = method is RuntimeConstructorInfo; + bool hasThis = !(emitNew || method.IsStatic); + + // The first parameter is unused but supports treating the DynamicMethod as an instance method which is slightly faster than a static. + Type[] delegateParameters = new Type[3] { typeof(object), typeof(object), typeof(IntPtr*) }; + + string declaringTypeName = method.DeclaringType != null ? method.DeclaringType.Name + "." : string.Empty; + var dm = new DynamicMethod( + InvokeStubPrefix + declaringTypeName + method.Name, + returnType: typeof(object), + delegateParameters, + restrictedSkipVisibility: true); + + ILGenerator il = dm.GetILGenerator(); + + // Handle instance methods. + if (hasThis) + { + il.Emit(OpCodes.Ldarg_1); + if (method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Unbox, method.DeclaringType); + } + } + + // Push the arguments. + ParameterInfo[] parameters = method.GetParametersNoCopy(); + for (int i = 0; i < parameters.Length; i++) + { + il.Emit(OpCodes.Ldarg_2); + if (i != 0) + { + il.Emit(OpCodes.Ldc_I4, i * IntPtr.Size); + il.Emit(OpCodes.Add); + } + + il.Emit(OpCodes.Call, Methods.ByReferenceOfByte_Value()); // This can be replaced by ldfld once byref fields are available in C# + + RuntimeType parameterType = (RuntimeType)parameters[i].ParameterType; + if (!parameterType.IsByRef) + { + il.Emit(OpCodes.Ldobj, parameterType.IsPointer ? typeof(IntPtr) : parameterType); + } + } + + // Invoke the method. + if (emitNew) + { + il.Emit(OpCodes.Newobj, (ConstructorInfo)method); + } + else if (method.IsStatic || method.DeclaringType!.IsValueType) + { + il.Emit(OpCodes.Call, (MethodInfo)method); + } + else + { + il.Emit(OpCodes.Callvirt, (MethodInfo)method); + } + + // Handle the return. + if (emitNew) + { + Type returnType = method.DeclaringType!; + if (returnType.IsValueType) + { + il.Emit(OpCodes.Box, returnType); + } + } + else + { + RuntimeType returnType; + if (method is RuntimeMethodInfo rmi) + { + returnType = (RuntimeType)rmi.ReturnType; + } + else + { + Debug.Assert(method is DynamicMethod); + returnType = (RuntimeType)((DynamicMethod)method).ReturnType; + } + + if (returnType == typeof(void)) + { + il.Emit(OpCodes.Ldnull); + } + else if (returnType.IsValueType) + { + il.Emit(OpCodes.Box, returnType); + } + else if (returnType.IsPointer) + { + il.Emit(OpCodes.Ldtoken, returnType); + il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle()); + il.Emit(OpCodes.Call, Methods.Pointer_Box()); + } + else if (returnType.IsByRef) + { + // Check for null ref return. + Type elementType = returnType.GetElementType()!; + Label retValueOk = il.DefineLabel(); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Brtrue_S, retValueOk); + il.Emit(OpCodes.Call, Methods.ThrowHelper_Throw_NullReference_InvokeNullRefReturned()); + il.MarkLabel(retValueOk); + + // Handle per-type differences. + if (elementType.IsValueType) + { + il.Emit(OpCodes.Ldobj, elementType); + il.Emit(OpCodes.Box, elementType); + } + else if (elementType.IsPointer) + { + il.Emit(OpCodes.Ldind_Ref); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Ldtoken, elementType); + il.Emit(OpCodes.Call, Methods.Type_GetTypeFromHandle()); + il.Emit(OpCodes.Call, Methods.Pointer_Box()); + } + else + { + il.Emit(OpCodes.Ldobj, elementType); + } + } + } + + il.Emit(OpCodes.Ret); + + // Create the delegate; it is also compiled at this point due to restrictedSkipVisibility=true. + return (InvokeFunc)dm.CreateDelegate(typeof(InvokeFunc), target: null); + } + + private static class ThrowHelper + { + public static void Throw_NullReference_InvokeNullRefReturned() + { + throw new NullReferenceException(SR.NullReference_InvokeNullRefReturned); + } + } + + private static class Methods + { + private static MethodInfo? s_ByReferenceOfByte_Value; + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(ByReference<>))] + public static MethodInfo ByReferenceOfByte_Value() => + s_ByReferenceOfByte_Value ?? + (s_ByReferenceOfByte_Value = typeof(ByReference).GetMethod("get_Value")!); + + private static MethodInfo? s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned; + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(ThrowHelper))] + public static MethodInfo ThrowHelper_Throw_NullReference_InvokeNullRefReturned() => + s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned ?? + (s_ThrowHelper_Throw_NullReference_InvokeNullRefReturned = typeof(ThrowHelper).GetMethod(nameof(ThrowHelper.Throw_NullReference_InvokeNullRefReturned))!); + + private static MethodInfo? s_Pointer_Box; + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(Pointer))] + public static MethodInfo Pointer_Box() => + s_Pointer_Box ?? + (s_Pointer_Box = typeof(Pointer).GetMethod(nameof(Pointer.Box), new[] { typeof(void*), typeof(Type) })!); + + private static MethodInfo? s_Type_GetTypeFromHandle; + public static MethodInfo Type_GetTypeFromHandle() => + s_Type_GetTypeFromHandle ?? + (s_Type_GetTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), new[] { typeof(RuntimeTypeHandle) })!); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 3053aab008a49..3c93886aa4fd7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -143,7 +143,7 @@ private protected void ValidateInvokeTarget(object? target) private protected unsafe void CheckArguments( Span copyOfParameters, IntPtr* byrefParameters, - Span shouldCopyBack, + Span shouldCopyBack, ReadOnlySpan parameters, RuntimeType[] sigTypes, Binder? binder, @@ -156,35 +156,45 @@ BindingFlags invokeAttr ParameterInfo[]? paramInfos = null; for (int i = 0; i < parameters.Length; i++) { - bool copyBackArg = false; - bool isValueType; + ParameterCopyBackAction copyBackArg = default; + bool isValueType = false; object? arg = parameters[i]; RuntimeType sigType = sigTypes[i]; if (arg is null) { - // Fast path that avoids calling CheckValue() for reference types. + // Fast path for null reference types. isValueType = RuntimeTypeHandle.IsValueType(sigType); if (isValueType || RuntimeTypeHandle.IsByRef(sigType)) { isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } } - else if (ReferenceEquals(arg.GetType(), sigType)) - { - // Fast path that avoids calling CheckValue() when argument value matches the signature type. - isValueType = RuntimeTypeHandle.IsValueType(sigType); - } else { - paramInfos ??= GetParametersNoCopy(); - ParameterInfo paramInfo = paramInfos[i]; - if (!ReferenceEquals(arg, Type.Missing)) + RuntimeType argType = (RuntimeType)arg.GetType(); + + if (ReferenceEquals(argType, sigType)) { + // Fast path when the value's type matches the signature type. + isValueType = RuntimeTypeHandle.IsValueType(argType); + } + else if (sigType.TryByRefFastPath(ref arg, ref isValueType)) + { + // Fast path when the value's type matches the signature type of a byref parameter. + copyBackArg = ParameterCopyBackAction.Copy; + } + else if (!ReferenceEquals(arg, Type.Missing)) + { + // Slow path that supports type conversions. isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } else { + // Convert Type.Missing to the default value. + paramInfos ??= GetParametersNoCopy(); + ParameterInfo paramInfo = paramInfos[i]; + if (paramInfo.DefaultValue == DBNull.Value) { throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); @@ -193,6 +203,7 @@ BindingFlags invokeAttr arg = paramInfo.DefaultValue; if (ReferenceEquals(arg?.GetType(), sigType)) { + // Fast path when the default value's type matches the signature type. isValueType = RuntimeTypeHandle.IsValueType(sigType); } else @@ -209,16 +220,20 @@ BindingFlags invokeAttr // as we validate them. n.b. This disallows use of ArrayPool, as ArrayPool-rented arrays are // considered user-visible to threads which may still be holding on to returned instances. // This separate array is also used to hold default values when 'null' is specified for value - // types, and also used to hold the results from conversions such as from Int16 to Int32; these - // default values and conversions should not be applied to the incoming arguments. + // types, and also used to hold the results from conversions such as from Int16 to Int32. For + // compat, these default values and conversions are not be applied to the incoming arguments. shouldCopyBack[i] = copyBackArg; copyOfParameters[i] = arg; if (isValueType) { -#if DEBUG - // Once Mono has managed conversion logic, VerifyValueType() can be lifted here as Asserts. - sigType.VerifyValueType(arg); +#if !MONO // Temporary until Mono is updated. + Debug.Assert(arg != null); + Debug.Assert( + arg.GetType() == sigType || + (sigType.IsPointer && arg.GetType() == typeof(IntPtr)) || + (sigType.IsByRef && arg.GetType() == RuntimeTypeHandle.GetElementType(sigType)) || + ((sigType.IsEnum || arg.GetType().IsEnum) && RuntimeType.GetUnderlyingType((RuntimeType)arg.GetType()) == RuntimeType.GetUnderlyingType(sigType))); #endif ByReference valueTypeRef = new(ref copyOfParameters[i]!.GetRawData()); *(ByReference*)(byrefParameters + i) = valueTypeRef; @@ -247,11 +262,11 @@ private protected ref struct StackAllocedArguments private object? _arg2; private object? _arg3; #pragma warning restore CA1823, CS0169, IDE0051 - internal bool _copyBack0; + internal ParameterCopyBackAction _copyBack0; #pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic - private bool _copyBack1; - private bool _copyBack2; - private bool _copyBack3; + private ParameterCopyBackAction _copyBack1; + private ParameterCopyBackAction _copyBack2; + private ParameterCopyBackAction _copyBack3; #pragma warning restore CA1823, CS0169, IDE0051 } 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 65c4e2bac2e74..3adbad39eb21c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -2,25 +2,84 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Reflection { internal sealed partial class MethodInvoker { - internal InvocationFlags _invocationFlags; - private readonly RuntimeMethodInfo _methodInfo; + private readonly MethodBase _method; - public MethodInvoker(RuntimeMethodInfo methodInfo) + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if MONO // Temporary until Mono is updated. + public unsafe object? InlinedInvoke(object? obj, Span args, BindingFlags invokeAttr) => InterpretedInvoke(obj, args, invokeAttr); +#else + public unsafe object? InlinedInvoke(object? obj, IntPtr* args, BindingFlags invokeAttr) { - _methodInfo = methodInfo; + if (_invokeFunc != null && (invokeAttr & BindingFlags.DoNotWrapExceptions) != 0) + { + return _invokeFunc(obj, args); + } + return Invoke(obj, args, invokeAttr); } +#endif + +#if !MONO // Temporary until Mono is updated. + private bool _invoked; + private bool _strategyDetermined; + private InvokerEmitUtil.InvokeFunc? _invokeFunc; [DebuggerStepThrough] [DebuggerHidden] - public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) + private unsafe object? Invoke(object? obj, IntPtr* args, BindingFlags invokeAttr) { - // Todo: add strategy for calling IL Emit-based version - return _methodInfo.InvokeNonEmitUnsafe(obj, args, argsForTemporaryMonoSupport, invokeAttr); + if (!_strategyDetermined) + { + if (!_invoked) + { + // The first time, ignoring race conditions, use the slow path. + _invoked = true; + } + else + { + if (RuntimeFeature.IsDynamicCodeCompiled) + { + _invokeFunc = InvokerEmitUtil.CreateInvokeDelegate(_method); + } + _strategyDetermined = true; + } + } + + object? ret; + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + try + { + if (_invokeFunc != null) + { + ret = _invokeFunc(obj, args); + } + else + { + ret = InterpretedInvoke(obj, args); + } + } + catch (Exception e) + { + throw new TargetInvocationException(e); + } + } + else if (_invokeFunc != null) + { + ret = _invokeFunc(obj, args); + } + else + { + ret = InterpretedInvoke(obj, args); + } + + return ret; } +#endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs new file mode 100644 index 0000000000000..e7a9692a7449a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterCopyBackAction.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection +{ + /// + /// Determines how an invoke parameter needs to be copied back to the caller's object[] parameters. + /// + internal enum ParameterCopyBackAction : byte + { + None = 0, + Copy = 1, + CopyNullable = 2 + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs index 046979aac68ef..0ad353cfd38e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -130,7 +130,7 @@ internal void ThrowNoInvokeException() { if (argCount == 0) { - Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); + Invoker.InlinedInvoke(obj, args: default, invokeAttr); } else if (argCount > MaxStackAllocArgCount) { @@ -141,8 +141,8 @@ internal void ThrowNoInvokeException() { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new Span(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + Span copyOfParameters = new(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; @@ -157,14 +157,29 @@ internal void ThrowNoInvokeException() culture, invokeAttr); - Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr); +#else + Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); +#endif // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } } @@ -187,15 +202,15 @@ private static unsafe void InvokeWithManyArguments( CultureInfo? culture) { object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + Span copyOfParameters = new(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which // naturally requires a stack size that is dependent on the arg count\size. IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; Buffer.ZeroMemory((byte*)pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, argCount); + ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; + Span shouldCopyBackParameters = new(copyBackActions, argCount); GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); @@ -212,7 +227,11 @@ private static unsafe void InvokeWithManyArguments( culture, invokeAttr); - ci.Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + ci.Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr); +#else + ci.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); +#endif } finally { @@ -222,9 +241,20 @@ private static unsafe void InvokeWithManyArguments( // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } } @@ -254,7 +284,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] { if (argCount == 0) { - retValue = Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, invokeAttr); + retValue = Invoker.InlinedInvoke(obj: null, args: default, invokeAttr); } else if (argCount > MaxStackAllocArgCount) { @@ -264,8 +294,8 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new Span(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + Span copyOfParameters = new(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; @@ -280,14 +310,29 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj: null, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + retValue = Invoker.InlinedInvoke(obj: null, copyOfParameters, invokeAttr); +#else + retValue = Invoker.InlinedInvoke(obj: null, pByRefStorage, invokeAttr); +#endif // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } } @@ -312,15 +357,15 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] Debug.Assert(parameters != null); object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + Span copyOfParameters = new(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which // naturally requires a stack size that is dependent on the arg count\size. IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; Buffer.ZeroMemory((byte*)pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, argCount); + ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; + Span shouldCopyBackParameters = new(copyBackActions, argCount); GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); @@ -338,7 +383,11 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] culture, invokeAttr); - retValue = ci.Invoker.InvokeUnsafe(obj: null, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + retValue = ci.Invoker.InlinedInvoke(obj: null, copyOfParameters, invokeAttr); +#else + retValue = ci.Invoker.InlinedInvoke(obj: null, pByRefStorage, invokeAttr); +#endif } finally { @@ -348,9 +397,20 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs index 87404f4723de1..14b7372605d7d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -126,7 +126,7 @@ private void ThrowNoInvokeException() { if (argCount == 0) { - retValue = Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); + retValue = Invoker.InlinedInvoke(obj, args: default, invokeAttr); } else if (argCount > MaxStackAllocArgCount) { @@ -137,8 +137,8 @@ private void ThrowNoInvokeException() { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new Span(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + Span copyOfParameters = new(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; @@ -153,14 +153,29 @@ private void ThrowNoInvokeException() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + retValue = Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr); +#else + retValue = Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); +#endif // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } } @@ -183,15 +198,15 @@ private void ThrowNoInvokeException() CultureInfo? culture) { object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + Span copyOfParameters = new(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which // naturally requires a stack size that is dependent on the arg count\size. IntPtr* pByRefStorage = stackalloc IntPtr[argCount]; Buffer.ZeroMemory((byte*)pByRefStorage, (uint)(argCount * sizeof(IntPtr))); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, argCount); + ParameterCopyBackAction* copyBackActions = stackalloc ParameterCopyBackAction[argCount]; + Span shouldCopyBackParameters = new(copyBackActions, argCount); GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); @@ -209,7 +224,11 @@ private void ThrowNoInvokeException() culture, invokeAttr); - retValue = mi.Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); +#if MONO // Temporary until Mono is updated. + retValue = mi.Invoker.InlinedInvoke(obj, copyOfParameters, invokeAttr); +#else + retValue = mi.Invoker.InlinedInvoke(obj, pByRefStorage, invokeAttr); +#endif } finally { @@ -219,9 +238,20 @@ private void ThrowNoInvokeException() // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. for (int i = 0; i < argCount; i++) { - if (shouldCopyBackParameters[i]) + ParameterCopyBackAction action = shouldCopyBackParameters[i]; + if (action != ParameterCopyBackAction.None) { - parameters[i] = copyOfParameters[i]; + if (action == ParameterCopyBackAction.Copy) + { + parameters[i] = copyOfParameters[i]; + } + else + { + Debug.Assert(action == ParameterCopyBackAction.CopyNullable); + Debug.Assert(copyOfParameters[i] != null); + Debug.Assert(((RuntimeType)copyOfParameters[i]!.GetType()).IsNullableOfT); + parameters[i] = RuntimeMethodHandle.ReboxFromNullable(copyOfParameters[i]); + } } } diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index 47d1bad96d6a4..678560ecb003f 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -709,6 +709,68 @@ static MethodInfo GetMethod(string name) => typeof(EnumMethods).GetMethod( name, BindingFlags.Public | BindingFlags.Static)!; } + [Fact] + public void ValueTypeMembers_WithOverrides() + { + ValueTypeWithOverrides obj = new() { Id = 1 }; + + // ToString is overridden. + Assert.Equal("Hello", (string)GetMethod(typeof(ValueTypeWithOverrides), nameof(ValueTypeWithOverrides.ToString)). + Invoke(obj, null)); + + // Ensure a normal method works. + Assert.Equal(1, (int)GetMethod(typeof(ValueTypeWithOverrides), nameof(ValueTypeWithOverrides.GetId)). + Invoke(obj, null)); + } + + [Fact] + public void ValueTypeMembers_WithoutOverrides() + { + ValueTypeWithoutOverrides obj = new() { Id = 1 }; + + // ToString is not overridden. + Assert.Equal(typeof(ValueTypeWithoutOverrides).ToString(), (string)GetMethod(typeof(ValueTypeWithoutOverrides), nameof(ValueTypeWithoutOverrides.ToString)). + Invoke(obj, null)); + + // Ensure a normal method works. + Assert.Equal(1, (int)GetMethod(typeof(ValueTypeWithoutOverrides), nameof(ValueTypeWithoutOverrides.GetId)). + Invoke(obj, null)); + } + + [Fact] + public void NullableOfTMembers() + { + // Ensure calling a method on Nullable works. + MethodInfo mi = GetMethod(typeof(int?), nameof(Nullable.GetValueOrDefault)); + Assert.Equal(42, mi.Invoke(42, null)); + } + + [Fact] + public void CopyBackWithByRefArgs() + { + object i = 42; + object[] args = new object[] { i }; + GetMethod(typeof(CopyBackMethods), nameof(CopyBackMethods.IncrementByRef)).Invoke(null, args); + Assert.Equal(43, (int)args[0]); + Assert.NotSame(i, args[0]); // A copy should be made; a boxed instance should never be directly updated. + + i = 42; + args = new object[] { i }; + GetMethod(typeof(CopyBackMethods), nameof(CopyBackMethods.IncrementByNullableRef)).Invoke(null, args); + Assert.Equal(43, (int)args[0]); + Assert.NotSame(i, args[0]); + + object o = null; + args = new object[] { o }; + GetMethod(typeof(CopyBackMethods), nameof(CopyBackMethods.SetToNonNullByRef)).Invoke(null, args); + Assert.NotNull(args[0]); + + o = new object(); + args = new object[] { o }; + GetMethod(typeof(CopyBackMethods), nameof(CopyBackMethods.SetToNullByRef)).Invoke(null, args); + Assert.Null(args[0]); + } + //Methods for Reflection Metadata private void DummyMethod1(string str, int iValue, long lValue) { @@ -1001,6 +1063,29 @@ public static bool ValueToNullBoxed(ref int? i, int expected) } } + public static class CopyBackMethods + { + public static void IncrementByRef(ref int i) + { + i++; + } + + public static void IncrementByNullableRef(ref int? i) + { + i++; + } + + public static void SetToNullByRef(ref object o) + { + o = null; + } + + public static void SetToNonNullByRef(ref object o) + { + o = new object(); + } + } + public enum ColorsInt : int { Red = 1 @@ -1016,6 +1101,19 @@ public enum OtherColorsInt : int Red = 1 } + public struct ValueTypeWithOverrides + { + public int Id; + public override string ToString() => "Hello"; + public int GetId() => Id; + } + + public struct ValueTypeWithoutOverrides + { + public int Id; + public int GetId() => Id; + } + public static class EnumMethods { public static bool PassColorsInt(ColorsInt color) diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs index 5c698d2ec4f46..229cd08ada4f1 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs @@ -566,10 +566,23 @@ private static void CheckObjectTypeIntegrity(ISerializable serializable) private static void SanityCheckBlob(object obj, TypeSerializableValue[] blobs) { // These types are unstable during serialization and produce different blobs. + string name = obj.GetType().FullName; if (obj is WeakReference || obj is Collections.Specialized.HybridDictionary || obj is Color || - obj.GetType().FullName == "System.Collections.SortedList+SyncSortedList") + name == "System.Collections.SortedList+SyncSortedList" || + // Due to non-deterministic field ordering the types below will fail when using IL Emit-based Invoke. + // The types above may also be failing for the same reason. + // Remove these cases once https://github.com/dotnet/runtime/issues/46272 is fixed. + name == "System.Collections.Comparer" || + name == "System.Collections.Hashtable" || + name == "System.Collections.SortedList" || + name == "System.Collections.Specialized.ListDictionary" || + name == "System.CultureAwareComparer" || + name == "System.Globalization.CompareInfo" || + name == "System.Net.Cookie" || + name == "System.Net.CookieCollection" || + name == "System.Net.CookieContainer") { return; } diff --git a/src/libraries/System.Runtime/tests/System/Reflection/PointerTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/PointerTests.cs index cfe5f5b20ace8..f89f19ec5d19e 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/PointerTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/PointerTests.cs @@ -16,15 +16,27 @@ public void Method(byte* ptr, int expected) Assert.Equal(expected, unchecked((int)ptr)); } + public void MethodWithSystemPointer(Pointer ptr, int expected) + { + Assert.Equal(expected, unchecked((int)Pointer.Unbox(ptr))); + } + public bool* Return(int expected) { return unchecked((bool*)expected); } + + public object ReturnWithSystemPointer(int expected) + { + return Pointer.Box((byte*)expected, typeof(byte*)); + } } unsafe delegate void MethodDelegate(byte* ptr, int expected); + unsafe delegate void MethodDelegateWithSystemPointer(Pointer ptr, int expected); unsafe delegate bool* ReturnDelegate(int expected); + unsafe delegate object ReturnDelegateWithSystemPointer(int expected); public unsafe class PointerTests { @@ -242,6 +254,15 @@ public void PointerMethodDelegateParameter(int value) d.DynamicInvoke(Pointer.Box(unchecked((void*)value), typeof(byte*)), value); } + [Theory] + [MemberData(nameof(Pointers))] + public void MethodDelegateParameter_SystemPointer(int value) + { + var obj = new PointerHolder(); + MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer; + d.DynamicInvoke(Pointer.Box(unchecked((void*)value), typeof(byte*)), value); + } + [Fact] public void PointerNullMethodDelegateParameter() { @@ -250,6 +271,24 @@ public void PointerNullMethodDelegateParameter() d.DynamicInvoke(null, 0); } + [Fact] + public void PointerNullMethodDelegateParameter_InvalidType_SystemPointer() + { + // An null is not converted to a System.Pointer. + var obj = new PointerHolder(); + MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer; + try + { + d.DynamicInvoke(null, 0); + } + catch (TargetInvocationException e) when (e.InnerException is ArgumentException) + { + return; + } + + Assert.Fail("Inner exception should be ArgumentException."); + } + [Theory] [MemberData(nameof(Pointers))] public void IntPtrMethodDelegateParameter(int value) @@ -259,6 +298,19 @@ public void IntPtrMethodDelegateParameter(int value) d.DynamicInvoke((IntPtr)value, value); } + [Theory] + [MemberData(nameof(Pointers))] + public void IntPtrMethodDelegateParameter_InvalidType_SystemPointer(int value) + { + // An IntPtr is not converted to a System.Pointer. + var obj = new PointerHolder(); + MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer; + AssertExtensions.Throws(null, () => + { + d.DynamicInvoke((IntPtr)value, value); + }); + } + [Theory] [MemberData(nameof(Pointers))] public void PointerMethodDelegateParameter_InvalidType(int value) @@ -271,6 +323,16 @@ public void PointerMethodDelegateParameter_InvalidType(int value) }); } + [Theory] + [MemberData(nameof(Pointers))] + public void PointerMethodDelegateParameter_InvalidType_SystemPointer(int value) + { + // Although the type boxed doesn't match, when unboxing void* is returned. + var obj = new PointerHolder(); + MethodDelegateWithSystemPointer d = obj.MethodWithSystemPointer; + d.DynamicInvoke(Pointer.Box(unchecked((void*)value), typeof(long*)), value); + } + [Theory] [MemberData(nameof(Pointers))] public void PointerMethodDelegateReturn(int value) @@ -282,5 +344,17 @@ public void PointerMethodDelegateReturn(int value) void* actualPointer = Pointer.Unbox(actualValue); Assert.Equal(value, unchecked((int)actualPointer)); } + + [Theory] + [MemberData(nameof(Pointers))] + public void PointerMethodDelegateReturn_SystemPointer(int value) + { + var obj = new PointerHolder(); + ReturnDelegateWithSystemPointer d = obj.ReturnWithSystemPointer; + object actualValue = d.DynamicInvoke(value); + Assert.IsType(actualValue); + void* actualPointer = Pointer.Unbox(actualValue); + Assert.Equal(value, unchecked((int)actualPointer)); + } } } diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 18ec40cc3ca0b..51f1b835032e0 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + false true @@ -200,10 +200,12 @@ + + diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs new file mode 100644 index 0000000000000..49b13afc66511 --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.Mono.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + internal partial class ConstructorInvoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe object? InterpretedInvoke(object? obj, Span args, BindingFlags invokeAttr) + { + Exception exc; + object? o; + + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + try + { + o = _method.InternalInvoke(obj, args, out exc); + } + catch (MethodAccessException) + { + throw; + } + catch (OverflowException) + { + throw; + } + catch (Exception e) + { + throw new TargetInvocationException(e); + } + } + else + { + o = _method.InternalInvoke(obj, args, out exc); + } + + if (exc != null) + throw exc; + + return obj == null ? o : null; + } + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs new file mode 100644 index 0000000000000..2ea61086c7a4f --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/MethodInvoker.Mono.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + internal partial class MethodInvoker + { + public MethodInvoker(MethodBase method) + { + _method = method; + +#if USE_NATIVE_INVOKE + // Always use the native invoke; useful for testing. + _strategyDetermined = true; +#elif USE_EMIT_INVOKE + // Always use emit invoke (if IsDynamicCodeCompiled == true); useful for testing. + _invoked = true; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe object? InterpretedInvoke(object? obj, Span args, BindingFlags invokeAttr) + { + Exception? exc; + object? o; + + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + try + { + o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out exc); + } + catch (Mono.NullByRefReturnException) + { + throw new NullReferenceException(); + } + catch (OverflowException) + { + throw; + } + catch (Exception e) + { + throw new TargetInvocationException(e); + } + } + else + { + try + { + o = ((RuntimeMethodInfo)_method).InternalInvoke(obj, args, out exc); + } + catch (Mono.NullByRefReturnException) + { + throw new NullReferenceException(); + } + } + + if (exc != null) + throw exc; + + return o; + } + } +} diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index 8d6c46f153ca2..73aa4744e8f56 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -278,7 +278,7 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, if (val != null) { RuntimeType fieldType = (RuntimeType)FieldType; - bool _ = false; + ParameterCopyBackAction _ = default; if (!ReferenceEquals(val.GetType(), fieldType)) { diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs index 208640b790de5..146739163199b 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs @@ -384,49 +384,6 @@ internal RuntimeType[] ArgumentTypes [MethodImplAttribute(MethodImplOptions.InternalCall)] internal extern object? InternalInvoke(object? obj, in Span parameters, out Exception? exc); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) - { - Exception? exc; - object? o; - - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - try - { - o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); - } - catch (Mono.NullByRefReturnException) - { - throw new NullReferenceException(); - } - catch (OverflowException) - { - throw; - } - catch (Exception e) - { - throw new TargetInvocationException(e); - } - } - else - { - try - { - o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); - } - catch (Mono.NullByRefReturnException) - { - throw new NullReferenceException(); - } - } - - if (exc != null) - throw exc; - - return o; - } - public override RuntimeMethodHandle MethodHandle { get @@ -858,43 +815,6 @@ private static void InvokeClassConstructor() [MethodImplAttribute(MethodImplOptions.InternalCall)] internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); - [DebuggerHidden] - [DebuggerStepThrough] - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) - { - Exception exc; - object? o; - - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - try - { - o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); - } - catch (MethodAccessException) - { - throw; - } - catch (OverflowException) - { - throw; - } - catch (Exception e) - { - throw new TargetInvocationException(e); - } - } - else - { - o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); - } - - if (exc != null) - throw exc; - - return obj == null ? o : null; - } - public override RuntimeMethodHandle MethodHandle { get diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs index 2f0e1b726082f..d57633d1a52a8 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs @@ -85,5 +85,8 @@ internal bool IsNullHandle() { return value == IntPtr.Zero; } + + // Temporary placeholder until Mono adds support for supporting boxing true Nullables. + internal static object? ReboxFromNullable(object? src) => src; } } diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs index 607f9b462a75f..91ba5db495c05 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1635,29 +1635,20 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) unsafe { - return ctor.Invoker.InvokeUnsafe( + return ctor.Invoker.InlinedInvoke( obj: null, args: default, - argsForTemporaryMonoSupport: default, wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); } } - // Once Mono has managed conversion logic, this method can be removed and the Core - // implementation of this method moved to RuntimeMethod.Invoke(). -#if DEBUG -#pragma warning disable CA1822 - internal void VerifyValueType(object? value) { } -#pragma warning restore CA1822 -#endif - /// /// Verify and optionally convert the value for special cases. /// /// Not yet implemented in Mono: True if the value should be considered a value type, False otherwise internal bool CheckValue( ref object? value, - ref bool copyBack, + ref ParameterCopyBackAction copyBack, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) @@ -1665,7 +1656,7 @@ internal bool CheckValue( // Already fast-pathed by the caller. Debug.Assert(!ReferenceEquals(value?.GetType(), this)); - copyBack = true; + copyBack = ParameterCopyBackAction.Copy; CheckValueStatus status = TryConvertToType(ref value); if (status == CheckValueStatus.Success) @@ -1769,6 +1760,11 @@ private CheckValueStatus TryConvertToType(ref object? value) return CheckValueStatus.ArgumentException; } + // Stub method to allow for shared code with CoreClr. +#pragma warning disable CA1822 + internal bool TryByRefFastPath(ref object arg, ref bool isValueType) => false; +#pragma warning restore CA1822 + // Binder uses some incompatible conversion rules. For example // int value cannot be used with decimal parameter but in other // ways it's more flexible than normal convertor, for example @@ -2029,7 +2025,7 @@ internal static object CreateInstanceForAnotherGenericParameter( unsafe { - return ctor.Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, BindingFlags.Default)!; + return ctor.Invoker.InlinedInvoke(obj: null, args: default, BindingFlags.Default)!; } } @@ -2339,6 +2335,8 @@ public override string? FullName public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore(other); + internal bool IsNullableOfT => Nullable.GetUnderlyingType(this) != null; + public override bool IsSZArray { get diff --git a/src/tests/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs b/src/tests/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs index 9596b84510f50..21561ec3ff151 100644 --- a/src/tests/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs +++ b/src/tests/Loader/binding/tracing/BinderTracingTest.EventHandlers.cs @@ -275,7 +275,9 @@ public static BindOperation AssemblyLoadFromResolveHandler_LoadDependency() }; } - [BinderTest(isolate: true, additionalLoadsToTrack: new string[] { "AssemblyToLoadDependency" })] + [BinderTest(isolate: true, + additionalLoadsToTrack: new string[] { "AssemblyToLoadDependency" }, + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes an extra load. public static BindOperation AssemblyLoadFromResolveHandler_MissingDependency() { string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); diff --git a/src/tests/Loader/binding/tracing/BinderTracingTest.ResolutionFlow.cs b/src/tests/Loader/binding/tracing/BinderTracingTest.ResolutionFlow.cs index 780162821f508..08efd20131636 100644 --- a/src/tests/Loader/binding/tracing/BinderTracingTest.ResolutionFlow.cs +++ b/src/tests/Loader/binding/tracing/BinderTracingTest.ResolutionFlow.cs @@ -73,7 +73,8 @@ public static BindOperation FindInLoadContext_DefaultALC() // ResolutionAttempted : DefaultAssemblyLoadContextFallback (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AssemblyLoadContextResolvingEvent (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AppDomainAssemblyResolveEvent (CustomALC) [AssemblyNotFound] - [BinderTest(isolate: true, testSetup: nameof(LoadSubdirectoryAssembly_InstanceALC))] + [BinderTest(isolate: true, testSetup: nameof(LoadSubdirectoryAssembly_InstanceALC), + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes an extra load. public static BindOperation FindInLoadContext_CustomALC_IncompatibleVersion() { var assemblyName = new AssemblyName($"{SubdirectoryAssemblyName}, Version=4.3.2.1"); @@ -180,7 +181,9 @@ public static BindOperation ApplicationAssemblies_IncompatibleVersion() // ResolutionAttempted : ApplicationAssemblies (DefaultALC) [MismatchedAssemblyName] // ResolutionAttempted : AssemblyLoadContextResolvingEvent (DefaultALC) [AssemblyNotFound] // ResolutionAttempted : AppDomainAssemblyResolveEvent (DefaultALC) [AssemblyNotFound] - [BinderTest(isolate: true, additionalLoadsToTrack: new string[] { DependentAssemblyName + "_Copy" } )] + [BinderTest(isolate: true, + additionalLoadsToTrack: new string[] { DependentAssemblyName + "_Copy" }, + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes AssemblyNotFound instead of MismatchedAssemblyName. public static BindOperation ApplicationAssemblies_MismatchedAssemblyName() { var assemblyName = new AssemblyName($"{DependentAssemblyName}_Copy, Culture=neutral, PublicKeyToken=null"); @@ -336,7 +339,8 @@ public static BindOperation ResolveSatelliteAssembly() // ResolutionAttempted : ApplicationAssemblies (DefaultALC) [AssemblyNotFound] // ResolutionAttempted : DefaultAssemblyLoadContextFallback (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AssemblyLoadContextResolvingEvent (CustomALC) [Success] - [BinderTest(isolate: true)] + [BinderTest(isolate: true, + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes an extra load. public static BindOperation AssemblyLoadContextResolvingEvent_CustomALC() { var assemblyName = new AssemblyName(SubdirectoryAssemblyName); @@ -405,7 +409,8 @@ public static BindOperation AssemblyLoadContextResolvingEvent_DefaultALC() // ResolutionAttempted : ApplicationAssemblies (DefaultALC) [AssemblyNotFound] // ResolutionAttempted : DefaultAssemblyLoadContextFallback (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AssemblyLoadContextResolvingEvent (CustomALC) [Exception] - [BinderTest(isolate: true)] + [BinderTest(isolate: true, + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes an extra load. public static BindOperation AssemblyLoadContextResolvingEvent_CustomALC_Exception() { var assemblyName = new AssemblyName(SubdirectoryAssemblyName); @@ -471,7 +476,8 @@ public static BindOperation AssemblyLoadContextResolvingEvent_DefaultALC_Excepti // ResolutionAttempted : DefaultAssemblyLoadContextFallback (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AssemblyLoadContextResolvingEvent (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AppDomainAssemblyResolveEvent (CustomALC) [Success] - [BinderTest(isolate: true)] + [BinderTest(isolate: true, + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes an extra load. public static BindOperation AppDomainAssemblyResolveEvent_CustomALC() { var assemblyName = new AssemblyName(SubdirectoryAssemblyName); @@ -544,7 +550,8 @@ public static BindOperation AppDomainAssemblyResolveEvent_DefaultALC() // ResolutionAttempted : DefaultAssemblyLoadContextFallback (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AssemblyLoadContextResolvingEvent (CustomALC) [AssemblyNotFound] // ResolutionAttempted : AppDomainAssemblyResolveEvent (CustomALC) [Exception] - [BinderTest(isolate: true)] + [BinderTest(isolate: true, + activeIssue: "https://github.com/dotnet/runtime/issues/68521")] // Emit-based Invoke causes an extra load. public static BindOperation AppDomainAssemblyResolveEvent_Exception() { var assemblyName = new AssemblyName(SubdirectoryAssemblyName); diff --git a/src/tests/Loader/binding/tracing/BinderTracingTest.cs b/src/tests/Loader/binding/tracing/BinderTracingTest.cs index d3f1fd8eee3db..bbafdf07db1f1 100644 --- a/src/tests/Loader/binding/tracing/BinderTracingTest.cs +++ b/src/tests/Loader/binding/tracing/BinderTracingTest.cs @@ -18,13 +18,15 @@ namespace BinderTracingTests class BinderTestAttribute : Attribute { public bool Isolate { get; private set; } + public string ActiveIssue { get; private set; } public string TestSetup { get; private set; } public string[] AdditionalLoadsToTrack { get; private set; } - public BinderTestAttribute(bool isolate = false, string testSetup = null, string[] additionalLoadsToTrack = null) + public BinderTestAttribute(bool isolate = false, string testSetup = null, string[] additionalLoadsToTrack = null, string activeIssue = null) { Isolate = isolate; TestSetup = testSetup; AdditionalLoadsToTrack = additionalLoadsToTrack; + ActiveIssue = activeIssue; } } @@ -75,7 +77,9 @@ public static bool RunAllTests() { MethodInfo[] methods = typeof(BinderTracingTest) .GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(m => m.GetCustomAttribute() != null && m.ReturnType == typeof(BindOperation)) + .Where(m => m.GetCustomAttribute() != null && + m.ReturnType == typeof(BindOperation) && + m.GetCustomAttribute().ActiveIssue == null) .ToArray(); foreach (var method in methods) @@ -110,7 +114,10 @@ public static int Main(string[] args) // Run specific test - first argument should be the test method name MethodInfo method = typeof(BinderTracingTest) .GetMethod(args[0], BindingFlags.Public | BindingFlags.Static); - Assert.True(method != null && method.GetCustomAttribute() != null && method.ReturnType == typeof(BindOperation)); + Assert.True(method != null && + method.GetCustomAttribute() != null && + method.ReturnType == typeof(BindOperation) && + method.GetCustomAttribute().ActiveIssue == null); success = RunSingleTest(method); } } diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 1bcefd91fd5b3..8959fd992834d 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -666,6 +666,18 @@ https://github.com/dotnet/runtime/issues/57856 + + https://github.com/dotnet/runtime/issues/68837 + + + https://github.com/dotnet/runtime/issues/68837 + + + https://github.com/dotnet/runtime/issues/68837 + + + https://github.com/dotnet/runtime/issues/68837 + https://github.com/dotnet/runtime/issues/57875