diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index c9c3bca531be0f..072b16cad05712 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -160,6 +160,7 @@ + @@ -315,7 +316,7 @@ - + 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 18428b0eb34ad1..0942a3a0312002 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 @@ -8,6 +8,7 @@ using System.Runtime.Loader; using System.Text; using System.Threading; +using static System.Runtime.CompilerServices.RuntimeHelpers; namespace System.Reflection.Emit { @@ -22,6 +23,8 @@ 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 Signature? _signature; // We want the creator of the DynamicMethod to control who has access to the // DynamicMethod (just like we do for delegates). However, a user can get to @@ -417,6 +420,37 @@ internal RuntimeMethodHandle GetMethodDescriptor() public override bool IsSecurityTransparent => false; + private DynamicMethodInvoker Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + _invoker ??= new DynamicMethodInvoker(this); + + return _invoker; + } + } + + internal Signature Signature + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + [MethodImpl(MethodImplOptions.NoInlining)] // move lazy sig generation out of the hot path + Signature LazyCreateSignature() + { + Debug.Assert(m_methodHandle != null); + Debug.Assert(m_parameterTypes != null); + + Signature newSig = new Signature(m_methodHandle, m_parameterTypes, m_returnType, CallingConvention); + Volatile.Write(ref _signature, newSig); + return newSig; + } + + return _signature ?? LazyCreateSignature(); + } + } + public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) { if ((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs) @@ -431,38 +465,139 @@ internal RuntimeMethodHandle GetMethodDescriptor() _ = GetMethodDescriptor(); // ignore obj since it's a static method - // create a signature object - Signature sig = new Signature( - this.m_methodHandle!, m_parameterTypes, m_returnType, CallingConvention); - - // verify arguments - int formalCount = sig.Arguments.Length; - int actualCount = (parameters != null) ? parameters.Length : 0; - if (formalCount != actualCount) + int argCount = (parameters != null) ? parameters.Length : 0; + if (Signature.Arguments.Length != argCount) throw new TargetParameterCountException(SR.Arg_ParmCnt); - // if we are here we passed all the previous checks. Time to look at the arguments - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; + object? retValue; - StackAllocedArguments stackArgs = default; - Span arguments = default; - if (actualCount != 0) + unsafe { - arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig.Arguments); + if (argCount == 0) + { + retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr); + } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); + } + else + { + Debug.Assert(parameters != null); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; + + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + Signature.Arguments, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(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]) + { + parameters[i] = copyOfParameters[i]; + } + } + } } - object? retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, false, wrapExceptions); + GC.KeepAlive(this); + return retValue; + } + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + // This is a separate method to support better performance for the faster paths. + private static unsafe object? InvokeWithManyArguments( + DynamicMethod mi, + int argCount, + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(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))); - // copy out. This should be made only if ByRef are present. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) - parameters![index] = arguments[index]; + bool* boolHolder = stackalloc bool[argCount]; + Span shouldCopyBackParameters = new Span(boolHolder, argCount); + + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + + object? retValue; + try + { + RegisterForGCReporting(®); + mi.CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + mi.Signature.Arguments, + binder, + culture, + invokeAttr); + + retValue = mi.Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); + } + + // 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]) + { + parameters[i] = copyOfParameters[i]; + } + } - GC.KeepAlive(this); 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 new file mode 100644 index 00000000000000..9e5a5e17be4e80 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs @@ -0,0 +1,24 @@ +// 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/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index 1e41f2eb01b6aa..8eb09a7756e8f5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -17,13 +17,23 @@ internal sealed unsafe class RtFieldInfo : RuntimeFieldInfo, IRuntimeFieldInfo // lazy caching private string? m_name; private RuntimeType? m_fieldType; - private InvocationFlags m_invocationFlags; + internal FieldAccessor? m_invoker; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (m_invocationFlags & InvocationFlags.Initialized) != 0 ? - m_invocationFlags : InitializeInvocationFlags(); + get => (Invoker._invocationFlags & InvocationFlags.Initialized) != 0 ? + Invoker._invocationFlags : InitializeInvocationFlags(); + } + + private FieldAccessor Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + m_invoker ??= new FieldAccessor(this); + return m_invoker; + } } [MethodImpl(MethodImplOptions.NoInlining)] @@ -43,10 +53,10 @@ private InvocationFlags InitializeInvocationFlags() // this should be an usable field, determine the other flags if (invocationFlags == 0) { - if ((m_fieldAttributes & FieldAttributes.InitOnly) != (FieldAttributes)0) + if ((m_fieldAttributes & FieldAttributes.InitOnly) != 0) invocationFlags |= InvocationFlags.SpecialField; - if ((m_fieldAttributes & FieldAttributes.HasFieldRVA) != (FieldAttributes)0) + if ((m_fieldAttributes & FieldAttributes.HasFieldRVA) != 0) invocationFlags |= InvocationFlags.SpecialField; // find out if the field type is one of the following: Primitive, Enum or Pointer @@ -56,7 +66,7 @@ private InvocationFlags InitializeInvocationFlags() } // must be last to avoid threading problems - return m_invocationFlags = invocationFlags | InvocationFlags.Initialized; + return Invoker._invocationFlags = invocationFlags | InvocationFlags.Initialized; } #endregion @@ -72,7 +82,6 @@ internal RtFieldInfo( #region Private Members RuntimeFieldHandleInternal IRuntimeFieldInfo.Value => new RuntimeFieldHandleInternal(m_fieldHandle); - #endregion #region Internal Members @@ -103,6 +112,63 @@ internal override bool CacheEquals(object? o) return o is RtFieldInfo m && m.m_fieldHandle == m_fieldHandle; } + [DebuggerStepThrough] + [DebuggerHidden] + internal object? GetValueNonEmit(object? obj) + { + RuntimeType? declaringType = DeclaringType as RuntimeType; + RuntimeType fieldType = (RuntimeType)FieldType; + bool domainInitialized = false; + + if (declaringType == null) + { + return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + return retVal; + } + } + + [DebuggerStepThrough] + [DebuggerHidden] + internal void SetValueNonEmit(object? obj, object? value) + { + RuntimeType? declaringType = DeclaringType as RuntimeType; + RuntimeType fieldType = (RuntimeType)FieldType; + bool domainInitialized = false; + + if (declaringType == null) + { + RuntimeFieldHandle.SetValue( + this, + obj, + value, + fieldType, + Attributes, + declaringType: null, + ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + + RuntimeFieldHandle.SetValue( + this, + obj, + value, + fieldType, + Attributes, + declaringType, + ref domainInitialized); + + declaringType.DomainInitialized = domainInitialized; + } + } + #endregion #region MemberInfo Overrides @@ -120,8 +186,8 @@ internal override RuntimeModule GetRuntimeModule() #endregion #region FieldInfo Overrides - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override object? GetValue(object? obj) { InvocationFlags invocationFlags = InvocationFlags; @@ -137,26 +203,13 @@ internal override RuntimeModule GetRuntimeModule() CheckConsistency(obj); - RuntimeType fieldType = (RuntimeType)FieldType; - - bool domainInitialized = false; - if (declaringType == null) - { - return RuntimeFieldHandle.GetValue(this, obj, fieldType, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - object? retVal = RuntimeFieldHandle.GetValue(this, obj, fieldType, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - return retVal; - } + return Invoker.GetValue(obj); } public override object GetRawConstantValue() { throw new InvalidOperationException(); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override object? GetValueDirect(TypedReference obj) { if (obj.IsNull) @@ -166,8 +219,8 @@ internal override RuntimeModule GetRuntimeModule() return RuntimeFieldHandle.GetValueDirect(this, (RuntimeType)FieldType, &obj, (RuntimeType?)DeclaringType); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) { InvocationFlags invocationFlags = InvocationFlags; @@ -183,24 +236,25 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); + bool _ref = false; RuntimeType fieldType = (RuntimeType)FieldType; - value = fieldType.CheckValue(value, binder, culture, invokeAttr); - - bool domainInitialized = false; - if (declaringType == null) + if (value is null) { - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, null, ref domainInitialized); + if (RuntimeTypeHandle.IsValueType(fieldType)) + { + fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); + } } - else + else if (!ReferenceEquals(value.GetType(), fieldType)) { - domainInitialized = declaringType.DomainInitialized; - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; + fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); } + + Invoker.SetValue(obj, value); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override void SetValueDirect(TypedReference obj, object value) { if (obj.IsNull) 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 267b738794a63a..35e14b8ebfee40 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 @@ -28,21 +28,31 @@ internal sealed partial class RuntimeConstructorInfo : ConstructorInfo, IRuntime private MethodAttributes m_methodAttributes; private BindingFlags m_bindingFlags; private Signature? m_signature; - private InvocationFlags m_invocationFlags; + private ConstructorInvoker? m_invoker; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - InvocationFlags flags = m_invocationFlags; + InvocationFlags flags = Invoker._invocationFlags; if ((flags & InvocationFlags.Initialized) == 0) { - flags = ComputeAndUpdateInvocationFlags(this, ref m_invocationFlags); + flags = ComputeAndUpdateInvocationFlags(this, ref Invoker._invocationFlags); } return flags; } } + + private ConstructorInvoker Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + m_invoker ??= new ConstructorInvoker(this); + return m_invoker; + } + } #endregion #region Constructor @@ -64,7 +74,7 @@ internal RuntimeConstructorInfo( internal override bool CacheEquals(object? o) => o is RuntimeConstructorInfo m && m.m_handle == m_handle; - private Signature Signature + internal Signature Signature { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -84,6 +94,28 @@ Signature LazyCreateSignature() private RuntimeType ReflectedTypeInternal => m_reflectedTypeCache.GetRuntimeType(); 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 @@ -186,7 +218,7 @@ public override MethodImplAttributes GetMethodImplementationFlags() public override CallingConventions CallingConvention => Signature.CallingConvention; - private RuntimeType[] ArgumentTypes => Signature.Arguments; + internal RuntimeType[] ArgumentTypes => Signature.Arguments; [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2059:RunClassConstructor", Justification = "This ConstructorInfo instance represents the static constructor itself, so if this object was created, the static constructor exists.")] @@ -205,20 +237,6 @@ private void InvokeClassConstructor() } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object? InvokeWorker(object? obj, BindingFlags invokeAttr, Span arguments) - { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, false, wrapExceptions); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object InvokeCtorWorker(BindingFlags invokeAttr, Span arguments) - { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(null, in arguments, Signature, true, wrapExceptions)!; - } - [RequiresUnreferencedCode("Trimming may change method bodies. For example it can change some instructions, remove branches or local variables.")] public override MethodBody? GetMethodBody() { 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 a78691e1e6552d..8cd33129735fde 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 @@ -27,21 +27,31 @@ internal sealed partial class RuntimeMethodInfo : MethodInfo, IRuntimeMethodInfo private Signature? m_signature; private RuntimeType m_declaringType; private object? m_keepalive; - private InvocationFlags m_invocationFlags; + private MethodInvoker? m_invoker; internal InvocationFlags InvocationFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - InvocationFlags flags = m_invocationFlags; + InvocationFlags flags = Invoker._invocationFlags; if ((flags & InvocationFlags.Initialized) == 0) { - flags = ComputeAndUpdateInvocationFlags(this, ref m_invocationFlags); + flags = ComputeAndUpdateInvocationFlags(this, ref Invoker._invocationFlags); } return flags; } } + + private MethodInvoker Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + m_invoker ??= new MethodInvoker(this); + return m_invoker; + } + } #endregion #region Constructor @@ -295,7 +305,7 @@ public override MethodImplAttributes GetMethodImplementationFlags() public override CallingConventions CallingConvention => Signature.CallingConvention; - private RuntimeType[] ArgumentTypes => Signature.Arguments; + internal RuntimeType[] ArgumentTypes => Signature.Arguments; [RequiresUnreferencedCode("Trimming may change method bodies. For example it can change some instructions, remove branches or local variables.")] public override MethodBody? GetMethodBody() @@ -308,16 +318,9 @@ public override MethodImplAttributes GetMethodImplementationFlags() #endregion - #region Invocation Logic(On MemberBase) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object? InvokeWorker(object? obj, BindingFlags invokeAttr, Span arguments) - { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, false, wrapExceptions); - } - - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + #region Invocation Logic + [DebuggerStepThrough] + [DebuggerHidden] internal object? InvokeOneParameter(object? obj, BindingFlags invokeAttr, Binder? binder, object? parameter, CultureInfo? culture) { // ContainsStackPointers means that the struct (either the declaring type or the return type) @@ -337,11 +340,53 @@ public override MethodImplAttributes GetMethodImplementationFlags() throw new TargetParameterCountException(SR.Arg_ParmCnt); } - StackAllocedArguments stackArgs = default; - Span arguments = CheckArguments(ref stackArgs, new ReadOnlySpan(in parameter), binder, invokeAttr, culture, sig.Arguments); + object? retValue; + + unsafe + { + StackAllocedArguments argStorage = default; + Span copyOfParameters = new(ref argStorage._arg0, 1); + ReadOnlySpan parameters = new(in parameter); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); + + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; + + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); + } - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, constructor: false, wrapExceptions); + 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 diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 329ccb349b3711..938a24c5922840 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -350,6 +350,34 @@ private static unsafe void DispatchTailCalls( } } } + +#pragma warning disable 0414 + // Type that represents a managed view of the unmanaged GCFrame + // data structure in coreclr. The type layouts between the two should match. + internal unsafe ref struct GCFrameRegistration + { + private nuint m_reserved1; + private nuint m_reserved2; + private void* m_pObjRefs; + private uint m_numObjRefs; + private int m_MaybeInterior; + + public GCFrameRegistration(void* allocation, uint elemCount, bool areByRefs = true) + { + m_reserved1 = 0; + m_reserved2 = 0; + m_pObjRefs = allocation; + m_numObjRefs = elemCount; + m_MaybeInterior = areByRefs ? 1 : 0; + } + } +#pragma warning restore 0414 + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe void RegisterForGCReporting(GCFrameRegistration* pRegistration); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern unsafe void UnregisterForGCReporting(GCFrameRegistration* pRegistration); } // Helper class to assist with unsafe pinning of arbitrary objects. // It's used by VM code. diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 811772e39d004c..450f9a3441d86a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -796,6 +796,7 @@ public RuntimeMethodInfoStub(IntPtr methodHandleValue, object keepalive) private object? m_e; private object? m_f; private object? m_g; + private object? m_h; #pragma warning restore CA1823, 414, 169 public RuntimeMethodHandleInternal m_value; @@ -975,10 +976,10 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method) [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool MatchesNameHash(RuntimeMethodHandleInternal method, uint hash); - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object? InvokeMethod(object? target, in Span arguments, Signature sig, bool constructor, bool wrapExceptions); + internal static extern object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetMethodInstantiation")] private static partial void GetMethodInstantiation(RuntimeMethodHandleInternal method, ObjectHandleOnStack types, Interop.BOOL fAsRuntimeTypeArray); @@ -1130,6 +1131,7 @@ internal sealed class RuntimeFieldInfoStub : IRuntimeFieldInfo private object? m_d; private int m_b; private object? m_e; + private object? m_f; private RuntimeFieldHandleInternal m_fieldHandle; #pragma warning restore 414, 169 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 dbeb8c760e3bc3..0670e4660a8eb7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -803,7 +803,7 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) return list.ToArray(); } - [UnconditionalSuppressMessage ("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", Justification = "Calls to GetInterfaces technically require all interfaces on ReflectedType" + "But this is not a public API to enumerate reflection items, all the public APIs which do that" + "should be annotated accordingly.")] @@ -1448,6 +1448,7 @@ internal T[] GetMemberList(MemberListType listType, string? name, CacheType cach private string? m_namespace; private readonly bool m_isGlobal; private bool m_bIsDomainInitialized; + private bool m_isNullableOfT; private MemberInfoCache? m_methodInfoCache; private MemberInfoCache? m_constructorInfoCache; private MemberInfoCache? m_fieldInfoCache; @@ -1468,6 +1469,7 @@ internal RuntimeTypeCache(RuntimeType runtimeType) m_typeCode = TypeCode.Empty; m_runtimeType = runtimeType; m_isGlobal = RuntimeTypeHandle.GetModule(runtimeType).RuntimeType == runtimeType; + m_isNullableOfT = Nullable.GetUnderlyingType(runtimeType) != null; } #endregion @@ -1581,6 +1583,8 @@ internal TypeCode TypeCode internal bool IsGlobal => m_isGlobal; + internal bool IsNullableOfT => m_isNullableOfT; + internal void InvalidateCachedNestedType() => m_nestedClassesCache = null; internal string? GetDefaultMemberName() @@ -3443,6 +3447,8 @@ public override Type[] GetGenericParameterConstraints() #endregion #region Misc + internal bool IsNullableOfT => Cache.IsNullableOfT; + public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore(other); public override Type MakePointerType() => new RuntimeTypeHandle(this).MakePointer(); @@ -3465,119 +3471,246 @@ public override Type MakeArrayType(int rank) #region Invoke Member - private static readonly RuntimeType s_typedRef = (RuntimeType)typeof(TypedReference); - [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool CanValueSpecialCast(RuntimeType valueType, RuntimeType targetType); [MethodImpl(MethodImplOptions.InternalCall)] private static extern object AllocateValueType(RuntimeType type, object? value, bool fForceTypeChange); - internal object? CheckValue(object? value, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) + private enum CheckValueStatus + { + Success = 0, + ArgumentException, + NotSupported_ByRefLike + } + +#if DEBUG + internal void VerifyValueType(object? value) { - // this method is used by invocation in reflection to check whether a value can be assigned to type. + 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 + internal bool CheckValue( + ref object? value, + ref bool copyBack, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr) + { + // Already fast-pathed by the caller. + Debug.Assert(!ReferenceEquals(value?.GetType(), this)); + + // 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); - Type type = value!.GetType(); + if (RuntimeTypeHandle.IsValueType(this)) + { + // Nullable is the only value type that will get here. + Debug.Assert(IsNullableOfT); + + // Fall through and treat as a reference type. + } + + return false; + } + + bool isValueType; + CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType); + if (result == CheckValueStatus.Success) + { + return isValueType; + } + + if (result == CheckValueStatus.ArgumentException && (invokeAttr & BindingFlags.ExactBinding) == 0) + { + Debug.Assert(value != null); - if (!ReferenceEquals(type, this) && RuntimeTypeHandle.IsValueType(this)) + // Use the binder + if (binder != null && binder != DefaultBinder) { - // must be an equivalent type, re-box to the target type - return AllocateValueType(this, value, true); + value = binder.ChangeType(value, this, culture); + if (IsInstanceOfType(value)) + { + copyBack = true; + return IsValueType; + } + + result = TryChangeType(ref value, out copyBack, out isValueType); + if (result == CheckValueStatus.Success) + { + return isValueType; + } } - else + } + + switch (result) + { + case CheckValueStatus.ArgumentException: + throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this)); + case CheckValueStatus.NotSupported_ByRefLike: + throw new NotSupportedException(SR.NotSupported_ByRefLike); + } + + Debug.Fail("Error result not expected"); + return false; + } + + private CheckValueStatus TryChangeType( + ref object? value, + out bool copyBack, + out bool isValueType) + { + // If this is a ByRef get the element type and check if it's compatible + if (IsByRef) + { + 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; + } + + if (sigElementType.IsInstanceOfType(value)) { - return 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 = copyBack = default; + return CheckValueStatus.NotSupported_ByRefLike; + } + + value = AllocateValueType(sigElementType, value, fForceTypeChange: false); + isValueType = sigElementType.IsValueType; + copyBack = true; + return CheckValueStatus.Success; + } + + if (NeedsSpecialCast()) + { + if (SpecialCast(sigElementType, ref value) == CheckValueStatus.Success) + { + isValueType = true; + copyBack = false; + return CheckValueStatus.Success; + } + } + + isValueType = copyBack = default; + return CheckValueStatus.ArgumentException; + } + + if (value == null) + { + if (!RuntimeTypeHandle.IsValueType(this)) + { + isValueType = false; + copyBack = false; + return CheckValueStatus.Success; + } + + if (IsNullableOfT) + { + // Treat as a boxed value. + isValueType = false; + copyBack = false; + return CheckValueStatus.Success; + } + + if (RuntimeTypeHandle.IsByRefLike(this)) + { + 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; + return CheckValueStatus.Success; } - // if this is a ByRef get the element type and check if it's compatible - bool isByRef = IsByRef; - if (isByRef) + if (NeedsSpecialCast()) { - RuntimeType elementType = RuntimeTypeHandle.GetElementType(this); - if (elementType.IsInstanceOfType(value) || value == null) + if (SpecialCast(this, ref value) == CheckValueStatus.Success) { - // need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type - return AllocateValueType(elementType, value, false); + isValueType = true; + copyBack = false; + return CheckValueStatus.Success; } } - else if (value == null) - return value; - else if (this == s_typedRef) - // everything works for a typedref - return value; - // check the strange ones courtesy of reflection: + 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; - if (needsSpecialCast) + bool NeedsSpecialCast() => IsPointer || IsEnum || IsPrimitive; + + static CheckValueStatus SpecialCast(RuntimeType type, ref object value) { - RuntimeType valueType; Pointer? pointer = value as Pointer; - if (pointer != null) - valueType = pointer.GetPointerType(); - else - valueType = (RuntimeType)value.GetType(); + RuntimeType srcType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); - if (CanValueSpecialCast(valueType, this)) + if (!CanValueSpecialCast(srcType, type)) { - if (pointer != null) - return pointer.GetPointerValue(); - else - return value; + return CheckValueStatus.ArgumentException; } - } - - if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), this)); - - return TryChangeType(value, binder, culture, needsSpecialCast); - } - // Factored out of CheckValue to reduce code complexity. - private object? TryChangeType(object value, Binder? binder, CultureInfo? culture, bool needsSpecialCast) - { - if (binder != null && binder != Type.DefaultBinder) - { - value = binder.ChangeType(value, this, culture); - if (IsInstanceOfType(value)) - return value; - // if this is a ByRef get the element type and check if it's compatible - if (IsByRef) + if (pointer != null) { - RuntimeType elementType = RuntimeTypeHandle.GetElementType(this); - if (elementType.IsInstanceOfType(value) || value == null) - return AllocateValueType(elementType, value, false); + value = pointer.GetPointerValue(); // Convert source pointer to IntPtr } - else if (value == null) - return value; - if (needsSpecialCast) + else { - RuntimeType valueType; - Pointer? pointer = value as Pointer; - if (pointer != null) - valueType = pointer.GetPointerType(); - else - valueType = (RuntimeType)value.GetType(); - - if (CanValueSpecialCast(valueType, this)) + CorElementType srcElementType = GetUnderlyingType(srcType); + CorElementType dstElementType = GetUnderlyingType(type); + if (dstElementType != srcElementType) { - if (pointer != null) - return pointer.GetPointerValue(); - else - return value; + value = InvokeUtils.ConvertOrWiden(srcType, srcElementType, value, type, dstElementType); } } + + return CheckValueStatus.Success; + } + } + + private static CorElementType GetUnderlyingType(RuntimeType type) + { + if (type.IsEnum) + { + type = (RuntimeType)Enum.GetUnderlyingType(type); } - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), this)); + return RuntimeTypeHandle.GetCorElementType(type); } #endregion diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 55076560a46a4b..f83ab000ddc091 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1556,7 +1556,10 @@ bool SystemDomain::IsReflectionInvocationMethod(MethodDesc* pMeth) CLASS__RUNTIME_HELPERS, CLASS__DYNAMICMETHOD, CLASS__DELEGATE, - CLASS__MULTICAST_DELEGATE + CLASS__MULTICAST_DELEGATE, + CLASS__METHOD_INVOKER, + CLASS__CONSTRUCTOR_INVOKER, + CLASS__DYNAMIC_METHOD_INVOKER }; static bool fInited = false; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index ee95e5fd8a6d47..33b536a60af39f 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -512,6 +512,9 @@ DEFINE_CLASS(VECTORT, Numerics, Vector`1) 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/ecall.h b/src/coreclr/vm/ecall.h index 538227cf13d6a6..bc9d63ae467137 100644 --- a/src/coreclr/vm/ecall.h +++ b/src/coreclr/vm/ecall.h @@ -129,4 +129,11 @@ class ECall extern "C" FCDECL1(VOID, FCComCtor, LPVOID pV); +class GCReporting final +{ +public: + static FCDECL1(void, Register, GCFrame*); + static FCDECL1(void, Unregister, GCFrame*); +}; + #endif // _ECALL_H_ diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 18c983ff40d628..0c0525eedfc899 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -606,6 +606,8 @@ FCFuncStart(gRuntimeHelpers) FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack) FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer) FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo) + FCFuncElement("RegisterForGCReporting", GCReporting::Register) + FCFuncElement("UnregisterForGCReporting", GCReporting::Unregister) FCFuncEnd() FCFuncStart(gMngdFixedArrayMarshalerFuncs) diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index c2fef85d92b554..ef17779910fb7d 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -4,6 +4,7 @@ #include "common.h" +#include "ecall.h" #include "eetwain.h" #include "dbginterface.h" #include "gcenv.h" @@ -4412,7 +4413,27 @@ void promoteVarArgs(PTR_BYTE argsStart, PTR_VASigCookie varArgSig, GCCONTEXT* ct } } -INDEBUG(void* forceStack1;) +#ifndef DACCESS_COMPILE +FCIMPL1(void, GCReporting::Register, GCFrame* frame) +{ + FCALL_CONTRACT; + + // Construct a GCFrame. + _ASSERTE(frame != NULL); + frame->Push(GetThread()); +} +FCIMPLEND + +FCIMPL1(void, GCReporting::Unregister, GCFrame* frame) +{ + FCALL_CONTRACT; + + // Destroy the GCFrame. + _ASSERTE(frame != NULL); + frame->Pop(); +} +FCIMPLEND +#endif // !DACCESS_COMPILE #ifndef USE_GC_INFO_DECODER diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index 9377b4ec028b72..4ac458b1d62f6f 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -391,7 +391,7 @@ VOID Frame::Push(Thread *pThread) // declared in the same source function. We cannot predict the order // in which the C compiler will lay them out in the stack frame. // So GetOsPageSize() is a guess of the maximum stack frame size of any method - // with multiple Frames in mscorwks.dll + // with multiple Frames in coreclr.dll _ASSERTE((pThread->IsExecutingOnAltStack() || (m_Next == FRAME_TOP) || (PBYTE(m_Next) + (2 * GetOsPageSize())) > PBYTE(this)) && @@ -903,6 +903,7 @@ GCFrame::GCFrame(Thread *pThread, OBJECTREF *pObjRefs, UINT numObjRefs, BOOL may NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; + PRECONDITION(pThread != NULL); } CONTRACTL_END; @@ -929,19 +930,67 @@ GCFrame::GCFrame(Thread *pThread, OBJECTREF *pObjRefs, UINT numObjRefs, BOOL may #endif // USE_CHECKED_OBJECTREFS +#ifdef _DEBUG + m_Next = NULL; + m_pCurThread = NULL; +#endif // _DEBUG + m_pObjRefs = pObjRefs; m_numObjRefs = numObjRefs; - m_pCurThread = pThread; m_MaybeInterior = maybeInterior; + Push(pThread); +} + +GCFrame::~GCFrame() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(m_pCurThread != NULL); + } + CONTRACTL_END; + + // Do a manual switch to the GC cooperative mode instead of using the GCX_COOP_THREAD_EXISTS + // macro so that this function isn't slowed down by having to deal with FS:0 chain on x86 Windows. + BOOL wasCoop = m_pCurThread->PreemptiveGCDisabled(); + if (!wasCoop) + { + m_pCurThread->DisablePreemptiveGC(); + } + + Pop(); + + if (!wasCoop) + { + m_pCurThread->EnablePreemptiveGC(); + } +} + +void GCFrame::Push(Thread* pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(pThread != NULL); + PRECONDITION(m_Next == NULL); + PRECONDITION(m_pCurThread == NULL); + } + CONTRACTL_END; + // Push the GC frame to the per-thread list m_Next = pThread->GetGCFrame(); + m_pCurThread = pThread; // GetOsPageSize() is used to relax the assert for cases where two Frames are // declared in the same source function. We cannot predict the order - // in which the C compiler will lay them out in the stack frame. + // in which the compiler will lay them out in the stack frame. // So GetOsPageSize() is a guess of the maximum stack frame size of any method - // with multiple Frames in mscorwks.dll + // with multiple GCFrames in coreclr.dll _ASSERTE(((m_Next == NULL) || (PBYTE(m_Next) + (2 * GetOsPageSize())) > PBYTE(this)) && "Pushing a GCFrame out of order ?"); @@ -949,24 +998,17 @@ GCFrame::GCFrame(Thread *pThread, OBJECTREF *pObjRefs, UINT numObjRefs, BOOL may pThread->SetGCFrame(this); } -GCFrame::~GCFrame() +void GCFrame::Pop() { CONTRACTL { NOTHROW; GC_NOTRIGGER; - MODE_ANY; + MODE_COOPERATIVE; + PRECONDITION(m_pCurThread != NULL); } CONTRACTL_END; - // Do a manual switch to the GC cooperative mode instead of using the GCX_COOP_THREAD_EXISTS - // macro so that this function isn't slowed down by having to deal with FS:0 chain on x86 Windows. - BOOL wasCoop = m_pCurThread->PreemptiveGCDisabled(); - if (!wasCoop) - { - m_pCurThread->DisablePreemptiveGC(); - } - // When the frame is destroyed, make sure it is no longer in the // frame chain managed by the Thread. // It also cancels the GC protection provided by the frame. @@ -981,12 +1023,8 @@ GCFrame::~GCFrame() for(UINT i = 0; i < m_numObjRefs; i++) Thread::ObjectRefNew(&m_pObjRefs[i]); // Unprotect them #endif - - if (!wasCoop) - { - m_pCurThread->EnablePreemptiveGC(); - } } +#endif // !DACCESS_COMPILE // // GCFrame Object Scanning @@ -995,9 +1033,6 @@ GCFrame::~GCFrame() // protected by the programmer explicitly protecting it in a GC Frame // via the GCPROTECTBEGIN / GCPROTECTEND facility... // - -#endif // !DACCESS_COMPILE - void GCFrame::GcScanRoots(promote_func *fn, ScanContext* sc) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 82e57cbbb04017..38bbc80cce3035 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2421,6 +2421,10 @@ class GCFrame GCFrame(Thread *pThread, OBJECTREF *pObjRefs, UINT numObjRefs, BOOL maybeInterior); ~GCFrame(); + // Push and pop this frame from the thread's stack. + void Push(Thread* pThread); + void Pop(); + #endif // DACCESS_COMPILE void GcScanRoots(promote_func *fn, ScanContext* sc); @@ -2446,9 +2450,9 @@ class GCFrame private: PTR_GCFrame m_Next; + PTR_Thread m_pCurThread; PTR_OBJECTREF m_pObjRefs; UINT m_numObjRefs; - PTR_Thread m_pCurThread; BOOL m_MaybeInterior; }; diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index b9779762d40cd7..e50cdc7f1511d5 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -27,7 +27,7 @@ // The Attributes Table // 20 bits for built in types and 12 bits for Properties -// The properties are followed by the widening mask. All types widen to them selves. +// The properties are followed by the widening mask. All types widen to themselves. const DWORD InvokeUtil::PrimitiveAttributes[PRIMITIVE_TABLE_SIZE] = { 0x00, // ELEMENT_TYPE_END 0x00, // ELEMENT_TYPE_VOID @@ -124,191 +124,109 @@ void *InvokeUtil::GetIntPtrValue(OBJECTREF pObj) { RETURN *(void **)((pObj)->UnBox()); } -void InvokeUtil::CopyArg(TypeHandle th, OBJECTREF *pObjUNSAFE, ArgDestination *argDest) { +void InvokeUtil::CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest) { CONTRACTL { THROWS; GC_NOTRIGGER; // Caller does not protect object references MODE_COOPERATIVE; PRECONDITION(!th.IsNull()); - PRECONDITION(CheckPointer(pObjUNSAFE)); INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; void *pArgDst = argDest->GetDestinationAddress(); + CorElementType type = th.GetVerifierCorElementType(); - OBJECTREF rObj = *pObjUNSAFE; - MethodTable* pMT; - CorElementType oType; - CorElementType type; - - if (rObj != 0) { - pMT = rObj->GetMethodTable(); - oType = pMT->GetInternalCorElementType(); - } - else { - pMT = 0; - oType = ELEMENT_TYPE_OBJECT; - } - type = th.GetVerifierCorElementType(); - - // This basically maps the Signature type our type and calls the CreatePrimitiveValue - // method. We can omit this if we get alignment on these types. switch (type) { case ELEMENT_TYPE_BOOLEAN: - case ELEMENT_TYPE_I1: case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I1: + { + _ASSERTE(argRef != NULL); + *(INT8 *)pArgDst = *(INT8 *)argRef; + break; + } + case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: + { + _ASSERTE(argRef != NULL); + *(INT16 *)pArgDst = *(INT16 *)argRef; + break; + } + case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_R4: - IN_TARGET_32BIT(case ELEMENT_TYPE_I:) IN_TARGET_32BIT(case ELEMENT_TYPE_U:) + IN_TARGET_32BIT(case ELEMENT_TYPE_I:) { - // If we got the univeral zero...Then assign it and exit. - if (rObj == 0) - *(PVOID *)pArgDst = 0; - else - { - ARG_SLOT slot; - CreatePrimitiveValue(type, oType, rObj, &slot); - *(PVOID *)pArgDst = (PVOID)slot; - } + _ASSERTE(argRef != NULL); + *(INT32 *)pArgDst = *(INT32 *)argRef; break; } - case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R8: IN_TARGET_64BIT(case ELEMENT_TYPE_I:) IN_TARGET_64BIT(case ELEMENT_TYPE_U:) { - // If we got the univeral zero...Then assign it and exit. - if (rObj == 0) - *(INT64 *)pArgDst = 0; - else - { - ARG_SLOT slot; - CreatePrimitiveValue(type, oType, rObj, &slot); - *(INT64 *)pArgDst = (INT64)slot; - } + _ASSERTE(argRef != NULL); + *(INT64 *)pArgDst = *(INT64 *)argRef; break; } case ELEMENT_TYPE_VALUETYPE: { - // If we got the universal zero...Then assign it and exit. - if (rObj == 0) { - InitValueClassArg(argDest, th.AsMethodTable()); - } - else { - if (!th.AsMethodTable()->UnBoxIntoArg(argDest, rObj)) + 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); + } break; } + case ELEMENT_TYPE_STRING: // System.String + case ELEMENT_TYPE_CLASS: // Class + case ELEMENT_TYPE_OBJECT: // System.Object case ELEMENT_TYPE_SZARRAY: // Single Dim case ELEMENT_TYPE_ARRAY: // General Array - case ELEMENT_TYPE_CLASS: // Class - case ELEMENT_TYPE_OBJECT: - case ELEMENT_TYPE_STRING: // System.String case ELEMENT_TYPE_VAR: { - if (rObj == 0) + if (argRef == NULL) *(PVOID *)pArgDst = 0; else - *(PVOID *)pArgDst = OBJECTREFToObject(rObj); + *(PVOID *)pArgDst = OBJECTREFToObject((OBJECTREF)(Object*)*(PVOID*)argRef); break; } case ELEMENT_TYPE_BYREF: { - // - // (obj is the parameter passed to MethodInfo.Invoke, by the caller) - // if argument is a primitive - // { - // if incoming argument, obj, is null - // Allocate a boxed object and place ref to it in 'obj' - // Unbox 'obj' and pass it to callee - // } - // if argument is a value class - // { - // if incoming argument, obj, is null - // Allocate an object of that valueclass, and place ref to it in 'obj' - // Unbox 'obj' and pass it to callee - // } - // if argument is an objectref - // { - // pass obj to callee - // } - // - TypeHandle thBaseType = th.AsTypeDesc()->GetTypeParam(); - // 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(thBaseType)); - - TypeHandle srcTH = TypeHandle(); - if (rObj == 0) - oType = thBaseType.GetSignatureCorElementType(); - else - srcTH = rObj->GetTypeHandle(); - - //CreateByRef only triggers GC in throw path, so it's OK to use the raw unsafe pointer - *(PVOID *)pArgDst = CreateByRef(thBaseType, oType, srcTH, rObj, pObjUNSAFE); - break; - } - - case ELEMENT_TYPE_TYPEDBYREF: - { - TypedByRef* ptr = (TypedByRef*) pArgDst; - TypeHandle srcTH; - BOOL bIsZero = FALSE; - - // If we got the univeral zero...Then assign it and exit. - if (rObj== 0) { - bIsZero = TRUE; - ptr->data = 0; - ptr->type = TypeHandle(); - } - else { - bIsZero = FALSE; - srcTH = rObj->GetTypeHandle(); - ptr->type = rObj->GetTypeHandle(); - } - - if (!bIsZero) - { - //CreateByRef only triggers GC in throw path - ptr->data = CreateByRef(srcTH, oType, srcTH, rObj, pObjUNSAFE); - } + _ASSERTE(!Nullable::IsNullableType(th.AsTypeDesc()->GetTypeParam())); + *(PVOID *)pArgDst = argRef; break; } case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_FNPTR: { - // If we got the univeral zero...Then assign it and exit. - if (rObj == 0) { - *(PVOID *)pArgDst = 0; - } - else { - if (rObj->GetMethodTable() == CoreLibBinder::GetClassIfExist(CLASS__POINTER) && type == ELEMENT_TYPE_PTR) - *(PVOID *)pArgDst = GetPointerValue(rObj); - else if (rObj->GetTypeHandle().AsMethodTable() == CoreLibBinder::GetElementType(ELEMENT_TYPE_I)) - { - ARG_SLOT slot; - CreatePrimitiveValue(oType, oType, rObj, &slot); - *(PVOID *)pArgDst = (PVOID)slot; - } - else - COMPlusThrow(kArgumentException,W("Arg_ObjObj")); - } + _ASSERTE(argRef != NULL); + MethodTable* pMT = th.GetMethodTable(); + CopyValueClassArg(argDest, argRef, pMT, 0); break; } @@ -342,10 +260,12 @@ void InvokeUtil::CreatePrimitiveValue(CorElementType dstType, CreatePrimitiveValue(dstType, srcType, srcObj->UnBox(), srcObj->GetMethodTable(), pDst); } -void InvokeUtil::CreatePrimitiveValue(CorElementType dstType,CorElementType srcType, - void *pSrc, MethodTable *pSrcMT, ARG_SLOT* pDst) +void InvokeUtil::CreatePrimitiveValue(CorElementType dstType, + CorElementType srcType, + void *pSrc, + MethodTable *pSrcMT, + ARG_SLOT* pDst) { - CONTRACTL { THROWS; GC_NOTRIGGER; @@ -499,46 +419,6 @@ void InvokeUtil::CreatePrimitiveValue(CorElementType dstType,CorElementType srcT } } -void* InvokeUtil::CreateByRef(TypeHandle dstTh, - CorElementType srcType, - TypeHandle srcTH, - OBJECTREF srcObj, - OBJECTREF *pIncomingObj) { - CONTRACTL { - THROWS; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(!dstTh.IsNull()); - PRECONDITION(CheckPointer(pIncomingObj)); - - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - CorElementType dstType = dstTh.GetSignatureCorElementType(); - if (IsPrimitiveType(srcType) && IsPrimitiveType(dstType)) { - if (dstType != srcType) - { - CONTRACT_VIOLATION (GCViolation); - COMPlusThrow(kArgumentException,W("Arg_PrimWiden")); - } - - return srcObj->UnBox(); - } - - if (srcTH.IsNull()) { - return pIncomingObj; - } - - _ASSERTE(srcObj != NULL); - - if (dstType == ELEMENT_TYPE_VALUETYPE) { - return srcObj->UnBox(); - } - else - return pIncomingObj; -} - //ValidField // This method checks that the object can be widened to the proper type void InvokeUtil::ValidField(TypeHandle th, OBJECTREF* value) @@ -613,7 +493,6 @@ void InvokeUtil::ValidField(TypeHandle th, OBJECTREF* value) return; } - if (!IsPrimitiveType(oType)) COMPlusThrow(kArgumentException,W("Arg_ObjObj")); // Now make sure we can widen into the proper type -- CanWiden may run GC... diff --git a/src/coreclr/vm/invokeutil.h b/src/coreclr/vm/invokeutil.h index b3908f02aa3242..7fe16289f43327 100644 --- a/src/coreclr/vm/invokeutil.h +++ b/src/coreclr/vm/invokeutil.h @@ -51,7 +51,7 @@ class InvokeUtil { public: - static void CopyArg(TypeHandle th, OBJECTREF *obj, ArgDestination *argDest); + static void CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest); // Given a type, this routine will convert an return value representing that // type into an ObjectReference. If the type is a primitive, the @@ -113,7 +113,7 @@ class InvokeUtil static BOOL IsVoidPtr(TypeHandle th); // CanPrimitiveWiden - // This method determines if the srcType and be widdened without loss to the destType + // This method determines if the srcType can be widened without loss to the destType // destType -- The target type // srcType -- The source type. inline static DWORD CanPrimitiveWiden(const CorElementType destType, const CorElementType srcType) @@ -154,9 +154,6 @@ class InvokeUtil static void* GetPointerValue(OBJECTREF pObj); static void* GetIntPtrValue(OBJECTREF pObj); -private: - static void* CreateByRef(TypeHandle dstTh,CorElementType srcType, TypeHandle srcTH,OBJECTREF srcObj, OBJECTREF *pIncomingObj); - private: // The Attributes Table // This constructs a table of legal widening operations diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 8071392c9b4cce..703e1edabf5bf1 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -1656,7 +1656,7 @@ BOOL Nullable::UnBox(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) if (boxedVal == NULL) { // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure becasue it may contain GC references + // 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) InitValueClass(destPtr, destMT); fRet = TRUE; @@ -1703,16 +1703,16 @@ BOOL Nullable::UnBoxNoGC(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) 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) { // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure becasue it may contain GC references + // 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) InitValueClass(destPtr, destMT); } @@ -1762,7 +1762,7 @@ BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, Met if (boxedVal == NULL) { // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure becasue it may contain GC references + // 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); } @@ -1816,7 +1816,7 @@ void Nullable::UnBoxNoCheck(void* destPtr, OBJECTREF boxedVal, MethodTable* dest if (boxedVal == NULL) { // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure becasue it may contain GC references + // 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) InitValueClass(destPtr, destMT); } diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index bf3f9c0bc17bc9..4bd551382195f7 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1092,7 +1092,7 @@ class ReflectClassBaseObject : public BaseObjectWithCachedData }; // This is the Method version of the Reflection object. -// A Method has adddition information. +// A Method has additional information: // m_pMD - A pointer to the actual MethodDesc of the method. // m_object - a field that has a reference type in it. Used only for RuntimeMethodInfoStub to keep the real type alive. // This structure matches the structure up to the m_pMD for several different managed types. @@ -1112,6 +1112,7 @@ class ReflectMethodObject : public BaseObjectWithCachedData OBJECTREF m_empty5; OBJECTREF m_empty6; OBJECTREF m_empty7; + OBJECTREF m_empty8; MethodDesc * m_pMD; public: @@ -1135,7 +1136,7 @@ class ReflectMethodObject : public BaseObjectWithCachedData }; // This is the Field version of the Reflection object. -// A Method has adddition information. +// A Method has additional information: // m_pFD - A pointer to the actual MethodDesc of the method. // m_object - a field that has a reference type in it. Used only for RuntimeFieldInfoStub to keep the real type alive. // This structure matches the structure up to the m_pFD for several different managed types. @@ -1152,6 +1153,7 @@ class ReflectFieldObject : public BaseObjectWithCachedData INT32 m_empty2; OBJECTREF m_empty3; OBJECTREF m_empty4; + OBJECTREF m_empty5; FieldDesc * m_pFD; public: diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 1f24faf245d0e8..197d4ac0262322 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -1,7 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// #include "common.h" #include "reflectioninvocation.h" @@ -50,86 +48,6 @@ static TypeHandle NullableTypeOfByref(TypeHandle th) { return subType; } -static void TryCallMethodWorker(MethodDescCallSite* pMethodCallSite, ARG_SLOT* args, Frame* pDebuggerCatchFrame) -{ - // Use static contracts b/c we have SEH. - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_ANY; - - struct Param: public NotifyOfCHFFilterWrapperParam - { - MethodDescCallSite * pMethodCallSite; - ARG_SLOT* args; - } param; - - param.pFrame = pDebuggerCatchFrame; - param.pMethodCallSite = pMethodCallSite; - param.args = args; - - PAL_TRY(Param *, pParam, ¶m) - { - pParam->pMethodCallSite->CallWithValueTypes(pParam->args); - } - PAL_EXCEPT_FILTER(NotifyOfCHFFilterWrapper) - { - // Should never reach here b/c handler should always continue search. - _ASSERTE(false); - } - PAL_ENDTRY -} - -// Warning: This method has subtle differences from CallDescrWorkerReflectionWrapper -// In particular that one captures watson bucket data and corrupting exception severity, -// then transfers that data to the newly produced TargetInvocationException. This one -// doesn't take those same steps. -// -static void TryCallMethod(MethodDescCallSite* pMethodCallSite, ARG_SLOT* args, bool wrapExceptions) { - CONTRACTL { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - if (wrapExceptions) - { - OBJECTREF ppException = NULL; - GCPROTECT_BEGIN(ppException); - - // The sole purpose of having this frame is to tell the debugger that we have a catch handler here - // which may swallow managed exceptions. The debugger needs this in order to send a - // CatchHandlerFound (CHF) notification. - FrameWithCookie catchFrame; - EX_TRY{ - TryCallMethodWorker(pMethodCallSite, args, &catchFrame); - } - EX_CATCH{ - ppException = GET_THROWABLE(); - _ASSERTE(ppException); - } - EX_END_CATCH(RethrowTransientExceptions) - catchFrame.Pop(); - - // It is important to re-throw outside the catch block because re-throwing will invoke - // the jitter and managed code and will cause us to use more than the backout stack limit. - if (ppException != NULL) - { - // If we get here we need to throw an TargetInvocationException - OBJECTREF except = InvokeUtil::CreateTargetExcept(&ppException); - COMPlusThrow(except); - } - GCPROTECT_END(); - } - else - { - pMethodCallSite->CallWithValueTypes(args); - } -} - - - - FCIMPL5(Object*, RuntimeFieldHandle::GetValue, ReflectFieldObject *pFieldUNSAFE, Object *instanceUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pDomainInitialized) { CONTRACTL { FCALL_CHECK; @@ -451,34 +369,7 @@ struct ByRefToNullable { } }; -static void CallDescrWorkerReflectionWrapper(CallDescrData * pCallDescrData, Frame * pFrame) -{ - // Use static contracts b/c we have SEH. - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_TRIGGERS; - STATIC_CONTRACT_MODE_ANY; - - struct Param: public NotifyOfCHFFilterWrapperParam - { - CallDescrData * pCallDescrData; - } param; - - param.pFrame = pFrame; - param.pCallDescrData = pCallDescrData; - - PAL_TRY(Param *, pParam, ¶m) - { - CallDescrWorkerWithHandler(pParam->pCallDescrData); - } - PAL_EXCEPT_FILTER(ReflectionInvocationExceptionFilter) - { - // Should never reach here b/c handler should always continue search. - _ASSERTE(false); - } - PAL_ENDTRY -} // CallDescrWorkerReflectionWrapper - -static OBJECTREF InvokeArrayConstructor(TypeHandle th, Span* objs, int argCnt) +static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) { CONTRACTL { THROWS; @@ -498,20 +389,14 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, Span* objs, in INT32* indexes = (INT32*) _alloca((size_t)allocSize); ZeroMemory(indexes, allocSize); + MethodTable* pMT = CoreLibBinder::GetElementType(ELEMENT_TYPE_I4); for (DWORD i=0; i<(DWORD)argCnt; i++) { - if (!objs->GetAt(i)) - COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); + _ASSERTE(args[i] != NULL); - MethodTable* pMT = objs->GetAt(i)->GetMethodTable(); - CorElementType oType = TypeHandle(pMT).GetVerifierCorElementType(); - - if (!InvokeUtil::IsPrimitiveType(oType) || !InvokeUtil::CanPrimitiveWiden(ELEMENT_TYPE_I4,oType)) - COMPlusThrow(kArgumentException,W("Arg_PrimWiden")); - - ARG_SLOT value; - InvokeUtil::CreatePrimitiveValue(ELEMENT_TYPE_I4, oType, objs->GetAt(i), &value); + INT32 size = *(INT32*)args[i]; + ARG_SLOT value = size; memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); } @@ -645,117 +530,11 @@ class ArgIteratorForMethodInvoke : public ArgIteratorTemplateAreWatsonBucketsPresent()) - { - // If an exception is raised by the VM (e.g. type load exception by the JIT) and it comes - // across the reflection invocation boundary before CLR's personality routine for managed - // code has been invoked, then no buckets would be available for us at this point. - // - // Since we cannot assert this, better log it for diagnosis if required. - LOG((LF_EH, LL_INFO100, "InvokeImpl - No watson buckets available - regular exception likely raised within VM and not seen by managed code.\n")); - } - } - else - { - // Exception is preallocated. - PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = GetThread()->GetExceptionState()->GetUEWatsonBucketTracker(); - if ((IsThrowableThreadAbortException(targetException) && pUEWatsonBucketTracker->CapturedForThreadAbort())|| - (pUEWatsonBucketTracker->CapturedAtReflectionInvocation())) - { - // ReflectionInvocationExceptionFilter would have captured - // the watson bucket details for preallocated exceptions - // in the UE watson bucket tracker. - - if(pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) - { - // See comment above - LOG((LF_EH, LL_INFO100, "InvokeImpl - No watson buckets available - preallocated exception likely raised within VM and not seen by managed code.\n")); - } - } - } - } -#endif // _DEBUG && !TARGET_UNIX - - OBJECTREF except = InvokeUtil::CreateTargetExcept(&targetException); - -#ifndef TARGET_UNIX - if (IsWatsonEnabled()) - { - struct - { - OBJECTREF oExcept; - } gcTIE; - ZeroMemory(&gcTIE, sizeof(gcTIE)); - GCPROTECT_BEGIN(gcTIE); - - gcTIE.oExcept = except; - - _ASSERTE(!CLRException::IsPreallocatedExceptionObject(gcTIE.oExcept)); - - // If the original exception was preallocated, then copy over the captured - // watson buckets to the TargetInvocationException object, if available. - // - // We dont need to do this if the original exception was not preallocated - // since it already contains the watson buckets inside the object. - if (CLRException::IsPreallocatedExceptionObject(targetException)) - { - PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = GetThread()->GetExceptionState()->GetUEWatsonBucketTracker(); - BOOL fCopyWatsonBuckets = TRUE; - PTR_VOID pBuckets = pUEWatsonBucketTracker->RetrieveWatsonBuckets(); - if (pBuckets != NULL) - { - // Copy the buckets to the exception object - CopyWatsonBucketsToThrowable(pBuckets, gcTIE.oExcept); - - // Confirm that they are present. - _ASSERTE(((EXCEPTIONREF)gcTIE.oExcept)->AreWatsonBucketsPresent()); - } - - // Clear the UE watson bucket tracker since the bucketing - // details are now in the TargetInvocationException object. - pUEWatsonBucketTracker->ClearWatsonBucketDetails(); - } - - // update "except" incase the reference to the object - // was updated by the GC - except = gcTIE.oExcept; - GCPROTECT_END(); - } -#endif // !TARGET_UNIX - - // Since the original exception is inner of target invocation exception, - // when TIE is seen to be raised for the first time, we will end up - // using the inner exception buckets automatically. - - // Since VM is throwing the exception, we set it to use the same corruption severity - // that the original exception came in with from reflection invocation. - COMPlusThrow(except); - - GCPROTECT_END(); -} - -FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, - Object *target, Span* objs, SignatureNative* pSigUNSAFE, - CLR_BOOL fConstructor, CLR_BOOL fWrapExceptions) +FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, + Object *target, + PVOID* args, // An array of byrefs + SignatureNative* pSigUNSAFE, + CLR_BOOL fConstructor) { FCALL_CONTRACT; @@ -775,7 +554,9 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); if (ownerType.IsSharedByGenericInstantiations()) + { COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); + } #ifdef _DEBUG if (g_pConfig->ShouldInvokeHalt(pMeth)) @@ -792,7 +573,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, // handle this specially. if (ownerType.IsArray()) { gc.retVal = InvokeArrayConstructor(ownerType, - objs, + args, gc.pSig->NumFixedArgs()); goto Done; } @@ -911,8 +692,8 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, if (pMeth->IsUnboxingStub()) pThisPtr = OBJECTREFToObject(gc.target); else { - // Create a true boxed Nullable and use that as the 'this' pointer. - // since what is passed in is just a boxed T + // Create a true boxed Nullable and use that as the 'this' pointer. + // since what is passed in is just a boxed T MethodTable* pMT = pMeth->GetMethodTable(); if (Nullable::IsNullableType(pMT)) { OBJECTREF bufferObj = pMT->Allocate(); @@ -952,7 +733,6 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, // copy args UINT nNumArgs = gc.pSig->NumFixedArgs(); for (UINT i = 0 ; i < nNumArgs; i++) { - TypeHandle th = gc.pSig->GetArgumentAt(i); int ofs = argit.GetNextOffset(); @@ -979,12 +759,13 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, UINT structSize = argit.GetArgSize(); bool needsStackCopy = false; + ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - // 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. 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; @@ -996,18 +777,15 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } #endif - ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - - if(needsStackCopy) + if (needsStackCopy) { - MethodTable * pMT = th.GetMethodTable(); + MethodTable* pMT = th.GetMethodTable(); _ASSERTE(pMT && pMT->IsValueType()); PVOID pArgDst = argDest.GetDestinationAddress(); PVOID pStackCopy = _alloca(structSize); *(PVOID *)pArgDst = pStackCopy; - pArgDst = pStackCopy; if (!nullableType.IsNull()) { @@ -1024,7 +802,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, argDest = ArgDestination(pStackCopy, 0, NULL); } - InvokeUtil::CopyArg(th, &objs->GetAt(i), &argDest); + InvokeUtil::CopyArg(th, args[i], &argDest); } ENDFORBIDGC(); @@ -1037,48 +815,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } // Call the method - bool fExceptionThrown = false; - if (fWrapExceptions) - { - // The sole purpose of having this frame is to tell the debugger that we have a catch handler here - // which may swallow managed exceptions. The debugger needs this in order to send a - // CatchHandlerFound (CHF) notification. - FrameWithCookie catchFrame(pThread); - - EX_TRY_THREAD(pThread) { - CallDescrWorkerReflectionWrapper(&callDescrData, &catchFrame); - } EX_CATCH{ - // Rethrow transient exceptions for constructors for backward compatibility - if (fConstructor && GET_EXCEPTION()->IsTransient()) - { - EX_RETHROW; - } - - // Abuse retval to store the exception object - gc.retVal = GET_THROWABLE(); - _ASSERTE(gc.retVal); - -#ifdef _MSC_VER - // Workaround bogus MSVC warning about uninitialized local variables - *(BYTE*)&callDescrData.returnValue = 0; -#endif - - fExceptionThrown = true; - } EX_END_CATCH(SwallowAllExceptions); - - catchFrame.Pop(pThread); - } - else - { - CallDescrWorkerWithHandler(&callDescrData); - } - - // Now that we are safely out of the catch block, we can create and raise the - // TargetInvocationException. - if (fExceptionThrown) - { - ThrowInvokeMethodException(pMeth, gc.retVal); - } + CallDescrWorkerWithHandler(&callDescrData); // It is still illegal to do a GC here. The return type might have/contain GC pointers. if (fConstructor) @@ -1117,8 +854,8 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, // From here on out, it is OK to have GCs since the return object (which may have had // GC pointers has been put into a GC object and thus protected. - // TODO this creates two objects which is inefficient - // If the return type is a Nullable box it into the correct form + // TODO this creates two objects which is inefficient + // If the return type is a Nullable box it into the correct form gc.retVal = Nullable::NormalizeBox(gc.retVal); } else if (retType == ELEMENT_TYPE_BYREF) @@ -1139,7 +876,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, while (byRefToNullables != NULL) { OBJECTREF obj = Nullable::Box(byRefToNullables->data, byRefToNullables->type.GetMethodTable()); - SetObjectReference(&objs->GetAt(byRefToNullables->argNum), obj); + SetObjectReference((OBJECTREF*)args[byRefToNullables->argNum], obj); byRefToNullables = byRefToNullables->next; } @@ -1332,13 +1069,9 @@ FCIMPL4(Object*, RuntimeFieldHandle::GetValueDirect, ReflectFieldObject *pFieldU break; case ELEMENT_TYPE_PTR: - { - p = ((BYTE*) pTarget->data) + pField->GetOffset(); - - refRet = InvokeUtil::CreatePointer(fieldType, *(void **)p); - - break; - } + p = ((BYTE*) pTarget->data) + pField->GetOffset(); + refRet = InvokeUtil::CreatePointer(fieldType, *(void **)p); + break; default: _ASSERTE(!"Unknown Type"); @@ -1781,7 +1514,7 @@ FCIMPL1(Object*, ReflectionInvocation::TypedReferenceToObject, TypedByRef * valu if (pMT->IsValueType()) { // value->data is protected by the caller - HELPER_METHOD_FRAME_BEGIN_RET_1(Obj); + HELPER_METHOD_FRAME_BEGIN_RET_1(Obj); Obj = pMT->Box(value->data); diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 85b7fa3d8e7f5a..29bdfa08a846f9 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -222,7 +222,7 @@ class RuntimeMethodHandle { public: static FCDECL1(ReflectMethodObject*, GetCurrentMethod, StackCrawlMark* stackMark); - static FCDECL5(Object*, InvokeMethod, Object *target, Span* objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL fWrapExceptions); + static FCDECL4(Object*, InvokeMethod, Object *target, PVOID* args, SignatureNative* pSig, CLR_BOOL fConstructor); struct StreamingContextData { Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a diff --git a/src/libraries/System.Memory/tests/Span/Reflection.cs b/src/libraries/System.Memory/tests/Span/Reflection.cs index 89fc722414348e..77fb1159fa0548 100644 --- a/src/libraries/System.Memory/tests/Span/Reflection.cs +++ b/src/libraries/System.Memory/tests/Span/Reflection.cs @@ -32,11 +32,8 @@ public static void MemoryExtensions_StaticReturningReadOnlySpan() public static void MemoryExtensions_StaticWithSpanArguments() { Type type = typeof(MemoryExtensions); - MethodInfo method = type.GetMethod(nameof(MemoryExtensions.CompareTo)); - - int result = (int)method.Invoke(null, new object[] { default, default, StringComparison.Ordinal }); - Assert.Equal(0, result); + Assert.Throws(() => method.Invoke(null, new object[] { default, default, StringComparison.Ordinal })); } [Fact] @@ -45,11 +42,10 @@ public static void BinaryPrimitives_StaticWithSpanArgument() Type type = typeof(BinaryPrimitives); MethodInfo method = type.GetMethod(nameof(BinaryPrimitives.ReadInt16LittleEndian)); - Assert.Throws(() => method.Invoke(null, new object[] { default })); + Assert.Throws(() => method.Invoke(null, new object[] { default })); method = type.GetMethod(nameof(BinaryPrimitives.TryReadInt16LittleEndian)); - bool result = (bool)method.Invoke(null, new object[] { default, null }); - Assert.False(result); + Assert.Throws(() => method.Invoke(null, new object[] { default, null })); } [Fact] diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index d3505fea8c9576..65f7d81c06eeb3 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -636,9 +636,6 @@ Specified method is not supported. - - Arrays indexes must be set to an object instance. - Object reference not set to an instance of an object. 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 0b77ecb3fb0878..fc5a71d6bbd2ba 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 @@ -580,6 +580,7 @@ + @@ -604,6 +605,7 @@ + @@ -613,6 +615,7 @@ + @@ -626,6 +629,7 @@ + @@ -2395,4 +2399,4 @@ - + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs new file mode 100644 index 00000000000000..48fe08e491026a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -0,0 +1,26 @@ +// 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; + +namespace System.Reflection +{ + internal sealed partial class ConstructorInvoker + { + private readonly RuntimeConstructorInfo _constructorInfo; + public InvocationFlags _invocationFlags; + + public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) + { + _constructorInfo = constructorInfo; + } + + [DebuggerStepThrough] + [DebuggerHidden] + public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return _constructorInfo.InvokeNonEmitUnsafe(obj, args, argsForTemporaryMonoSupport, invokeAttr); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs new file mode 100644 index 00000000000000..530c5e767f7745 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs @@ -0,0 +1,34 @@ +// 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; + +namespace System.Reflection +{ + internal sealed partial class FieldAccessor + { + private readonly RtFieldInfo _fieldInfo; + public InvocationFlags _invocationFlags; + + public FieldAccessor(RtFieldInfo fieldInfo) + { + _fieldInfo = fieldInfo; + } + + [DebuggerStepThrough] + [DebuggerHidden] + public object? GetValue(object? obj) + { + // Todo: add strategy for calling IL Emit-based version + return _fieldInfo.GetValueNonEmit(obj); + } + + [DebuggerStepThrough] + [DebuggerHidden] + public void SetValue(object? obj, object? value) + { + // Todo: add strategy for calling IL Emit-based version + _fieldInfo.SetValueNonEmit(obj, value); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs new file mode 100644 index 00000000000000..74cf3c81817815 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -0,0 +1,127 @@ +// 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; +using System.Runtime.CompilerServices; +using System.Diagnostics; + +namespace System.Reflection +{ + internal static class InvokeUtils + { + // This method is similar to the NativeAot method ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(). + public static object ConvertOrWiden(Type srcType, CorElementType srcElementType, object srcObject, Type dstType, CorElementType dstElementType) + { + object dstObject; + + if (dstType.IsPointer) + { + if (TryConvertPointer(srcObject, srcElementType, dstElementType, out IntPtr dstIntPtr)) + { + return dstIntPtr; + } + + Debug.Fail($"Unexpected CorElementType: {dstElementType}. Not a valid widening target."); + throw new NotSupportedException(); + } + + switch (dstElementType) + { + case CorElementType.ELEMENT_TYPE_BOOLEAN: + bool boolValue = Convert.ToBoolean(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, boolValue ? 1 : 0) : boolValue; + break; + + case CorElementType.ELEMENT_TYPE_CHAR: + char charValue = Convert.ToChar(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, charValue) : charValue; + break; + + case CorElementType.ELEMENT_TYPE_I1: + sbyte sbyteValue = Convert.ToSByte(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, sbyteValue) : sbyteValue; + break; + + case CorElementType.ELEMENT_TYPE_I2: + short shortValue = Convert.ToInt16(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, shortValue) : shortValue; + break; + + case CorElementType.ELEMENT_TYPE_I4: + int intValue = Convert.ToInt32(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, intValue) : intValue; + break; + + case CorElementType.ELEMENT_TYPE_I8: + long longValue = Convert.ToInt64(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, longValue) : longValue; + break; + + case CorElementType.ELEMENT_TYPE_U1: + byte byteValue = Convert.ToByte(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, byteValue) : byteValue; + break; + + case CorElementType.ELEMENT_TYPE_U2: + ushort ushortValue = Convert.ToUInt16(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, ushortValue) : ushortValue; + break; + + case CorElementType.ELEMENT_TYPE_U4: + uint uintValue = Convert.ToUInt32(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, uintValue) : uintValue; + break; + + case CorElementType.ELEMENT_TYPE_U8: + ulong ulongValue = Convert.ToUInt64(srcObject); + dstObject = dstType.IsEnum ? Enum.ToObject(dstType, (long)ulongValue) : ulongValue; + break; + + case CorElementType.ELEMENT_TYPE_R4: + if (srcType == typeof(char)) + { + dstObject = (float)(char)srcObject; + } + else + { + dstObject = Convert.ToSingle(srcObject); + } + break; + + case CorElementType.ELEMENT_TYPE_R8: + if (srcType == typeof(char)) + { + dstObject = (double)(char)srcObject; + } + else + { + dstObject = Convert.ToDouble(srcObject); + } + break; + + default: + Debug.Fail($"Unexpected CorElementType: {dstElementType}. Not a valid widening target."); + throw new NotSupportedException(); + } + + Debug.Assert(dstObject != null); + Debug.Assert(dstObject.GetType() == dstType); + return dstObject; + } + + private static bool TryConvertPointer(object srcObject, CorElementType srcEEType, CorElementType dstEEType, out IntPtr dstIntPtr) + { + if (srcObject is IntPtr srcIntPtr) + { + dstIntPtr = srcIntPtr; + return true; + } + + // The source pointer should already have been converted to an IntPtr. + Debug.Assert(srcObject is not Pointer); + + dstIntPtr = IntPtr.Zero; + return false; + } + } +} 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 f1267bb7781706..65b371a630191c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -140,55 +140,130 @@ private protected void ValidateInvokeTarget(object? target) } } - private protected Span CheckArguments(ref StackAllocedArguments stackArgs, ReadOnlySpan parameters, Binder? binder, - BindingFlags invokeAttr, CultureInfo? culture, RuntimeType[] sigTypes) + private protected unsafe void CheckArguments( + Span copyOfParameters, + IntPtr* byrefParameters, + Span shouldCopyBack, + ReadOnlySpan parameters, + RuntimeType[] sigTypes, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr + ) { - Debug.Assert(Unsafe.SizeOf() == StackAllocedArguments.MaxStackAllocArgCount * Unsafe.SizeOf(), - "MaxStackAllocArgCount not properly defined."); Debug.Assert(!parameters.IsEmpty); - // We need to perform type safety validation against the incoming arguments, but we also need - // to be resilient against the possibility that some other thread (or even the binder itself!) - // may mutate the array after we've validated the arguments but before we've properly invoked - // the method. The solution is to copy the arguments to a different, not-user-visible buffer - // 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. - - Span copyOfParameters = (parameters.Length <= StackAllocedArguments.MaxStackAllocArgCount) - ? MemoryMarshal.CreateSpan(ref stackArgs._arg0, parameters.Length) - : new Span(new object?[parameters.Length]); - - ParameterInfo[]? p = null; + ParameterInfo[]? paramInfos = null; for (int i = 0; i < parameters.Length; i++) { + bool copyBackArg = false; + bool isValueType; object? arg = parameters[i]; - RuntimeType argRT = sigTypes[i]; + RuntimeType sigType = sigTypes[i]; - if (arg == Type.Missing) + if (arg is null) { - p ??= GetParametersNoCopy(); - if (p[i].DefaultValue == System.DBNull.Value) - throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); - arg = p[i].DefaultValue!; + // Fast path that avoids calling CheckValue() for 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)) + { + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); + } + else + { + if (paramInfo.DefaultValue == DBNull.Value) + { + throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); + } + + arg = paramInfo.DefaultValue; + if (ReferenceEquals(arg?.GetType(), sigType)) + { + isValueType = RuntimeTypeHandle.IsValueType(sigType); + } + else + { + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); + } + } } - copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr); - } - return copyOfParameters; + // We need to perform type safety validation against the incoming arguments, but we also need + // to be resilient against the possibility that some other thread (or even the binder itself!) + // may mutate the array after we've validated the arguments but before we've properly invoked + // the method. The solution is to copy the arguments to a different, not-user-visible buffer + // 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. + 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); +#endif + ByReference valueTypeRef = new(ref copyOfParameters[i]!.GetRawData()); + *(ByReference*)(byrefParameters + i) = valueTypeRef; + } + else + { + ByReference objRef = new(ref copyOfParameters[i]); + *(ByReference*)(byrefParameters + i) = objRef; + } + } } + internal const int MaxStackAllocArgCount = 4; + // Helper struct to avoid intermediate object[] allocation in calls to the native reflection stack. - // Typical usage is to define a local of type default(StackAllocedArguments), then pass 'ref theLocal' - // as the first parameter to CheckArguments. CheckArguments will try to utilize storage within this - // struct instance if there's sufficient space; otherwise CheckArguments will allocate a temp array. - private protected struct StackAllocedArguments + // When argument count <= MaxStackAllocArgCount, define a local of type default(StackAllocatedByRefs) + // and pass it to CheckArguments(). + // For argument count > MaxStackAllocArgCount, do a stackalloc of void* pointers along with + // GCReportingRegistration to safely track references. + [StructLayout(LayoutKind.Sequential)] + private protected ref struct StackAllocedArguments { - internal const int MaxStackAllocArgCount = 4; internal object? _arg0; #pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic private object? _arg1; private object? _arg2; private object? _arg3; +#pragma warning restore CA1823, CS0169, IDE0051 + internal bool _copyBack0; +#pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic + private bool _copyBack1; + private bool _copyBack2; + private bool _copyBack3; +#pragma warning restore CA1823, CS0169, IDE0051 + } + + // Helper struct to avoid intermediate IntPtr[] allocation and RegisterForGCReporting in calls to the native reflection stack. + [StructLayout(LayoutKind.Sequential)] + private protected ref struct StackAllocatedByRefs + { + internal ByReference _arg0; +#pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic + private ByReference _arg1; + private ByReference _arg2; + private ByReference _arg3; #pragma warning restore CA1823, CS0169, IDE0051 } #endif diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs new file mode 100644 index 00000000000000..65c4e2bac2e746 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -0,0 +1,26 @@ +// 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; + +namespace System.Reflection +{ + internal sealed partial class MethodInvoker + { + internal InvocationFlags _invocationFlags; + private readonly RuntimeMethodInfo _methodInfo; + + public MethodInvoker(RuntimeMethodInfo methodInfo) + { + _methodInfo = methodInfo; + } + + [DebuggerStepThrough] + [DebuggerHidden] + public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return _methodInfo.InvokeNonEmitUnsafe(obj, args, argsForTemporaryMonoSupport, invokeAttr); + } + } +} 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 ee8b2a02de4613..c5f4d7b1345c7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using static System.Runtime.CompilerServices.RuntimeHelpers; namespace System.Reflection { @@ -92,10 +93,14 @@ internal void ThrowNoInvokeException() throw new TargetException(); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override object? Invoke( - object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) { if ((InvocationFlags & InvocationFlags.NoInvoke) != 0) ThrowNoInvokeException(); @@ -103,8 +108,8 @@ internal void ThrowNoInvokeException() ValidateInvokeTarget(obj); // Correct number of arguments supplied - int actualCount = (parameters is null) ? 0 : parameters.Length; - if (ArgumentTypes.Length != actualCount) + int argCount = (parameters is null) ? 0 : parameters.Length; + if (ArgumentTypes.Length != argCount) { throw new TargetParameterCountException(SR.Arg_ParmCnt); } @@ -117,27 +122,113 @@ internal void ThrowNoInvokeException() return null; } - StackAllocedArguments stackArgs = default; - Span arguments = default; - if (actualCount != 0) + Debug.Assert(obj != null); + + unsafe { - arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, ArgumentTypes); + if (argCount == 0) + { + Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); + } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); + } + else + { + Debug.Assert(parameters != null); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; + + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, 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]) + { + parameters[i] = copyOfParameters[i]; + } + } + } } - object? retValue = InvokeWorker(obj, invokeAttr, arguments); + return null; + } + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + // This is a separate method to support better performance for the faster paths. + [DebuggerStepThrough] + [DebuggerHidden] + private static unsafe void InvokeWithManyArguments( + RuntimeConstructorInfo ci, + int argCount, + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(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); - // copy out. This should be made only if ByRef are present. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + + try { - parameters![index] = arguments[index]; + RegisterForGCReporting(®); + ci.CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ci.ArgumentTypes, + binder, + culture, + invokeAttr); + + ci.Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); } - return retValue; + // 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]) + { + parameters[i] = copyOfParameters[i]; + } + } } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) { if ((InvocationFlags & (InvocationFlags.NoInvoke | InvocationFlags.ContainsStackPointers | InvocationFlags.NoConstructorInvoke)) != 0) @@ -149,25 +240,117 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] // JIT will insert the call to .cctor in the instance ctor. // Correct number of arguments supplied - int actualCount = (parameters is null) ? 0 : parameters.Length; - if (ArgumentTypes.Length != actualCount) + int argCount = (parameters is null) ? 0 : parameters.Length; + if (ArgumentTypes.Length != argCount) { throw new TargetParameterCountException(SR.Arg_ParmCnt); } - StackAllocedArguments stackArgs = default; - Span arguments = default; - if (actualCount != 0) + object? retValue; + + unsafe { - arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, ArgumentTypes); + if (argCount == 0) + { + retValue = Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, invokeAttr); + } + else if (argCount > MaxStackAllocArgCount) + { + retValue = InvokeWithManyArguments(this, argCount, invokeAttr, binder, parameters, culture); + } + else + { + Debug.Assert(parameters != null); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; + + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj: null, pByRefStorage, copyOfParameters, 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]) + { + parameters[i] = copyOfParameters[i]; + } + } + } } - object retValue = InvokeCtorWorker(invokeAttr, arguments); + Debug.Assert(retValue != null); + return retValue; + } + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + // This is a separate method to encourage more efficient IL for the faster paths. + [DebuggerStepThrough] + [DebuggerHidden] + private static unsafe object? InvokeWithManyArguments( + RuntimeConstructorInfo ci, + int argCount, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) + { + Debug.Assert(parameters != null); + + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(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); - // copy out. This should be made only if ByRef are present. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) - parameters![index] = arguments[index]; + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + + object? retValue; + try + { + RegisterForGCReporting(®); + ci.CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ci.ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = ci.Invoker.InvokeUnsafe(obj: null, pByRefStorage, copyOfParameters, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); + } + + // 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]) + { + parameters[i] = copyOfParameters[i]; + } + } return retValue; } 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 27ba0a0d51bc6b..87404f4723de1d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using static System.Runtime.CompilerServices.RuntimeHelpers; namespace System.Reflection { @@ -93,9 +94,14 @@ private void ThrowNoInvokeException() throw new TargetException(); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] - public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) + [DebuggerStepThrough] + [DebuggerHidden] + public override object? Invoke( + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) { // ContainsStackPointers means that the struct (either the declaring type or the return type) // contains pointers that point to the stack. This is either a ByRef or a TypedReference. These structs cannot @@ -108,26 +114,115 @@ private void ThrowNoInvokeException() ValidateInvokeTarget(obj); // Correct number of arguments supplied - int actualCount = (parameters is null) ? 0 : parameters.Length; - if (ArgumentTypes.Length != actualCount) + int argCount = (parameters is null) ? 0 : parameters.Length; + if (ArgumentTypes.Length != argCount) { throw new TargetParameterCountException(SR.Arg_ParmCnt); } - StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible - Span arguments = default; - if (actualCount != 0) + object? retValue; + + unsafe { - arguments = CheckArguments(ref stackArgs, parameters!, binder, invokeAttr, culture, ArgumentTypes); + if (argCount == 0) + { + retValue = Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); + } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); + } + else + { + Debug.Assert(parameters != null); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; + + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, 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]) + { + parameters[i] = copyOfParameters[i]; + } + } + } } - object? retValue = InvokeWorker(obj, invokeAttr, arguments); + return retValue; + } - // copy out. This should be made only if ByRef are present. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + // This is a separate method to support better performance for the faster paths. + [DebuggerStepThrough] + [DebuggerHidden] + private static unsafe object? InvokeWithManyArguments( + RuntimeMethodInfo mi, + int argCount, + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[] parameters, + CultureInfo? culture) + { + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(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); + + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + + object? retValue; + try + { + RegisterForGCReporting(®); + mi.CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + mi.ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = mi.Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); + } + finally { - parameters![index] = arguments[index]; + UnregisterForGCReporting(®); + } + + // 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]) + { + parameters[i] = copyOfParameters[i]; + } } return retValue; diff --git a/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs b/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs index a050476cee765c..27d0327c53689d 100644 --- a/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs +++ b/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs @@ -8,6 +8,7 @@ namespace System.Reflection.Tests public class ConstructorInfoInvokeArrayTests { [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public void Invoke_SZArrayConstructor() { Type type = Type.GetType("System.Object[]"); @@ -18,7 +19,10 @@ public void Invoke_SZArrayConstructor() int[] blength = new int[] { -100, -9, -1 }; for (int j = 0; j < blength.Length; j++) { - Assert.Throws(() => constructor.Invoke(new object[] { blength[j] })); + Exception ex = Assert.Throws(() => constructor.Invoke( + new object[] { blength[j] })); + + Assert.IsType(ex.InnerException); } int[] glength = new int[] { 0, 1, 2, 3, 5, 10, 99, 65535 }; @@ -52,7 +56,10 @@ public void Invoke_1DArrayConstructor() int[] invalidLengths = new int[] { -100, -9, -1 }; for (int j = 0; j < invalidLengths.Length; j++) { - Assert.Throws(() => constructors[i].Invoke(new object[] { invalidLengths[j] })); + Exception ex = Assert.Throws(() => constructors[i].Invoke( + new object[] { invalidLengths[j] })); + + Assert.IsType(ex.InnerException); } int[] validLengths = new int[] { 0, 1, 2, 3, 5, 10, 99 }; @@ -75,7 +82,10 @@ public void Invoke_1DArrayConstructor() int[] invalidLengths = new int[] { -100, -9, -1 }; for (int j = 0; j < invalidLengths.Length; j++) { - Assert.Throws(() => constructors[i].Invoke(new object[] { invalidLowerBounds[j], invalidLengths[j] })); + Exception ex = Assert.Throws(() => constructors[i].Invoke( + new object[] { invalidLowerBounds[j], invalidLengths[j] })); + + Assert.IsType(ex.InnerException); } int[] validLowerBounds = new int[] { 0, 1, -1, 2, -3, 5, -10, 99, 100 }; @@ -99,6 +109,7 @@ public void Invoke_1DArrayConstructor() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public void Invoke_2DArrayConstructor() { Type type = Type.GetType("System.Int32[,]", false); @@ -117,7 +128,10 @@ public void Invoke_2DArrayConstructor() for (int j = 0; j < invalidLengths1.Length; j++) { - Assert.Throws(() => constructors[i].Invoke(new object[] { invalidLengths1[j], invalidLengths2[j] })); + Exception ex = Assert.Throws(() => constructors[i].Invoke( + new object[] { invalidLengths1[j], invalidLengths2[j] })); + + Assert.IsType(ex.InnerException); } int[] validLengths1 = new int[] { 0, 0, 1, 1, 2, 1, 2, 10, 17, 99 }; @@ -150,7 +164,10 @@ public void Invoke_2DArrayConstructor() for (int j = 0; j < invalidLengths3.Length; j++) { - Assert.Throws(() => constructors[i].Invoke(new object[] { invalidLowerBounds1[j], invalidLengths3[j], invalidLowerBounds2[j], invalidLengths4[j] })); + Exception ex = Assert.Throws(() => constructors[i].Invoke( + new object[] { invalidLowerBounds1[j], invalidLengths3[j], invalidLowerBounds2[j], invalidLengths4[j] })); + + Assert.IsType(ex.InnerException); } int baseNum = 3; @@ -230,6 +247,7 @@ public void Invoke_LargeDimensionalArrayConstructor() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public void Invoke_JaggedArrayConstructor() { Type type = Type.GetType("System.String[][]"); @@ -245,7 +263,10 @@ public void Invoke_JaggedArrayConstructor() int[] invalidLengths = new int[] { -11, -10, -99 }; for (int j = 0; j < invalidLengths.Length; j++) { - Assert.Throws(() => constructors[i].Invoke(new object[] { invalidLengths[j] })); + Exception ex = Assert.Throws(() => constructors[i].Invoke( + new object[] { invalidLengths[j] })); + + Assert.IsType(ex.InnerException); } int[] validLengths = new int[] { 0, 1, 2, 10, 17, 99 }; @@ -264,7 +285,10 @@ public void Invoke_JaggedArrayConstructor() int[] invalidLengths2 = new int[] { -33, 0, -33, -1 }; for (int j = 0; j < invalidLengths1.Length; j++) { - Assert.Throws(() => constructors[i].Invoke(new object[] { invalidLengths1[j], invalidLengths2[j] })); + Exception ex = Assert.Throws(() => constructors[i].Invoke( + new object[] { invalidLengths1[j], invalidLengths2[j] })); + + Assert.IsType(ex.InnerException); } int[] validLengths1 = new int[] { 0, 0, 0, 1, 1, 2, 1, 2, 10, 17, 500 }; diff --git a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs index ea861aa1dcdeb8..262fdc2e4de1b1 100644 --- a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs @@ -107,13 +107,14 @@ public void Invoke_OneDimensionalArray() // Try to invoke Array ctors with different lengths foreach (int length in arraylength) { - // Create big Array with elements + // Create big Array with elements object[] arr = (object[])constructors[0].Invoke(new object[] { length }); Assert.Equal(arr.Length, length); } } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public void Invoke_OneDimensionalArray_NegativeLengths_ThrowsOverflowException() { ConstructorInfo[] constructors = GetConstructors(typeof(object[])); @@ -121,8 +122,9 @@ public void Invoke_OneDimensionalArray_NegativeLengths_ThrowsOverflowException() // Try to invoke Array ctors with different lengths foreach (int length in arraylength) { - // Create big Array with elements - Assert.Throws(() => (object[])constructors[0].Invoke(new object[] { length })); + // Create big Array with elements + Exception ex = Assert.Throws(() => constructors[0].Invoke(new object[] { length })); + Assert.IsType(ex.InnerException); } } diff --git a/src/libraries/System.Reflection/tests/FieldInfoTests.cs b/src/libraries/System.Reflection/tests/FieldInfoTests.cs index c06becb11e9bb7..5f107d9b44e585 100644 --- a/src/libraries/System.Reflection/tests/FieldInfoTests.cs +++ b/src/libraries/System.Reflection/tests/FieldInfoTests.cs @@ -435,6 +435,18 @@ public void SecurityAttributes() Assert.False(info.IsSecurityTransparent); } + [Fact] + public static void SetNullOnValueTypeDefaultToZero() + { + FieldInfoTests obj = new(); + FieldInfo info = GetField(typeof(FieldInfoTests), nameof(intField)); + + info.SetValue(obj, 5); + Assert.Equal(5, obj.intField); + info.SetValue(obj, null); + Assert.Equal(0, obj.intField); + } + private static FieldInfo GetField(Type type, string name) { return type.GetTypeInfo().DeclaredFields.FirstOrDefault(fieldInfo => fieldInfo.Name.Equals(name)); diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index a538fc9f3a9c9f..890befefd88e48 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -218,7 +219,7 @@ public void Equality1(string str1, string str2, bool expected) public static IEnumerable TestEqualityMethodData2() { //Verify two different MethodInfo objects with same name from two different classes are not equal - yield return new object[] { typeof(Sample), typeof(SampleG<>), "Method1", "Method1", false}; + yield return new object[] { typeof(Sample), typeof(SampleG<>), "Method1", "Method1", false }; //Verify two different MethodInfo objects with same name from two different classes are not equal yield return new object[] { typeof(Sample), typeof(SampleG), "Method2", "Method2", false }; } @@ -619,6 +620,95 @@ public void ToStringTest_ByMethodInfo(MethodInfo methodInfo, string expected) Assert.Equal(expected, methodInfo.ToString()); } + [ActiveIssue("https://github.com/dotnet/runtime/issues/67269", TestRuntimes.Mono)] + [Fact] + public void InvokeNullableRefs() + { + object?[] args; + + int? iNull = null; + args = new object[] { iNull }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.Null)).Invoke(null, args)); + Assert.Null(args[0]); + Assert.False(((int?)args[0]).HasValue); + + args = new object[] { iNull }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.NullBoxed)).Invoke(null, args)); + Assert.Null(args[0]); + + args = new object[] { iNull, 10 }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.NullToValue)).Invoke(null, args)); + Assert.IsType(args[0]); + Assert.Equal(10, (int)args[0]); + + iNull = 42; + args = new object[] { iNull, 42 }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.ValueToNull)).Invoke(null, args)); + Assert.Null(args[0]); + + iNull = null; + args = new object[] { iNull, 10 }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.NullToValueBoxed)).Invoke(null, args)); + Assert.IsType(args[0]); + Assert.Equal(10, (int)args[0]); + + static MethodInfo GetMethod(string name) => typeof(NullableRefMethods).GetMethod( + name, BindingFlags.Public | BindingFlags.Static)!; + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/67269", TestRuntimes.Mono)] + [Fact] + public void InvokeBoxedNullableRefs() + { + object?[] args; + + object? iNull = null; + args = new object[] { iNull }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.Null)).Invoke(null, args)); + Assert.Null(args[0]); + + args = new object[] { iNull }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.NullBoxed)).Invoke(null, args)); + Assert.Null(args[0]); + + args = new object[] { iNull, 10 }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.NullToValue)).Invoke(null, args)); + Assert.IsType(args[0]); + Assert.Equal(10, (int)args[0]); + + iNull = 42; + args = new object[] { iNull, 42 }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.ValueToNull)).Invoke(null, args)); + Assert.Null(args[0]); + + iNull = null; + args = new object[] { iNull, 10 }; + Assert.True((bool)GetMethod(nameof(NullableRefMethods.NullToValueBoxed)).Invoke(null, args)); + Assert.IsType(args[0]); + Assert.Equal(10, (int)args[0]); + + static MethodInfo GetMethod(string name) => typeof(NullableRefMethods).GetMethod( + name, BindingFlags.Public | BindingFlags.Static)!; + } + + [Fact] + public void InvokeEnum() + { + // Enums only need to match by primitive type. + Assert.True((bool)GetMethod(nameof(EnumMethods.PassColorsInt)). + Invoke(null, new object[] { OtherColorsInt.Red })); + + // Widening allowed + Assert.True((bool)GetMethod(nameof(EnumMethods.PassColorsInt)). + Invoke(null, new object[] { ColorsShort.Red })); + + // Narrowing not allowed + Assert.Throws(() => GetMethod(nameof(EnumMethods.PassColorsShort)). + Invoke(null, new object[] { OtherColorsInt.Red })); + + static MethodInfo GetMethod(string name) => typeof(EnumMethods).GetMethod( + name, BindingFlags.Public | BindingFlags.Static)!; + } //Methods for Reflection Metadata private void DummyMethod1(string str, int iValue, long lValue) @@ -829,8 +919,8 @@ public static string StaticMethod(int p1 = 1, string p2 = "test", decimal p3 = 3 return FormattableString.Invariant($"{p1}, {p2}, {p3}"); } - public object OptionalObjectParameter([Optional]object parameter) => parameter; - public string OptionalStringParameter([Optional]string parameter) => parameter; + public object OptionalObjectParameter([Optional] object parameter) => parameter; + public string OptionalStringParameter([Optional] string parameter) => parameter; } public delegate int Delegate_TC_Int(MI_BaseClass tc); @@ -868,5 +958,79 @@ public T Method2(S t1, T t2, string t3) return t2; } } + + public static class NullableRefMethods + { + public static bool Null(ref int? i) + { + Assert.Null(i); + return true; + } + + public static bool NullBoxed(ref object? i) + { + Assert.Null(i); + return true; + } + + public static bool NullToValue(ref int? i, int value) + { + Assert.Null(i); + i = value; + return true; + } + + public static bool NullToValueBoxed(ref object? i, int value) + { + Assert.Null(i); + i = value; + return true; + } + + public static bool ValueToNull(ref int? i, int expected) + { + Assert.Equal(expected, i); + i = null; + return true; + } + + public static bool ValueToNullBoxed(ref int? i, int expected) + { + Assert.Equal(expected, i); + i = null; + return true; + } + } + + public enum ColorsInt : int + { + Red = 1 + } + + public enum ColorsShort : short + { + Red = 1 + } + + public enum OtherColorsInt : int + { + Red = 1 + } + + public static class EnumMethods + { + public static bool PassColorsInt(ColorsInt color) + { + Assert.Equal(ColorsInt.Red, color); + return true; + } + + public static bool PassColorsShort(ColorsShort color) + { + Assert.Equal(ColorsShort.Red, color); + return true; + } + } #pragma warning restore 0414 } + diff --git a/src/libraries/System.Runtime/tests/System/DelegateTests.cs b/src/libraries/System.Runtime/tests/System/DelegateTests.cs index 9a3658457c10d2..a857a94a2962ff 100644 --- a/src/libraries/System.Runtime/tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System/DelegateTests.cs @@ -163,7 +163,7 @@ public static void DynamicInvoke_TypeDoesntExactlyMatchRefValueType_ThrowsArgume } [Theory] - [InlineData(7, (short)7)] // uint -> int + [InlineData(7, (short)7)] // short -> int [InlineData(7, IntEnum.Seven)] // Enum (int) -> int [InlineData(7, ShortEnum.Seven)] // Enum (short) -> int public static void DynamicInvoke_ValuePreservingPrimitiveWidening_Succeeds(object o1, object o2) diff --git a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs index 885e17992c1768..a86592b14ff2f9 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs @@ -38,12 +38,14 @@ public static void TestRefReturnNullableNoValue() [Theory] [MemberData(nameof(RefReturnInvokeTestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public static void TestNullRefReturnInvoke(T value) { TestClass tc = new TestClass(value); PropertyInfo p = typeof(TestClass).GetProperty(nameof(TestClass.NullRefReturningProp)); Assert.NotNull(p); - Assert.Throws(() => p.GetValue(tc)); + Exception ex = Assert.Throws(() => p.GetValue(tc)); + Assert.IsType(ex.InnerException); } [Fact] @@ -59,13 +61,15 @@ public static unsafe void TestRefReturnOfPointer() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public static unsafe void TestNullRefReturnOfPointer() { TestClassIntPointer tc = new TestClassIntPointer(null); PropertyInfo p = typeof(TestClassIntPointer).GetProperty(nameof(TestClassIntPointer.NullRefReturningProp)); Assert.NotNull(p); - Assert.Throws(() => p.GetValue(tc)); + Exception ex = Assert.Throws(() => p.GetValue(tc)); + Assert.IsType(ex.InnerException); } [Fact] diff --git a/src/libraries/System.Runtime/tests/System/Reflection/InvokeWithRefLikeArgs.cs b/src/libraries/System.Runtime/tests/System/Reflection/InvokeWithRefLikeArgs.cs index 94556ac76b0017..f078673841201f 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/InvokeWithRefLikeArgs.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/InvokeWithRefLikeArgs.cs @@ -18,26 +18,22 @@ public static void MethodReturnsRefToRefStruct_ThrowsNSE() [Fact] [ActiveIssue("https://github.com/dotnet/runtimelab/issues/155", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] - public static void MethodTakesRefStructAsArg_DoesNotCopyValueBack() + public static void MethodTakesRefStructAsArg_ThrowsNSE() { MethodInfo mi = GetMethod(nameof(TestClass.TakesRefStructAsArg)); object[] args = new object[] { null }; - mi.Invoke(null, args); - - Assert.Null(args[0]); // no value should have been copied back + Assert.Throws(() => mi.Invoke(null, args)); } [Fact] [ActiveIssue("https://github.com/dotnet/runtimelab/issues/155", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] - public static void MethodTakesRefStructAsArgWithDefaultValue_DoesNotCopyValueBack() + public static void MethodTakesRefStructAsArgWithDefaultValue_ThrowsNSE() { MethodInfo mi = GetMethod(nameof(TestClass.TakesRefStructAsArgWithDefaultValue)); object[] args = new object[] { Type.Missing }; - mi.Invoke(null, args); - - Assert.Null(args[0]); // no value should have been copied back + Assert.Throws(() => mi.Invoke(null, args)); } // Moq heavily utilizes RefEmit, which does not work on most aot workloads @@ -81,7 +77,7 @@ public static void PropertyTypedAsRefToRefStruct_AsPropInfo_ThrowsNSE() [Fact] [ActiveIssue("https://github.com/dotnet/runtimelab/issues/155", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] - public static void PropertyIndexerWithRefStructArg_DoesNotCopyValueBack() + public static void PropertyIndexerWithRefStructArg_ThrowsNSE() { PropertyInfo pi = typeof(TestClassWithIndexerWithRefStructArg).GetProperty("Item"); Assert.NotNull(pi); @@ -89,12 +85,8 @@ public static void PropertyIndexerWithRefStructArg_DoesNotCopyValueBack() object obj = new TestClassWithIndexerWithRefStructArg(); object[] args = new object[] { null }; - object retVal = pi.GetValue(obj, args); - Assert.Equal(42, retVal); - Assert.Null(args[0]); // no value should have been copied back - - pi.SetValue(obj, 42, args); - Assert.Null(args[0]); // no value should have been copied back + Assert.Throws(() => pi.GetValue(obj, args)); + Assert.Throws(() => pi.SetValue(obj, 42, args)); } private sealed class TestClass 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 a156178708443b..36b9b0ac99eca9 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -32,11 +32,14 @@ namespace System.Reflection { + // Note that in CoreCLR, RtFieldInfo derives from RuntimeFieldInfo. internal abstract class RtFieldInfo : FieldInfo { internal abstract object UnsafeGetValue(object obj); internal abstract void UnsafeSetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture); internal abstract void CheckConsistency(object target); + internal abstract object? GetValueNonEmit(object? obj); + internal abstract void SetValueNonEmit(object? obj, object? value); } [StructLayout(LayoutKind.Sequential)] @@ -48,8 +51,19 @@ internal sealed class RuntimeFieldInfo : RtFieldInfo private string? name; private Type? type; private FieldAttributes attrs; + private FieldAccessor? invoker; #pragma warning restore 649 + private FieldAccessor Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + invoker ??= new FieldAccessor(this); + return invoker; + } + } + public override Module Module { get @@ -92,16 +106,16 @@ internal override void CheckConsistency(object target) } } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] internal override void UnsafeSetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, CultureInfo? culture) { bool domainInitialized = false; RuntimeFieldHandle.SetValue(this, obj, value, null, Attributes, null, ref domainInitialized); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override void SetValueDirect(TypedReference obj, object value) { if (obj.IsNull) @@ -114,8 +128,15 @@ public override void SetValueDirect(TypedReference obj, object value) } } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] + internal override void SetValueNonEmit(object? obj, object? value) + { + SetValueInternal(this, obj, value); + } + + [DebuggerStepThrough] + [DebuggerHidden] public override object GetValueDirect(TypedReference obj) { if (obj.IsNull) @@ -128,6 +149,13 @@ public override object GetValueDirect(TypedReference obj) } } + [DebuggerStepThrough] + [DebuggerHidden] + internal override object? GetValueNonEmit(object? obj) + { + return GetValueInternal(obj); + } + public override FieldAttributes Attributes { get @@ -191,6 +219,7 @@ public override object[] GetCustomAttributes(bool inherit) { return CustomAttribute.GetCustomAttributes(this, inherit); } + public override object[] GetCustomAttributes(Type attributeType, bool inherit) { return CustomAttribute.GetCustomAttributes(this, attributeType, inherit); @@ -217,7 +246,8 @@ public override object[] GetCustomAttributes(Type attributeType, bool inherit) if (!IsLiteral) CheckGeneric(); - return GetValueInternal(obj); + + return Invoker.GetValue(obj); } public override string ToString() @@ -248,9 +278,11 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, if (val != null) { RuntimeType fieldType = (RuntimeType)FieldType; - val = fieldType.CheckValue(val, binder, culture, invokeAttr); + bool _ = false; + fieldType.CheckValue(ref val, ref _, binder, culture, invokeAttr); } - SetValueInternal(this, obj, val); + + Invoker.SetValue(obj, val); } internal RuntimeFieldInfo Clone(string newName) 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 8abf748f94b4c8..c8c67cdd629441 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 @@ -151,6 +151,7 @@ internal sealed partial class RuntimeMethodInfo : MethodInfo private string? toString; private RuntimeType[]? parameterTypes; private InvocationFlags invocationFlags; + private MethodInvoker? invoker; internal InvocationFlags InvocationFlags { @@ -166,6 +167,16 @@ internal InvocationFlags InvocationFlags } } + private MethodInvoker Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + invoker ??= new MethodInvoker(this); + return invoker; + } + } + public override Module Module { get @@ -343,7 +354,7 @@ internal override int GetParametersCount() return MonoMethodInfo.GetParametersInfo(mhandle, this).Length; } - private RuntimeType[] ArgumentTypes + internal RuntimeType[] ArgumentTypes { get { @@ -374,7 +385,7 @@ private RuntimeType[] ArgumentTypes internal extern object? InternalInvoke(object? obj, in Span parameters, out Exception? exc); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object? InvokeWorker(object? obj, BindingFlags invokeAttr, Span parameters) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { Exception? exc; object? o; @@ -383,7 +394,7 @@ private RuntimeType[] ArgumentTypes { try { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } catch (Mono.NullByRefReturnException) { @@ -402,7 +413,7 @@ private RuntimeType[] ArgumentTypes { try { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } catch (Mono.NullByRefReturnException) { @@ -412,38 +423,8 @@ private RuntimeType[] ArgumentTypes if (exc != null) throw exc; - return o; - } - - internal static void ConvertValues(Binder binder, object?[]? args, ParameterInfo[] pinfo, CultureInfo? culture, BindingFlags invokeAttr) - { - if (args == null) - { - if (pinfo.Length == 0) - return; - - throw new TargetParameterCountException(); - } - - if (pinfo.Length != args.Length) - throw new TargetParameterCountException(); - - for (int i = 0; i < args.Length; ++i) - { - object? arg = args[i]; - ParameterInfo pi = pinfo[i]; - if (arg == Type.Missing) - { - if (pi.DefaultValue == DBNull.Value) - throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); - - args[i] = pi.DefaultValue; - continue; - } - var rt = (RuntimeType)pi.ParameterType; - args[i] = rt.CheckValue(arg, binder, culture, invokeAttr); - } + return o; } public override RuntimeMethodHandle MethodHandle @@ -784,6 +765,7 @@ internal sealed partial class RuntimeConstructorInfo : ConstructorInfo private string? toString; private RuntimeType[]? parameterTypes; private InvocationFlags invocationFlags; + private ConstructorInvoker? invoker; internal InvocationFlags InvocationFlags { @@ -799,6 +781,16 @@ internal InvocationFlags InvocationFlags } } + internal ConstructorInvoker Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + invoker ??= new ConstructorInvoker(this); + return invoker; + } + } + public override Module Module { get @@ -833,7 +825,7 @@ internal override int GetParametersCount() return pi == null ? 0 : pi.Length; } - private RuntimeType[] ArgumentTypes + internal RuntimeType[] ArgumentTypes { get { @@ -860,20 +852,6 @@ private static void InvokeClassConstructor() // See https://github.com/dotnet/runtime/issues/40351 } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal object? InvokeWorker(object? obj, BindingFlags invokeAttr, Span arguments) - { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return InternalInvoke(obj, arguments, wrapExceptions); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal object InvokeCtorWorker(BindingFlags invokeAttr, Span arguments) - { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return InternalInvoke(null, arguments, wrapExceptions)!; - } - /* * InternalInvoke() receives the parameters correctly converted by the binder * to match the types of the method signature. @@ -881,16 +859,18 @@ internal object InvokeCtorWorker(BindingFlags invokeAttr, Span argument [MethodImplAttribute(MethodImplOptions.InternalCall)] internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); - private object? InternalInvoke(object? obj, Span parameters, bool wrapExceptions) + [DebuggerHidden] + [DebuggerStepThrough] + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { Exception exc; object? o; - if (wrapExceptions) + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } catch (MethodAccessException) { @@ -907,7 +887,7 @@ internal object InvokeCtorWorker(BindingFlags invokeAttr, Span argument } else { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } if (exc != null) diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index 0ee6d34682e909..b069ce059309c3 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -143,6 +143,18 @@ internal static bool ObjectHasReferences(object obj) return RuntimeTypeHandle.HasReferences((obj.GetType() as RuntimeType)!); } + // A conservative GC already scans the stack looking for potential object-refs or by-refs. + // Mono uses a conservative GC so there is no need for this API to be full implemented. + internal unsafe ref struct GCFrameRegistration + { + public GCFrameRegistration(void* allocation, uint elemCount, bool areByRefs = true) + { + } + } + + internal static unsafe void RegisterForGCReporting(GCFrameRegistration* pRegistration) { /* nop */ } + internal static unsafe void UnregisterForGCReporting(GCFrameRegistration* pRegistration) { /* nop */ } + public static object GetUninitializedObject( // This API doesn't call any constructors, but the type needs to be seen as constructed. // A type is seen as constructed if a constructor is kept. 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 0ea6071efc529a..16d19f3bed4c59 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1641,7 +1641,8 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) if (ReferenceEquals(elementType, typeof(TypedReference)) || ReferenceEquals(elementType, typeof(RuntimeArgumentHandle))) throw new NotSupportedException("NotSupported_ContainsStackPtr"); - if (IsValueType) { + if (IsValueType) + { var this_type = this; return CreateInstanceInternal(new QCallTypeHandle(ref this_type)); } @@ -1655,74 +1656,140 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) throw new MissingMethodException("Cannot create an abstract class '{0}'.", FullName); } - return ctor.InvokeWorker(null, wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions, Span.Empty); + unsafe + { + return ctor.Invoker.InvokeUnsafe( + obj: null, + args: default, + argsForTemporaryMonoSupport: default, + wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); + } } - internal object? CheckValue(object? value, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) + // 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, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr) { - bool failed = false; - object? res = TryConvertToType(value, ref failed); - if (!failed) - return res; + // Already fast-pathed by the caller. + Debug.Assert(!ReferenceEquals(value?.GetType(), this)); + + copyBack = true; + + CheckValueStatus status = TryConvertToType(ref value); + if (status == CheckValueStatus.Success) + { + return true; + } + + if (status == CheckValueStatus.NotSupported_ByRefLike) + { + throw new NotSupportedException(SR.Format(SR.NotSupported_ByRefLike, value?.GetType(), this)); + } if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); + { + throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this)); + } if (binder != null && binder != DefaultBinder) - return binder.ChangeType(value!, this, culture); + { + value = binder.ChangeType(value!, this, culture); + return true; + } - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); + throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this)); } - private object? TryConvertToType(object? value, ref bool failed) + private enum CheckValueStatus + { + Success = 0, + ArgumentException, + NotSupported_ByRefLike + } + + private CheckValueStatus TryConvertToType(ref object? value) { if (IsInstanceOfType(value)) - { - return value; - } + return CheckValueStatus.Success; if (IsByRef) { - Type? elementType = GetElementType(); + Type elementType = GetElementType(); + if (elementType.IsByRefLike) + { + return CheckValueStatus.NotSupported_ByRefLike; + } + if (value == null || elementType.IsInstanceOfType(value)) { - return value; + return CheckValueStatus.Success; } } if (value == null) - return value; + { + if (IsByRefLike) + { + return CheckValueStatus.NotSupported_ByRefLike; + } + + return CheckValueStatus.Success; + } if (IsEnum) { Type? type = Enum.GetUnderlyingType(this); if (type == value.GetType()) - return value; + return CheckValueStatus.Success; + object? res = IsConvertibleToPrimitiveType(value, type); if (res != null) - return res; + { + value = res; + return CheckValueStatus.Success; + } } else if (IsPrimitive) { object? res = IsConvertibleToPrimitiveType(value, this); if (res != null) - return res; + { + value = res; + return CheckValueStatus.Success; + } } else if (IsPointer) { Type? vtype = value.GetType(); if (vtype == typeof(IntPtr) || vtype == typeof(UIntPtr)) - return value; + return CheckValueStatus.Success; if (value is Pointer pointer) { Type pointerType = pointer.GetPointerType(); if (pointerType == this) - return pointer.GetPointerValue(); + { + value = pointer.GetPointerValue(); + return CheckValueStatus.Success; + } } } - failed = true; - return null; + return CheckValueStatus.ArgumentException; } // Binder uses some incompatible conversion rules. For example @@ -1983,7 +2050,10 @@ internal static object CreateInstanceForAnotherGenericParameter( if (ctor is null || !ctor.IsPublic) throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, gt!)); - return ctor.InvokeCtorWorker(BindingFlags.Default, Span.Empty)!; + unsafe + { + return ctor.Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, BindingFlags.Default)!; + } } [MethodImplAttribute(MethodImplOptions.InternalCall)] diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs index 0a2ddac541b366..8f76edca8cbb30 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeTypeHandle.cs @@ -182,6 +182,8 @@ internal static bool IsSzArray(RuntimeType type) return corElemType == CorElementType.ELEMENT_TYPE_SZARRAY; } + internal static bool IsValueType(RuntimeType type) => type.IsValueType; + internal static bool HasElementType(RuntimeType type) { CorElementType corElemType = GetCorElementType(type); diff --git a/src/tests/reflection/DefaultInterfaceMethods/Emit.cs b/src/tests/reflection/DefaultInterfaceMethods/Emit.cs index 029c941fcc122f..2f54244151b55b 100644 --- a/src/tests/reflection/DefaultInterfaceMethods/Emit.cs +++ b/src/tests/reflection/DefaultInterfaceMethods/Emit.cs @@ -117,7 +117,7 @@ static int Main() { ifooType.GetMethod("DefaultMethod").Invoke(o, null); } - catch (EntryPointNotFoundException) + catch (TargetInvocationException ie) when (ie.InnerException is EntryPointNotFoundException) { result |= 0x10; } @@ -126,7 +126,7 @@ static int Main() { ifooType.GetMethod("InterfaceMethod").Invoke(o, null); } - catch (EntryPointNotFoundException) + catch (TargetInvocationException ie) when (ie.InnerException is EntryPointNotFoundException) { result |= 0x20; } diff --git a/src/tests/reflection/DefaultInterfaceMethods/InvokeConsumer.cs b/src/tests/reflection/DefaultInterfaceMethods/InvokeConsumer.cs index c74906775cb506..ad3c78ca7e0fbd 100644 --- a/src/tests/reflection/DefaultInterfaceMethods/InvokeConsumer.cs +++ b/src/tests/reflection/DefaultInterfaceMethods/InvokeConsumer.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection; class Program { @@ -42,7 +43,7 @@ static int Main() typeof(IFoo).GetMethod("DefaultMethod").Invoke(new Reabstractor(), new object[] { 1 }); return 501; } - catch (EntryPointNotFoundException) + catch (TargetInvocationException ie) when (ie.InnerException is EntryPointNotFoundException) { }