From 7ae039b6151619e5654bb8ec38feb8cab83d588b Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 2 Feb 2022 17:09:03 -0600 Subject: [PATCH 01/28] Refactoring and perf of reflection Invoke --- .../System/Reflection/Emit/DynamicMethod.cs | 52 +++- .../src/System/Reflection/RtFieldInfo.cs | 91 ++++--- .../RuntimeConstructorInfo.CoreCLR.cs | 49 +++- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 49 +++- .../src/System/RuntimeHandles.cs | 8 +- .../src/System/RuntimeType.CoreCLR.cs | 107 +++++--- src/coreclr/vm/object.h | 6 +- src/coreclr/vm/reflectioninvocation.cpp | 243 ++---------------- src/coreclr/vm/runtimehandles.h | 2 +- .../System.Private.CoreLib.Shared.projitems | 5 +- .../System/Reflection/ConstructorInvoker.cs | 35 +++ .../src/System/Reflection/FieldAccessor.cs | 31 +++ .../src/System/Reflection/MethodBase.cs | 24 +- .../src/System/Reflection/MethodInvoker.cs | 34 +++ .../Reflection/RuntimeConstructorInfo.cs | 47 ++-- .../System/Reflection/RuntimeMethodInfo.cs | 28 +- .../tests/System.Runtime.Tests.csproj | 3 +- .../src/System/Reflection/RuntimeFieldInfo.cs | 3 +- .../Reflection/RuntimeMethodInfo.Mono.cs | 78 ++---- .../src/System/RuntimeType.Mono.cs | 66 +++-- 20 files changed, 526 insertions(+), 435 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs 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..5edfc60c6ed164 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 @@ -435,7 +435,6 @@ internal RuntimeMethodHandle GetMethodDescriptor() 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; @@ -443,21 +442,58 @@ internal RuntimeMethodHandle GetMethodDescriptor() 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; - StackAllocedArguments stackArgs = default; Span arguments = default; + bool copyBack = false; if (actualCount != 0) { - arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig.Arguments); + // Adopt the MethodInvoker pattern here instead if the IL Emit perf gain is necessary. + bool HasRefs() + { + for (int i = 0; i < sig.Arguments.Length; i++) + { + if (sig.Arguments[i].IsByRef) + { + return true; + } + } + + return false; + } + + copyBack = HasRefs(); + arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, sig.Arguments, binder, culture, invokeAttr); } - object? retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, false, wrapExceptions); + object? retValue; + bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; + if (wrapExceptions) + { + bool rethrow = false; - // copy out. This should be made only if ByRef are present. + try + { + retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, constructor: false, out rethrow); + } + catch (Exception ex) when (rethrow == false) + { + throw new TargetInvocationException(ex); + } + } + else + { + retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, constructor: false, out _); + } + + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. // 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]; + if (copyBack) + { + for (int index = 0; index < arguments.Length; index++) + { + parameters![index] = arguments[index]; + } + } GC.KeepAlive(this); return retValue; 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..e989a621c11871 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_reflectionInvoker; 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_reflectionInvoker ??= new FieldAccessor(InvokeGetterNonEmit, InvokeSetterNonEmit); + return m_reflectionInvoker; + } } [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 @@ -73,6 +83,45 @@ internal RtFieldInfo( #region Private Members RuntimeFieldHandleInternal IRuntimeFieldInfo.Value => new RuntimeFieldHandleInternal(m_fieldHandle); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal object? InvokeGetterNonEmit(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; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void InvokeSetterNonEmit(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, m_fieldAttributes, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + } + } + #endregion #region Internal Members @@ -137,20 +186,7 @@ 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.InvokeGetter(obj); } public override object GetRawConstantValue() { throw new InvalidOperationException(); } @@ -184,19 +220,10 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); RuntimeType fieldType = (RuntimeType)FieldType; - value = fieldType.CheckValue(value, binder, culture, invokeAttr); + bool _ = false; + fieldType.CheckValue(ref value, ref _, binder, culture, invokeAttr); - bool domainInitialized = false; - if (declaringType == null) - { - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - } + Invoker.InvokeSetter(obj, value); } [DebuggerStepThroughAttribute] 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..e190f8f0937c45 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_reflectionInvoker; 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_reflectionInvoker ??= new ConstructorInvoker(InvokeNonEmit, ArgumentTypes); + return m_reflectionInvoker; + } + } #endregion #region Constructor @@ -205,18 +215,31 @@ private void InvokeClassConstructor() } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object? InvokeWorker(object? obj, BindingFlags invokeAttr, Span arguments) + [DebuggerStepThrough] + [DebuggerHidden] + private object InvokeNonEmit(object? obj, Span arguments, BindingFlags invokeAttr) { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, false, wrapExceptions); - } + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; - [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)!; + try + { + return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: obj is null, out rethrow)!; + } + catch (OutOfMemoryException) + { + throw; // Re-throw for backward compatibility. + } + catch (Exception ex) when (rethrow == false) + { + throw new TargetInvocationException(ex); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: obj is null, out _)!; + } } [RequiresUnreferencedCode("Trimming may change method bodies. For example it can change some instructions, remove branches or local variables.")] 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 2b26b90cb7770f..7633633523ee47 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_reflectionInvoker; 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_reflectionInvoker ??= new MethodInvoker(InvokeNonEmit, ArgumentTypes); + return m_reflectionInvoker; + } + } #endregion #region Constructor @@ -309,11 +319,27 @@ public override MethodImplAttributes GetMethodImplementationFlags() #endregion #region Invocation Logic(On MemberBase) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object? InvokeWorker(object? obj, BindingFlags invokeAttr, Span arguments) + [DebuggerStepThrough] + [DebuggerHidden] + private object? InvokeNonEmit(object? obj, Span arguments, BindingFlags invokeAttr) { - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, false, wrapExceptions); + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: false, out rethrow); + } + catch (Exception e) when (!rethrow) + { + throw new TargetInvocationException(e); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: false, out _); + } } [DebuggerStepThroughAttribute] @@ -337,11 +363,14 @@ public override MethodImplAttributes GetMethodImplementationFlags() throw new TargetParameterCountException(SR.Arg_ParmCnt); } + Span parameters = new Span(ref parameter, 1); StackAllocedArguments stackArgs = default; - Span arguments = CheckArguments(ref stackArgs, new ReadOnlySpan(ref parameter, 1), binder, invokeAttr, culture, sig.Arguments); + bool _ = false; + Span arguments = CheckArguments(ref stackArgs, parameters, ref _, ArgumentTypes, binder, culture, invokeAttr); + + object? retValue = Invoker.Invoke(obj, arguments, invokeAttr); - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - return RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, constructor: false, wrapExceptions); + return retValue; } #endregion diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 811772e39d004c..fa80ab6a05539a 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, in Span arguments, Signature sig, bool constructor, out bool rethrow); [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..c0e925e79cf8a9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3473,9 +3473,18 @@ public override Type MakeArrayType(int rank) [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) + /// + /// Verify and optionally change it for special cases. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void CheckValue( + ref object? value, + ref bool copyBack, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr) { - // this method is used by invocation in reflection to check whether a value can be assigned to type. + // This method is used by invocation in reflection to check whether a value can be assigned to type. if (IsInstanceOfType(value)) { // Since this cannot be a generic parameter, we use RuntimeTypeHandle.IsValueType here @@ -3486,93 +3495,123 @@ public override Type MakeArrayType(int rank) if (!ReferenceEquals(type, this) && RuntimeTypeHandle.IsValueType(this)) { - // must be an equivalent type, re-box to the target type - return AllocateValueType(this, value, true); - } - else - { - return value; + // Must be an equivalent type, re-box to the target type + value = AllocateValueType(this, value, fForceTypeChange: true); + copyBack = true; } + + return; } - // if this is a ByRef get the element type and check if it's compatible + TryChangeType(ref value, ref copyBack, binder, culture, invokeAttr); + } + + private void TryChangeType( + ref object? value, + ref bool copyBack, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr) + { + // If this is a ByRef get the element type and check if it's compatible bool isByRef = IsByRef; if (isByRef) { RuntimeType elementType = RuntimeTypeHandle.GetElementType(this); if (elementType.IsInstanceOfType(value) || value == null) { - // 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); + // Need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type + value = AllocateValueType(elementType, value, fForceTypeChange: false); + copyBack = true; + return; } } else if (value == null) - return value; + { + return; + } else if (this == s_typedRef) - // everything works for a typedref - return value; + { + // Everything works for a typedref + return; + } - // check the strange ones courtesy of reflection: + // 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) { - RuntimeType valueType; Pointer? pointer = value as Pointer; - if (pointer != null) - valueType = pointer.GetPointerType(); - else - valueType = (RuntimeType)value.GetType(); + RuntimeType valueType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); if (CanValueSpecialCast(valueType, this)) { if (pointer != null) - return pointer.GetPointerValue(); + { + value = pointer.GetPointerValue(); + copyBack = true; + return; + } else - return value; + { + return; + } } } 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) - { + // Use the binder if (binder != null && binder != Type.DefaultBinder) { + copyBack = true; + value = binder.ChangeType(value, this, culture); if (IsInstanceOfType(value)) - return value; + { + return; + } + // if this is a ByRef get the element type and check if it's compatible if (IsByRef) { RuntimeType elementType = RuntimeTypeHandle.GetElementType(this); if (elementType.IsInstanceOfType(value) || value == null) - return AllocateValueType(elementType, value, false); + { + value = AllocateValueType(elementType, value, fForceTypeChange: false); + return; + } } else if (value == null) - return value; + { + return; + } if (needsSpecialCast) { RuntimeType valueType; Pointer? pointer = value as Pointer; if (pointer != null) + { valueType = pointer.GetPointerType(); + } else + { valueType = (RuntimeType)value.GetType(); + } if (CanValueSpecialCast(valueType, this)) { if (pointer != null) - return pointer.GetPointerValue(); - else - return value; + { + value = pointer.GetPointerValue(); + } + + return; } } } diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 0f4033f45b507b..b98b520d4aded7 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..432db40afc6a48 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,33 +369,6 @@ 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) { CONTRACTL { @@ -645,117 +536,12 @@ 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) + Object *target, + Span* objs, + SignatureNative* pSigUNSAFE, + CLR_BOOL fConstructor, + CLR_BOOL* pRethrow) { FCALL_CONTRACT; @@ -769,13 +555,16 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, gc.pSig = (SIGNATURENATIVEREF)pSigUNSAFE; gc.retVal = NULL; + *pRethrow = TRUE; MethodDesc* pMeth = gc.pSig->GetMethod(); TypeHandle ownerType = gc.pSig->GetDeclaringType(); HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); if (ownerType.IsSharedByGenericInstantiations()) + { COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); + } #ifdef _DEBUG if (g_pConfig->ShouldInvokeHalt(pMeth)) @@ -1037,6 +826,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } // Call the method +<<<<<<< HEAD bool fExceptionThrown = false; if (fWrapExceptions) { @@ -1079,6 +869,11 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, { ThrowInvokeMethodException(pMeth, gc.retVal); } +======= + *pRethrow = FALSE; + CallDescrWorkerWithHandler(&callDescrData); + *pRethrow = TRUE; +>>>>>>> 7014012a69b (Refactoring and perf of reflection Invoke) // It is still illegal to do a GC here. The return type might have/contain GC pointers. if (fConstructor) @@ -1332,13 +1127,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"); diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 85b7fa3d8e7f5a..03ab547d77766d 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 FCDECL5(Object*, InvokeMethod, Object *target, Span* objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); struct StreamingContextData { Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a 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 cbcaf580b96a38..070ad232c22837 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 @@ -579,6 +579,7 @@ + @@ -603,6 +604,7 @@ + @@ -625,6 +627,7 @@ + @@ -2394,4 +2397,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..4487289d3e7413 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection +{ + internal sealed partial class ConstructorInvoker + { + public delegate object? InvokeCtorFunc(object? obj, Span args, BindingFlags invokeAttr); + + private readonly bool _hasRefs; + public InvocationFlags _invocationFlags; + private InvokeCtorFunc _invoke; + + public ConstructorInvoker(InvokeCtorFunc invokeFunc, RuntimeType[] sigTypes) + { + _invoke = invokeFunc; + + for (int i = 0; i < sigTypes.Length; i++) + { + if (sigTypes[i].IsByRef) + { + _hasRefs = true; + break; + } + } + } + + public bool HasRefs => _hasRefs; + + public object? Invoke(object? obj, Span parameters, BindingFlags invokeAttr) + { + return _invoke(obj, parameters, 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..370b3907a12b38 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection +{ + internal sealed partial class FieldAccessor + { + public delegate object? InvokeGetterFunc(object? obj); + public delegate void InvokeSetterFunc(object? obj, object? value); + + public InvocationFlags _invocationFlags; + private InvokeGetterFunc _invokeGetterFunc; + private InvokeSetterFunc _invokeSetterFunc; + + public FieldAccessor(InvokeGetterFunc getterFallback, InvokeSetterFunc setterFallback) + { + _invokeGetterFunc = getterFallback; + _invokeSetterFunc = setterFallback; + } + + public object? InvokeGetter(object? obj) + { + return _invokeGetterFunc(obj); + } + + public void InvokeSetter(object? obj, object? value) + { + _invokeSetterFunc(obj, value); + } + } +} 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..7609750c4fe4aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -140,8 +140,15 @@ 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 Span CheckArguments( + ref StackAllocedArguments stackArgs, + ReadOnlySpan parameters, + ref bool copyBack, + RuntimeType[] sigTypes, + Binder? binder, + CultureInfo? culture, + BindingFlags invokeAttr + ) { Debug.Assert(Unsafe.SizeOf() == StackAllocedArguments.MaxStackAllocArgCount * Unsafe.SizeOf(), "MaxStackAllocArgCount not properly defined."); @@ -153,7 +160,6 @@ private protected void ValidateInvokeTarget(object? target) // 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]); @@ -162,16 +168,20 @@ private protected void ValidateInvokeTarget(object? target) for (int i = 0; i < parameters.Length; i++) { object? arg = parameters[i]; - RuntimeType argRT = sigTypes[i]; - if (arg == Type.Missing) { p ??= GetParametersNoCopy(); - if (p[i].DefaultValue == System.DBNull.Value) + if (p[i].DefaultValue == DBNull.Value) + { throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); + } + + copyBack = true; arg = p[i].DefaultValue!; } - copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr); + + sigTypes[i].CheckValue(ref arg, ref copyBack, binder, culture, invokeAttr); + copyOfParameters[i] = arg; } return copyOfParameters; 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..3223d1c4fbb043 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.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. + +namespace System.Reflection +{ + internal sealed partial class MethodInvoker + { + public delegate object? InvokeFunc(object? obj, Span args, BindingFlags invokeAttr); + internal InvocationFlags _invocationFlags; + private readonly bool _hasRefs; + private InvokeFunc _invokeFunc; + + public MethodInvoker(InvokeFunc invokeFunc, RuntimeType[] sigTypes) + { + _invokeFunc = invokeFunc; + + for (int i = 0; i < sigTypes.Length; i++) + { + if (sigTypes[i].IsByRef) + { + _hasRefs = true; + break; + } + } + } + + public bool HasRefs => _hasRefs; + + public object? Invoke(object? obj, Span parameters, BindingFlags invokeAttr) + { + return _invokeFunc(obj, parameters, 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..6fc7a05f098f36 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -95,7 +95,11 @@ internal void ThrowNoInvokeException() [DebuggerStepThroughAttribute] [Diagnostics.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(); @@ -117,23 +121,29 @@ internal void ThrowNoInvokeException() return null; } - StackAllocedArguments stackArgs = default; + Debug.Assert(obj != null); Span arguments = default; + StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible + bool copyBack = false; if (actualCount != 0) { - arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, ArgumentTypes); + copyBack = Invoker.HasRefs; + arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, ArgumentTypes, binder, culture, invokeAttr); } - object? retValue = InvokeWorker(obj, invokeAttr, arguments); + Invoker.Invoke(obj, arguments, invokeAttr); - // 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++) + if (copyBack) { - parameters![index] = arguments[index]; + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + // 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]; + } } - return retValue; + return null; } [DebuggerStepThroughAttribute] @@ -157,17 +167,24 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] StackAllocedArguments stackArgs = default; Span arguments = default; + bool copyBack = false; if (actualCount != 0) { - arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, ArgumentTypes); + copyBack = Invoker.HasRefs; + arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, ArgumentTypes, binder, culture, invokeAttr); } - object retValue = InvokeCtorWorker(invokeAttr, arguments); + object retValue = Invoker.Invoke(obj: null, arguments, invokeAttr)!; - // 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]; + if (copyBack) + { + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + // 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]; + } + } 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..82c7775f6e2589 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -93,9 +93,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 @@ -114,20 +119,25 @@ private void ThrowNoInvokeException() throw new TargetParameterCountException(SR.Arg_ParmCnt); } - StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible Span arguments = default; + StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible + bool copyBack = false; if (actualCount != 0) { - arguments = CheckArguments(ref stackArgs, parameters!, binder, invokeAttr, culture, ArgumentTypes); + copyBack = Invoker.HasRefs; + arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, ArgumentTypes, binder, culture, invokeAttr); } - object? retValue = InvokeWorker(obj, invokeAttr, arguments); + object? retValue = Invoker.Invoke(obj, arguments, invokeAttr); - // copy out. This should be made only if ByRef are present. + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) + if (copyBack) { - parameters![index] = arguments[index]; + for (int i = 0; i < arguments.Length; i++) + { + parameters![i] = arguments[i]; + } } return retValue; diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index 42939fce748365..b0e18106f469f4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -4,7 +4,8 @@ $(NoWarn),1718,SYSLIB0013 true true - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser + + $(NetCoreAppCurrent)-windows disable $(Features.Replace('nullablePublicOnly', '') 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..38be1cca5688db 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -248,7 +248,8 @@ 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); } 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..f433c88bd6da88 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(InvokeNonEmit, ArgumentTypes); + return invoker; + } + } + public override Module Module { 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) + private object? InvokeNonEmit(object? obj, Span arguments, BindingFlags invokeAttr) { Exception? exc; object? o; @@ -383,7 +394,7 @@ private RuntimeType[] ArgumentTypes { try { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, arguments, out exc); } catch (Mono.NullByRefReturnException) { @@ -402,7 +413,7 @@ private RuntimeType[] ArgumentTypes { try { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, arguments, 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(InvokeNonEmit, ArgumentTypes); + return invoker; + } + } + public override Module Module { 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,12 +859,12 @@ 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) + private object? InvokeNonEmit(object? obj, Span parameters, BindingFlags invokeAttr) { Exception exc; object? o; - if (wrapExceptions) + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { 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..345a5bee94c469 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,95 @@ 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); + return ctor.Invoker.Invoke( + obj: null, + Span.Empty, + wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); } - internal object? CheckValue(object? value, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) + internal void 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; + if (TryConvertToType(ref value, ref copyBack)) + return; if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) 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); + copyBack = true; + return; + } throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); } - private object? TryConvertToType(object? value, ref bool failed) + private bool TryConvertToType(ref object? value, ref bool copyBack) { if (IsInstanceOfType(value)) - { - return value; - } + return true; if (IsByRef) { Type? elementType = GetElementType(); if (value == null || elementType.IsInstanceOfType(value)) { - return value; + copyBack = true; + return true; } } if (value == null) - return value; + return true; if (IsEnum) { Type? type = Enum.GetUnderlyingType(this); if (type == value.GetType()) - return value; + return true; + object? res = IsConvertibleToPrimitiveType(value, type); if (res != null) - return res; + { + value = res; + copyBack = true; + return true; + } } else if (IsPrimitive) { object? res = IsConvertibleToPrimitiveType(value, this); if (res != null) - return res; + { + value = res; + copyBack = true; + return true; + } } else if (IsPointer) { Type? vtype = value.GetType(); if (vtype == typeof(IntPtr) || vtype == typeof(UIntPtr)) - return value; + return true; if (value is Pointer pointer) { Type pointerType = pointer.GetPointerType(); if (pointerType == this) - return pointer.GetPointerValue(); + { + value = pointer.GetPointerValue(); + copyBack = true; + return true; + } } } - failed = true; - return null; + return false; } // Binder uses some incompatible conversion rules. For example @@ -1983,7 +2005,7 @@ 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)!; + return ctor.Invoker.Invoke(obj: null, Span.Empty, BindingFlags.Default)!; } [MethodImplAttribute(MethodImplOptions.InternalCall)] From 840c4634c04c98e57b40f1f8b35717c2c503e253 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 7 Mar 2022 15:19:43 -0500 Subject: [PATCH 02/28] Stubs for register/unregister GC reporting --- .../RuntimeHelpers.CoreCLR.cs | 28 +++++++ src/coreclr/vm/ecall.h | 7 ++ src/coreclr/vm/ecalllist.h | 2 + src/coreclr/vm/eetwain.cpp | 23 +++++- src/coreclr/vm/frames.cpp | 80 ++++++++++++++----- src/coreclr/vm/frames.h | 6 +- 6 files changed, 122 insertions(+), 24 deletions(-) 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/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..2f810f8cff98ee 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,18 @@ 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_Next != NULL); + 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 +1024,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 +1034,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; }; From f9c0cacce571ffdeb31d5d5bca74fb9b82c31859 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sat, 12 Mar 2022 10:00:38 -0600 Subject: [PATCH 03/28] Uptake GC Registration for core (not mono) --- .../System.Private.CoreLib.csproj | 8 +- .../Reflection/ConstructorInvoker.CoreCLR.cs | 45 ++ .../System/Reflection/DynamicMethodInvoker.cs | 59 +++ .../System/Reflection/Emit/DynamicMethod.cs | 129 +++--- .../Reflection/FieldAccessor.CoreCLR.cs | 80 ++++ .../Reflection/MethodInvoker.CoreCLR.cs | 41 ++ .../src/System/Reflection/RtFieldInfo.cs | 46 +- .../RuntimeConstructorInfo.CoreCLR.cs | 37 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 59 ++- .../src/System/RuntimeHandles.cs | 2 +- src/coreclr/vm/ecalllist.h | 1 + src/coreclr/vm/invokeutil.cpp | 2 - src/coreclr/vm/reflectioninvocation.cpp | 410 ++++++++++++++++++ src/coreclr/vm/runtimehandles.h | 1 + .../System/Reflection/ConstructorInvoker.cs | 18 +- .../src/System/Reflection/FieldAccessor.cs | 21 +- .../src/System/Reflection/MethodBase.cs | 55 ++- .../src/System/Reflection/MethodInvoker.cs | 13 +- .../Reflection/RuntimeConstructorInfo.cs | 180 ++++++-- .../System/Reflection/RuntimeMethodInfo.cs | 86 +++- .../System/Reflection/InvokeRefReturn.cs | 125 ++++++ 21 files changed, 1138 insertions(+), 280 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index b84e52b4e94c14..e70407c1513d46 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + false @@ -155,6 +155,8 @@ + + @@ -177,6 +179,7 @@ + @@ -186,6 +189,7 @@ + @@ -315,7 +319,7 @@ - + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs new file mode 100644 index 00000000000000..6eaefc3aa5960b --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs @@ -0,0 +1,45 @@ +// 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 partial class ConstructorInvoker + { + [DebuggerStepThrough] + [DebuggerHidden] + public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return InvokeNonEmitUnsafe(obj, args, invokeAttr); + } + + [DebuggerStepThrough] + [DebuggerHidden] + private unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _constructorInfo.Signature, isConstructor: obj is null, out rethrow)!; + } + catch (OutOfMemoryException) + { + throw; // Re-throw for backward compatibility. + } + catch (Exception ex) when (!rethrow) + { + throw new TargetInvocationException(ex); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _constructorInfo.Signature, isConstructor: obj is null, out _)!; + } + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs new file mode 100644 index 00000000000000..de4feacd626e17 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs @@ -0,0 +1,59 @@ +// 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 +{ + internal sealed partial class DynamicMethodInvoker + { + private readonly bool _hasRefs; + + public Signature Signature { get; } + + public DynamicMethodInvoker(Signature signature) + { + Signature = signature; + + RuntimeType[] sigTypes = signature.Arguments; + for (int i = 0; i < sigTypes.Length; i++) + { + if (sigTypes[i].IsByRef) + { + _hasRefs = true; + break; + } + } + } + + public bool HasRefs => _hasRefs; + + public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return InvokeNonEmitUnsafe(obj, args, invokeAttr); + } + + private unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); + } + catch (Exception e) when (!rethrow) + { + throw new TargetInvocationException(e); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); + } + } + } +} 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 5edfc60c6ed164..25b0dec56a418a 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,7 @@ public sealed class DynamicMethod : MethodInfo private RuntimeModule m_module = null!; internal bool m_skipVisibility; internal RuntimeType? m_typeOwner; // can be null + private DynamicMethodInvoker? _invoker; // 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 +419,18 @@ internal RuntimeMethodHandle GetMethodDescriptor() public override bool IsSecurityTransparent => false; + private DynamicMethodInvoker Invoker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + _invoker ??= new DynamicMethodInvoker( + new Signature(m_methodHandle!, m_parameterTypes!, m_returnType, CallingConvention)); + + return _invoker; + } + } + public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) { if ((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs) @@ -431,67 +445,80 @@ 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 (Invoker.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 - StackAllocedArguments stackArgs = default; - Span arguments = default; - bool copyBack = false; - if (actualCount != 0) + object? retValue; + + unsafe { - // Adopt the MethodInvoker pattern here instead if the IL Emit perf gain is necessary. - bool HasRefs() + if (argCount == 0) { - for (int i = 0; i < sig.Arguments.Length; i++) + retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr); + } + else + { + Debug.Assert(parameters != null); + Span parametersOut; + bool copyBack = Invoker.HasRefs; + + if (argCount <= MaxStackAllocArgCount) { - if (sig.Arguments[i].IsByRef) + StackAllocatedByRefs byrefStorage = default; + IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + StackAllocedArguments argStorage = default; + parametersOut = new Span(ref argStorage._arg0, argCount); + + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + Invoker.Signature.Arguments, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } + else + { + parametersOut = new Span(new object[argCount]); + IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; + GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + + try { - return true; + RegisterForGCReporting(®); + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + Invoker.Signature.Arguments, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); } } - return false; - } - - copyBack = HasRefs(); - arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, sig.Arguments, binder, culture, invokeAttr); - } - - object? retValue; - bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0; - if (wrapExceptions) - { - bool rethrow = false; - - try - { - retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, constructor: false, out rethrow); - } - catch (Exception ex) when (rethrow == false) - { - throw new TargetInvocationException(ex); - } - } - else - { - retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, constructor: false, out _); - } - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - if (copyBack) - { - for (int index = 0; index < arguments.Length; index++) - { - parameters![index] = arguments[index]; + if (copyBack) + { + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) + for (int i = 0; i < argCount; i++) + { + parameters[i] = parametersOut[i]; + } + } } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs new file mode 100644 index 00000000000000..149dc7057467c8 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Reflection +{ + internal partial class FieldAccessor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object? InvokeGetter(object? obj) + { + // Todo: add strategy for calling IL Emit-based version + return InvokeGetterNonEmit(obj); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void InvokeSetter(object? obj, object? value) + { + // Todo: add strategy for calling IL Emit-based version + InvokeSetterNonEmit(obj, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal object? InvokeGetterNonEmit(object? obj) + { + RuntimeType? declaringType = _fieldInfo.DeclaringType as RuntimeType; + RuntimeType fieldType = (RuntimeType)_fieldInfo.FieldType; + bool domainInitialized = false; + + if (declaringType == null) + { + return RuntimeFieldHandle.GetValue(_fieldInfo, obj, fieldType, null, ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + object? retVal = RuntimeFieldHandle.GetValue(_fieldInfo, obj, fieldType, declaringType, ref domainInitialized); + declaringType.DomainInitialized = domainInitialized; + return retVal; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void InvokeSetterNonEmit(object? obj, object? value) + { + RuntimeType? declaringType = _fieldInfo.DeclaringType as RuntimeType; + RuntimeType fieldType = (RuntimeType)_fieldInfo.FieldType; + bool domainInitialized = false; + + if (declaringType == null) + { + RuntimeFieldHandle.SetValue( + _fieldInfo, + obj, + value, + fieldType, + _fieldInfo.Attributes, + declaringType: null, + ref domainInitialized); + } + else + { + domainInitialized = declaringType.DomainInitialized; + + RuntimeFieldHandle.SetValue( + _fieldInfo, + obj, + value, + fieldType, + _fieldInfo.Attributes, + declaringType, + ref domainInitialized); + + declaringType.DomainInitialized = domainInitialized; + } + } + + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs new file mode 100644 index 00000000000000..d3d1918e4e3e51 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs @@ -0,0 +1,41 @@ +// 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 partial class MethodInvoker + { + [DebuggerStepThrough] + [DebuggerHidden] + public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return InvokeNonEmitUnsafe(obj, args, invokeAttr); + } + + [DebuggerStepThrough] + [DebuggerHidden] + private unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _methodInfo.Signature, isConstructor: false, out rethrow); + } + catch (Exception e) when (!rethrow) + { + throw new TargetInvocationException(e); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _methodInfo.Signature, isConstructor: false, out _); + } + } + } +} 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 e989a621c11871..ab40881b1046d4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -17,7 +17,7 @@ internal sealed unsafe class RtFieldInfo : RuntimeFieldInfo, IRuntimeFieldInfo // lazy caching private string? m_name; private RuntimeType? m_fieldType; - internal FieldAccessor? m_reflectionInvoker; + internal FieldAccessor? m_invoker; internal InvocationFlags InvocationFlags { @@ -31,8 +31,8 @@ private FieldAccessor Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - m_reflectionInvoker ??= new FieldAccessor(InvokeGetterNonEmit, InvokeSetterNonEmit); - return m_reflectionInvoker; + m_invoker ??= new FieldAccessor(this); + return m_invoker; } } @@ -82,46 +82,6 @@ internal RtFieldInfo( #region Private Members RuntimeFieldHandleInternal IRuntimeFieldInfo.Value => new RuntimeFieldHandleInternal(m_fieldHandle); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal object? InvokeGetterNonEmit(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; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void InvokeSetterNonEmit(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, m_fieldAttributes, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - RuntimeFieldHandle.SetValue(this, obj, value, fieldType, m_fieldAttributes, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - } - } - #endregion #region Internal Members 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 e190f8f0937c45..f50c9daa2a10b3 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,7 +28,7 @@ internal sealed partial class RuntimeConstructorInfo : ConstructorInfo, IRuntime private MethodAttributes m_methodAttributes; private BindingFlags m_bindingFlags; private Signature? m_signature; - private ConstructorInvoker? m_reflectionInvoker; + private ConstructorInvoker? m_invoker; internal InvocationFlags InvocationFlags { @@ -49,8 +49,8 @@ private ConstructorInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - m_reflectionInvoker ??= new ConstructorInvoker(InvokeNonEmit, ArgumentTypes); - return m_reflectionInvoker; + m_invoker ??= new ConstructorInvoker(this); + return m_invoker; } } #endregion @@ -74,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 @@ -196,7 +196,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.")] @@ -215,33 +215,6 @@ private void InvokeClassConstructor() } } - [DebuggerStepThrough] - [DebuggerHidden] - private object InvokeNonEmit(object? obj, Span arguments, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - bool rethrow = false; - - try - { - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: obj is null, out rethrow)!; - } - catch (OutOfMemoryException) - { - throw; // Re-throw for backward compatibility. - } - catch (Exception ex) when (rethrow == false) - { - throw new TargetInvocationException(ex); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: obj is null, out _)!; - } - } - [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 7633633523ee47..439c9d4bd84f3a 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,7 +27,7 @@ internal sealed partial class RuntimeMethodInfo : MethodInfo, IRuntimeMethodInfo private Signature? m_signature; private RuntimeType m_declaringType; private object? m_keepalive; - private MethodInvoker? m_reflectionInvoker; + private MethodInvoker? m_invoker; internal InvocationFlags InvocationFlags { @@ -48,8 +48,11 @@ private MethodInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - m_reflectionInvoker ??= new MethodInvoker(InvokeNonEmit, ArgumentTypes); - return m_reflectionInvoker; + unsafe // todo:remove + { + m_invoker ??= new MethodInvoker(this); + return m_invoker; + } } } #endregion @@ -318,32 +321,9 @@ public override MethodImplAttributes GetMethodImplementationFlags() #endregion - #region Invocation Logic(On MemberBase) + #region Invocation Logic [DebuggerStepThrough] [DebuggerHidden] - private object? InvokeNonEmit(object? obj, Span arguments, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - bool rethrow = false; - - try - { - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: false, out rethrow); - } - catch (Exception e) when (!rethrow) - { - throw new TargetInvocationException(e); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, in arguments, Signature, constructor: false, out _); - } - } - - [DebuggerStepThroughAttribute] - [Diagnostics.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) @@ -363,12 +343,29 @@ public override MethodImplAttributes GetMethodImplementationFlags() throw new TargetParameterCountException(SR.Arg_ParmCnt); } - Span parameters = new Span(ref parameter, 1); - StackAllocedArguments stackArgs = default; bool _ = false; - Span arguments = CheckArguments(ref stackArgs, parameters, ref _, ArgumentTypes, binder, culture, invokeAttr); + object? retValue; - object? retValue = Invoker.Invoke(obj, arguments, invokeAttr); + unsafe + { + StackAllocatedByRefs byrefStorage = default; + IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + StackAllocedArguments argStorage = default; + Span parametersOut = new(ref argStorage._arg0, 1); + Span parameters = new(ref parameter, 1); + + CheckArguments( + ref parametersOut, + unsafeParameters, + ref _, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } return retValue; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index fa80ab6a05539a..5be5e55fad6a78 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -979,7 +979,7 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method) [DebuggerStepThrough] [DebuggerHidden] [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object? InvokeMethod(object? target, in Span arguments, Signature sig, bool constructor, out bool rethrow); + internal static extern object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor, out bool rethrow); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetMethodInstantiation")] private static partial void GetMethodInstantiation(RuntimeMethodHandleInternal method, ObjectHandleOnStack types, Interop.BOOL fAsRuntimeTypeArray); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 0c0525eedfc899..58aa589fbe1b25 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -217,6 +217,7 @@ FCFuncEnd() FCFuncStart(gRuntimeMethodHandle) FCFuncElement("_GetCurrentMethod", RuntimeMethodHandle::GetCurrentMethod) FCFuncElement("InvokeMethod", RuntimeMethodHandle::InvokeMethod) + FCFuncElement("InvokeMethod2", RuntimeMethodHandle::InvokeMethod2) FCFuncElement("GetImplAttributes", RuntimeMethodHandle::GetImplAttributes) FCFuncElement("GetAttributes", RuntimeMethodHandle::GetAttributes) FCFuncElement("GetDeclaringType", RuntimeMethodHandle::GetDeclaringType) diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index b9779762d40cd7..f43ce585032dd5 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -1236,5 +1236,3 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ return obj; } - - diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 432db40afc6a48..7daa2711391191 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -369,6 +369,46 @@ struct ByRefToNullable { } }; +static OBJECTREF InvokeArrayConstructor2(TypeHandle th, OBJECTREF** objs, int argCnt) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + // Validate the argCnt an the Rank. Also allow nested SZARRAY's. + _ASSERTE(argCnt == (int) th.GetRank() || argCnt == (int) th.GetRank() * 2 || + th.GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY); + + // Validate all of the parameters. These all typed as integers + int allocSize = 0; + if (!ClrSafeInt::multiply(sizeof(INT32), argCnt, allocSize)) + COMPlusThrow(kArgumentException, IDS_EE_SIGTOOCOMPLEX); + + INT32* indexes = (INT32*) _alloca((size_t)allocSize); + ZeroMemory(indexes, allocSize); + + for (DWORD i=0; i<(DWORD)argCnt; i++) + { + if (!objs[i]) + COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); + + MethodTable* pMT = (*objs[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[i], &value); + memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); + } + + return AllocateArrayEx(th, indexes, argCnt); +} + static OBJECTREF InvokeArrayConstructor(TypeHandle th, Span* objs, int argCnt) { CONTRACTL { @@ -536,6 +576,376 @@ class ArgIteratorForMethodInvoke : public ArgIteratorTemplateGetMethod(); + TypeHandle ownerType = gc.pSig->GetDeclaringType(); + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + if (ownerType.IsSharedByGenericInstantiations()) + { + COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); + } + +#ifdef _DEBUG + if (g_pConfig->ShouldInvokeHalt(pMeth)) + { + _ASSERTE(!"InvokeHalt"); + } +#endif + + BOOL fCtorOfVariableSizedObject = FALSE; + + if (fConstructor) + { + // If we are invoking a constructor on an array then we must + // handle this specially. + if (ownerType.IsArray()) { + gc.retVal = InvokeArrayConstructor2(ownerType, + objs, + gc.pSig->NumFixedArgs()); + goto Done; + } + + // Variable sized objects, like String instances, allocate themselves + // so they are a special case. + MethodTable * pMT = ownerType.AsMethodTable(); + fCtorOfVariableSizedObject = pMT->HasComponentSize(); + if (!fCtorOfVariableSizedObject) + gc.retVal = pMT->Allocate(); + } + + { + ArgIteratorForMethodInvoke argit(&gc.pSig, fCtorOfVariableSizedObject); + + if (argit.IsActivationNeeded()) + pMeth->EnsureActive(); + CONSISTENCY_CHECK(pMeth->CheckActivated()); + + UINT nStackBytes = argit.SizeOfFrameArgumentArray(); + + // Note that SizeOfFrameArgumentArray does overflow checks with sufficient margin to prevent overflows here + SIZE_T nAllocaSize = TransitionBlock::GetNegSpaceSize() + sizeof(TransitionBlock) + nStackBytes; + + Thread * pThread = GET_THREAD(); + + LPBYTE pAlloc = (LPBYTE)_alloca(nAllocaSize); + + LPBYTE pTransitionBlock = pAlloc + TransitionBlock::GetNegSpaceSize(); + + CallDescrData callDescrData; + + callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); + _ASSERTE((nStackBytes % TARGET_POINTER_SIZE) == 0); + callDescrData.numStackSlots = nStackBytes / TARGET_POINTER_SIZE; +#ifdef CALLDESCR_ARGREGS + callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); +#endif +#ifdef CALLDESCR_RETBUFFARGREG + callDescrData.pRetBuffArg = (UINT64*)(pTransitionBlock + TransitionBlock::GetOffsetOfRetBuffArgReg()); +#endif +#ifdef CALLDESCR_FPARGREGS + callDescrData.pFloatArgumentRegisters = NULL; +#endif +#ifdef CALLDESCR_REGTYPEMAP + callDescrData.dwRegTypeMap = 0; +#endif + callDescrData.fpReturnSize = argit.GetFPReturnSize(); + + // This is duplicated logic from MethodDesc::GetCallTarget + PCODE pTarget; + if (pMeth->IsVtableMethod()) + { + pTarget = pMeth->GetSingleCallableAddrOfVirtualizedCode(&gc.target, ownerType); + } + else + { + pTarget = pMeth->GetSingleCallableAddrOfCode(); + } + callDescrData.pTarget = pTarget; + + // Build the arguments on the stack + + GCStress::MaybeTrigger(); + + FrameWithCookie *pProtectValueClassFrame = NULL; + ValueClassInfo *pValueClasses = NULL; + ByRefToNullable* byRefToNullables = NULL; + + // if we have the magic Value Class return, we need to allocate that class + // and place a pointer to it on the stack. + + BOOL hasRefReturnAndNeedsBoxing = FALSE; // Indicates that the method has a BYREF return type and the target type needs to be copied into a preallocated boxed object. + + TypeHandle retTH = gc.pSig->GetReturnTypeHandle(); + + TypeHandle refReturnTargetTH; // Valid only if retType == ELEMENT_TYPE_BYREF. Caches the TypeHandle of the byref target. + BOOL fHasRetBuffArg = argit.HasRetBuffArg(); + CorElementType retType = retTH.GetSignatureCorElementType(); + BOOL hasValueTypeReturn = retTH.IsValueType() && retType != ELEMENT_TYPE_VOID; + _ASSERTE(hasValueTypeReturn || !fHasRetBuffArg); // only valuetypes are returned via a return buffer. + if (hasValueTypeReturn) { + gc.retVal = retTH.GetMethodTable()->Allocate(); + } + else if (retType == ELEMENT_TYPE_BYREF) + { + refReturnTargetTH = retTH.AsTypeDesc()->GetTypeParam(); + + // If the target of the byref is a value type, we need to preallocate a boxed object to hold the managed return value. + if (refReturnTargetTH.IsValueType()) + { + _ASSERTE(refReturnTargetTH.GetSignatureCorElementType() != ELEMENT_TYPE_VOID); // Managed Reflection layer has a bouncer for "ref void" returns. + hasRefReturnAndNeedsBoxing = TRUE; + gc.retVal = refReturnTargetTH.GetMethodTable()->Allocate(); + } + } + + // Copy "this" pointer + if (!pMeth->IsStatic() && !fCtorOfVariableSizedObject) { + PVOID pThisPtr; + + if (fConstructor) + { + // Copy "this" pointer: only unbox if type is value type and method is not unboxing stub + if (ownerType.IsValueType() && !pMeth->IsUnboxingStub()) { + // Note that we create a true boxed nullabe and then convert it to a T below + pThisPtr = gc.retVal->GetData(); + } + else + pThisPtr = OBJECTREFToObject(gc.retVal); + } + else + if (!pMeth->GetMethodTable()->IsValueType()) + pThisPtr = OBJECTREFToObject(gc.target); + else { + if (pMeth->IsUnboxingStub()) + pThisPtr = OBJECTREFToObject(gc.target); + else { + // Create a true boxed Nullable and use that as the 'this' pointer. + // since what is passed in is just a boxed T + MethodTable* pMT = pMeth->GetMethodTable(); + if (Nullable::IsNullableType(pMT)) { + OBJECTREF bufferObj = pMT->Allocate(); + void* buffer = bufferObj->GetData(); + Nullable::UnBox(buffer, gc.target, pMT); + pThisPtr = buffer; + } + else + pThisPtr = gc.target->UnBox(); + } + } + + *((LPVOID*) (pTransitionBlock + argit.GetThisOffset())) = pThisPtr; + } + + // NO GC AFTER THIS POINT. The object references in the method frame are not protected. + // + // We have already copied "this" pointer so we do not want GC to happen even sooner. Unfortunately, + // we may allocate in the process of copying this pointer that makes it hard to express using contracts. + // + // If an exception occurs a gc may happen but we are going to dump the stack anyway and we do + // not need to protect anything. + + { + BEGINFORBIDGC(); +#ifdef _DEBUG + GCForbidLoaderUseHolder forbidLoaderUse; +#endif + + // Take care of any return arguments + if (fHasRetBuffArg) + { + PVOID pRetBuff = gc.retVal->GetData(); + *((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pRetBuff; + } + + // copy args + UINT nNumArgs = gc.pSig->NumFixedArgs(); + for (UINT i = 0 ; i < nNumArgs; i++) { + + TypeHandle th = gc.pSig->GetArgumentAt(i); + + int ofs = argit.GetNextOffset(); + _ASSERTE(ofs != TransitionBlock::InvalidOffset); + +#ifdef CALLDESCR_REGTYPEMAP + FillInRegTypeMap(ofs, argit.GetArgType(), (BYTE *)&callDescrData.dwRegTypeMap); +#endif + +#ifdef CALLDESCR_FPARGREGS + // Under CALLDESCR_FPARGREGS -ve offsets indicate arguments in floating point registers. If we have at + // least one such argument we point the call worker at the floating point area of the frame (we leave + // it null otherwise since the worker can perform a useful optimization if it knows no floating point + // registers need to be set up). + + if (TransitionBlock::HasFloatRegister(ofs, argit.GetArgLocDescForStructInRegs()) && + (callDescrData.pFloatArgumentRegisters == NULL)) + { + callDescrData.pFloatArgumentRegisters = (FloatArgumentRegisters*) (pTransitionBlock + + TransitionBlock::GetOffsetOfFloatArgumentRegisters()); + } +#endif + + UINT structSize = argit.GetArgSize(); + + bool needsStackCopy = false; + + // 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()) { + th = nullableType; + structSize = th.GetSize(); + needsStackCopy = true; + } +#ifdef ENREGISTERED_PARAMTYPE_MAXSIZE + else if (argit.IsArgPassedByRef()) + { + needsStackCopy = true; + } +#endif + + ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); + + if(needsStackCopy) + { + MethodTable * pMT = th.GetMethodTable(); + _ASSERTE(pMT && pMT->IsValueType()); + + PVOID pArgDst = argDest.GetDestinationAddress(); + + PVOID pStackCopy = _alloca(structSize); + *(PVOID *)pArgDst = pStackCopy; + pArgDst = pStackCopy; + + if (!nullableType.IsNull()) + { + byRefToNullables = new(_alloca(sizeof(ByRefToNullable))) ByRefToNullable(i, pStackCopy, nullableType, byRefToNullables); + } + + // save the info into ValueClassInfo + if (pMT->ContainsPointers()) + { + pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pStackCopy, pMT, pValueClasses); + } + + // We need a new ArgDestination that points to the stack copy + argDest = ArgDestination(pStackCopy, 0, NULL); + } + + InvokeUtil::CopyArg(th, objs[i], &argDest); + } + + ENDFORBIDGC(); + } + + if (pValueClasses != NULL) + { + pProtectValueClassFrame = new (_alloca (sizeof (FrameWithCookie))) + FrameWithCookie(pThread, pValueClasses); + } + + // Call the method + *pRethrow = FALSE; + CallDescrWorkerWithHandler(&callDescrData); + *pRethrow = TRUE; + + // It is still illegal to do a GC here. The return type might have/contain GC pointers. + if (fConstructor) + { + // We have a special case for Strings...The object is returned... + if (fCtorOfVariableSizedObject) { + PVOID pReturnValue = &callDescrData.returnValue; + gc.retVal = *(OBJECTREF *)pReturnValue; + } + + // If it is a Nullable, box it using Nullable conventions. + // TODO: this double allocates on constructions which is wasteful + gc.retVal = Nullable::NormalizeBox(gc.retVal); + } + else + if (hasValueTypeReturn || hasRefReturnAndNeedsBoxing) + { + _ASSERTE(gc.retVal != NULL); + + if (hasRefReturnAndNeedsBoxing) + { + // Method has BYREF return and the target type is one that needs boxing. We need to copy into the boxed object we have allocated for this purpose. + LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; + if (pReturnedReference == NULL) + { + COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); + } + CopyValueClass(gc.retVal->GetData(), pReturnedReference, gc.retVal->GetMethodTable()); + } + // if the structure is returned by value, then we need to copy in the boxed object + // we have allocated for this purpose. + else if (!fHasRetBuffArg) + { + CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable()); + } + // From here on out, it is OK to have GCs since the return object (which may have had + // GC pointers has been put into a GC object and thus protected. + + // TODO this creates two objects which is inefficient + // If the return type is a Nullable box it into the correct form + gc.retVal = Nullable::NormalizeBox(gc.retVal); + } + else if (retType == ELEMENT_TYPE_BYREF) + { + // WARNING: pReturnedReference is an unprotected inner reference so we must not trigger a GC until the referenced value has been safely captured. + LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; + if (pReturnedReference == NULL) + { + COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); + } + + gc.retVal = InvokeUtil::CreateObjectAfterInvoke(refReturnTargetTH, pReturnedReference); + } + else + { + gc.retVal = InvokeUtil::CreateObjectAfterInvoke(retTH, &callDescrData.returnValue); + } + + while (byRefToNullables != NULL) { + OBJECTREF obj = Nullable::Box(byRefToNullables->data, byRefToNullables->type.GetMethodTable()); + SetObjectReference(objs[byRefToNullables->argNum], obj); + byRefToNullables = byRefToNullables->next; + } + + if (pProtectValueClassFrame != NULL) + pProtectValueClassFrame->Pop(pThread); + + } + +Done: + ; + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.retVal); +} +FCIMPLEND + FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, Object *target, Span* objs, diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 03ab547d77766d..948feb536f95fd 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -223,6 +223,7 @@ class RuntimeMethodHandle { static FCDECL1(ReflectMethodObject*, GetCurrentMethod, StackCrawlMark* stackMark); static FCDECL5(Object*, InvokeMethod, Object *target, Span* objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); + static FCDECL5(Object*, InvokeMethod2, Object *target, OBJECTREF** objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); struct StreamingContextData { Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index 4487289d3e7413..28c1c619fede8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -5,19 +5,18 @@ namespace System.Reflection { internal sealed partial class ConstructorInvoker { - public delegate object? InvokeCtorFunc(object? obj, Span args, BindingFlags invokeAttr); - private readonly bool _hasRefs; + private readonly RuntimeConstructorInfo _constructorInfo; public InvocationFlags _invocationFlags; - private InvokeCtorFunc _invoke; - public ConstructorInvoker(InvokeCtorFunc invokeFunc, RuntimeType[] sigTypes) + public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) { - _invoke = invokeFunc; + _constructorInfo = constructorInfo; - for (int i = 0; i < sigTypes.Length; i++) + RuntimeType[] argTypes = constructorInfo.ArgumentTypes; + for (int i = 0; i < argTypes.Length; i++) { - if (sigTypes[i].IsByRef) + if (argTypes[i].IsByRef) { _hasRefs = true; break; @@ -26,10 +25,5 @@ public ConstructorInvoker(InvokeCtorFunc invokeFunc, RuntimeType[] sigTypes) } public bool HasRefs => _hasRefs; - - public object? Invoke(object? obj, Span parameters, BindingFlags invokeAttr) - { - return _invoke(obj, parameters, 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 index 370b3907a12b38..32118ef0047ef0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs @@ -5,27 +5,12 @@ namespace System.Reflection { internal sealed partial class FieldAccessor { - public delegate object? InvokeGetterFunc(object? obj); - public delegate void InvokeSetterFunc(object? obj, object? value); - + private readonly RtFieldInfo _fieldInfo; public InvocationFlags _invocationFlags; - private InvokeGetterFunc _invokeGetterFunc; - private InvokeSetterFunc _invokeSetterFunc; - - public FieldAccessor(InvokeGetterFunc getterFallback, InvokeSetterFunc setterFallback) - { - _invokeGetterFunc = getterFallback; - _invokeSetterFunc = setterFallback; - } - - public object? InvokeGetter(object? obj) - { - return _invokeGetterFunc(obj); - } - public void InvokeSetter(object? obj, object? value) + public FieldAccessor(RtFieldInfo fieldInfo) { - _invokeSetterFunc(obj, value); + _fieldInfo = fieldInfo; } } } 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 7609750c4fe4aa..df5a3277d5c2e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -140,30 +140,19 @@ private protected void ValidateInvokeTarget(object? target) } } - private protected Span CheckArguments( - ref StackAllocedArguments stackArgs, - ReadOnlySpan parameters, + private protected unsafe void CheckArguments( + ref Span parametersOut, + IntPtr* unsafeParameters, ref bool copyBack, + 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; for (int i = 0; i < parameters.Length; i++) { @@ -181,24 +170,44 @@ BindingFlags invokeAttr } sigTypes[i].CheckValue(ref arg, ref copyBack, binder, culture, invokeAttr); - copyOfParameters[i] = arg; - } - 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. + parametersOut[i] = arg; + + unsafeParameters[i] = (IntPtr)Unsafe.AsPointer(ref parametersOut[i]); + } } + 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. + 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 + } + + // Helper struct to avoid intermediate IntPtr[] allocation and RegisterForGCReporting in calls to the native reflection stack. + 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 index 3223d1c4fbb043..14774347be3178 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -5,15 +5,15 @@ namespace System.Reflection { internal sealed partial class MethodInvoker { - public delegate object? InvokeFunc(object? obj, Span args, BindingFlags invokeAttr); internal InvocationFlags _invocationFlags; + private readonly RuntimeMethodInfo _methodInfo; private readonly bool _hasRefs; - private InvokeFunc _invokeFunc; - public MethodInvoker(InvokeFunc invokeFunc, RuntimeType[] sigTypes) + public MethodInvoker(RuntimeMethodInfo methodInfo) { - _invokeFunc = invokeFunc; + _methodInfo = methodInfo; + RuntimeType[] sigTypes = methodInfo.Signature.Arguments; for (int i = 0; i < sigTypes.Length; i++) { if (sigTypes[i].IsByRef) @@ -25,10 +25,5 @@ public MethodInvoker(InvokeFunc invokeFunc, RuntimeType[] sigTypes) } public bool HasRefs => _hasRefs; - - public object? Invoke(object? obj, Span parameters, BindingFlags invokeAttr) - { - return _invokeFunc(obj, parameters, 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 6fc7a05f098f36..92f27a73977593 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,8 +93,8 @@ internal void ThrowNoInvokeException() throw new TargetException(); } - [DebuggerStepThroughAttribute] - [Diagnostics.DebuggerHidden] + [DebuggerStepThrough] + [DebuggerHidden] public override object? Invoke( object? obj, BindingFlags invokeAttr, @@ -107,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); } @@ -122,32 +123,81 @@ internal void ThrowNoInvokeException() } Debug.Assert(obj != null); - Span arguments = default; - StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible - bool copyBack = false; - if (actualCount != 0) - { - copyBack = Invoker.HasRefs; - arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, ArgumentTypes, binder, culture, invokeAttr); - } - - Invoker.Invoke(obj, arguments, invokeAttr); - if (copyBack) + unsafe { - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) + if (argCount == 0) { - parameters![index] = arguments[index]; + Invoker.InvokeUnsafe(obj, args: default, invokeAttr); } - } + else + { + Debug.Assert(parameters != null); + Span parametersOut; + bool copyBack = Invoker.HasRefs; + + if (argCount <= MaxStackAllocArgCount) + { + StackAllocatedByRefs byrefStorage = default; + IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + StackAllocedArguments argStorage = default; + parametersOut = new Span(ref argStorage._arg0, argCount); + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } + else + { + parametersOut = new Span(new object[argCount]); + IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; + GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + + try + { + RegisterForGCReporting(®); + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); + } + } + + if (copyBack) + { + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) + for (int i = 0; i < argCount; i++) + { + parameters[i] = parametersOut[i]; + } + } + } + } return null; } - [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) @@ -159,33 +209,85 @@ 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; - bool copyBack = false; - if (actualCount != 0) - { - copyBack = Invoker.HasRefs; - arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, ArgumentTypes, binder, culture, invokeAttr); - } - - object retValue = Invoker.Invoke(obj: null, arguments, invokeAttr)!; + object? retValue; - if (copyBack) + unsafe { - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int index = 0; index < arguments.Length; index++) + if (argCount == 0) { - parameters![index] = arguments[index]; + retValue = Invoker.InvokeUnsafe(obj: null, args: default, invokeAttr); + } + else + { + Debug.Assert(parameters != null); + Span parametersOut; + bool copyBack = Invoker.HasRefs; + + if (argCount <= MaxStackAllocArgCount) + { + StackAllocatedByRefs byrefStorage = default; + IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + StackAllocedArguments argStorage = default; + parametersOut = new Span(ref argStorage._arg0, argCount); + + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj: null, unsafeParameters, invokeAttr); + } + else + { + parametersOut = new Span(new object[argCount]); + IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; + GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + + try + { + RegisterForGCReporting(®); + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj: null, unsafeParameters, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); + } + } + + if (copyBack) + { + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) + for (int i = 0; i < argCount; i++) + { + parameters[i] = parametersOut[i]; + } + } } } + Debug.Assert(retValue != null); 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 82c7775f6e2589..ad1780b6a7c91f 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 { @@ -113,30 +114,81 @@ 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); } - Span arguments = default; - StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible - bool copyBack = false; - if (actualCount != 0) - { - copyBack = Invoker.HasRefs; - arguments = CheckArguments(ref stackArgs, parameters!, ref copyBack, ArgumentTypes, binder, culture, invokeAttr); - } + object? retValue; - object? retValue = Invoker.Invoke(obj, arguments, invokeAttr); - - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - if (copyBack) + unsafe { - for (int i = 0; i < arguments.Length; i++) + if (argCount == 0) { - parameters![i] = arguments[i]; + retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr); + } + else + { + Debug.Assert(parameters != null); + Span parametersOut; + bool copyBack = Invoker.HasRefs; + + if (argCount <= MaxStackAllocArgCount) + { + StackAllocatedByRefs byrefStorage = default; + IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + StackAllocedArguments argStorage = default; + parametersOut = new Span(ref argStorage._arg0, argCount); + + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } + else + { + parametersOut = new Span(new object[argCount]); + IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; + GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + + try + { + RegisterForGCReporting(®); + CheckArguments( + ref parametersOut, + unsafeParameters, + ref copyBack, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + } + finally + { + UnregisterForGCReporting(®); + } + } + + if (copyBack) + { + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) + for (int i = 0; i < argCount; i++) + { + parameters[i] = parametersOut[i]; + } + } } } diff --git a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs index 885e17992c1768..c070287bf12e44 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.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.Runtime.CompilerServices; using Xunit; @@ -10,6 +11,77 @@ namespace System.Reflection.Tests { public class InvokeRefReturnNetcoreTests { + [Fact] + public void Test_Class() + { + MethodInfo mi = typeof(TestClass).GetMethod(nameof(TestClass.Initialize), BindingFlags.Instance | BindingFlags.Public); + + int intField = 42; + int intProperty = 43; + string stringProperty = "Hello"; + + var obj = new TestClass(); + + Stopwatch sw = new Stopwatch(); + sw.Start(); + for (long l = 0; l < 10_000_000; l++) + { + object ret = mi.Invoke(obj, BindingFlags.Default, null, new object[] { intField, intProperty, stringProperty }, null); //1977 + //string str = (string)ret; + //object ret = mi.Invoke(obj, BindingFlags.Default, null, new object[] { intField, intProperty, stringProperty }, null); //1977 + + //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField, intProperty, stringProperty }, null); //804 456 (new) + + //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField }, null); //631 + //object ret = mi.Invoke(obj, new object[] { intField, intProperty, stringProperty }); + //object ret = mi.InvokeDirect(obj, args); + + //Assert.Equal(42, obj.MyIntField); + //Assert.Equal(43, obj.MyIntProperty); + //Assert.Equal("Hello", obj.MyStringProperty); + } + sw.Stop(); + + long ticks = sw.ElapsedMilliseconds; + throw new Exception("TIME:" + ticks); + } + + [Fact] + public void Test_Class2() + { + MethodInfo mi = typeof(TestClass).GetMethod(nameof(TestClass.Initialize), BindingFlags.Instance | BindingFlags.Public); + + int intField = 42; + int intProperty = 43; + string stringProperty = "Hello"; + + var obj = new TestClass(); + + Stopwatch sw = new Stopwatch(); + sw.Start(); + for (long l = 0; l < 10_000_000; l++) + { + //obj = new TestClass(); + object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField, intProperty, stringProperty }, null); //1977 + + //object ret = mi.Invoke(obj, BindingFlags.Default, null, new object[] { intField, intProperty, stringProperty }, null); //1977 + + //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField, intProperty, stringProperty }, null); //804 456 (new) + + //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField }, null); //631 + //object ret = mi.Invoke(obj, new object[] { intField, intProperty, stringProperty }); + //object ret = mi.InvokeDirect(obj, args); + + //Assert.Equal(42, obj.MyIntField); + //Assert.Equal(43, obj.MyIntProperty); + //Assert.Equal("Hello", obj.MyStringProperty); + } + sw.Stop(); + + long ticks = sw.ElapsedMilliseconds; + throw new Exception("TIME:" + ticks); + } + [Theory] [MemberData(nameof(RefReturnInvokeTestData))] public static void TestRefReturnPropertyGetValue(T value) @@ -178,4 +250,57 @@ private sealed unsafe class TestClassIntPointer public ref int* NullRefReturningProp => ref *(int**)null; } } + internal sealed class TestClass + { + public int MyIntField; + public int MyIntProperty { get; set; } + public string MyStringProperty { get; set; } + + // public void Initialize(out int myIntField, int myIntProperty, string myStringProperty) + public void Initialize(int myIntField, int myIntProperty, string myStringProperty) + { + MyIntField = myIntField; + MyIntProperty = myIntProperty; + MyStringProperty = myStringProperty; + //return "Hello"; + } + + + public void Initialize1(int i) + { + string s = "SDF"; + s += "sdg"; + } + + public void Initialize5(int myIntField, int myIntProperty, string myStringProperty, int p4, long p5) + { + MyIntField = myIntField; + MyIntProperty = myIntProperty; + MyStringProperty = myStringProperty; + p5 = 100; + } + + public string InitializeAndReturn(int myIntField, int myIntProperty, string myStringProperty) + { + MyIntField = myIntField; + MyIntProperty = myIntProperty; + MyStringProperty = myStringProperty; + return MyStringProperty; + } + + public string InitializeAndThrow(int myIntField, int myIntProperty, string myStringProperty) + { + throw new Exception("Hello"); + } + + public void AddToMyIntField(int value) + { + MyIntField += value; + } + + public string ConcatToMyStringProperty(string value) + { + return MyStringProperty + value; + } + } } From 2587e3f64d78bc5f1dd1a196277fb0c8653289e0 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sun, 13 Mar 2022 21:15:56 -0500 Subject: [PATCH 04/28] Remove scaffolding; remove Assert --- src/coreclr/vm/ecalllist.h | 1 - src/coreclr/vm/frames.cpp | 1 - src/coreclr/vm/reflectioninvocation.cpp | 49 +++---------------------- src/coreclr/vm/runtimehandles.h | 3 +- 4 files changed, 7 insertions(+), 47 deletions(-) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 58aa589fbe1b25..0c0525eedfc899 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -217,7 +217,6 @@ FCFuncEnd() FCFuncStart(gRuntimeMethodHandle) FCFuncElement("_GetCurrentMethod", RuntimeMethodHandle::GetCurrentMethod) FCFuncElement("InvokeMethod", RuntimeMethodHandle::InvokeMethod) - FCFuncElement("InvokeMethod2", RuntimeMethodHandle::InvokeMethod2) FCFuncElement("GetImplAttributes", RuntimeMethodHandle::GetImplAttributes) FCFuncElement("GetAttributes", RuntimeMethodHandle::GetAttributes) FCFuncElement("GetDeclaringType", RuntimeMethodHandle::GetDeclaringType) diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index 2f810f8cff98ee..4ac458b1d62f6f 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -1005,7 +1005,6 @@ void GCFrame::Pop() NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; - PRECONDITION(m_Next != NULL); PRECONDITION(m_pCurThread != NULL); } CONTRACTL_END; diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 7daa2711391191..302f4763ae6b76 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -369,7 +369,7 @@ struct ByRefToNullable { } }; -static OBJECTREF InvokeArrayConstructor2(TypeHandle th, OBJECTREF** objs, int argCnt) +static OBJECTREF InvokeArrayConstructor(TypeHandle th, OBJECTREF** objs, int argCnt) { CONTRACTL { THROWS; @@ -409,46 +409,6 @@ static OBJECTREF InvokeArrayConstructor2(TypeHandle th, OBJECTREF** objs, int ar return AllocateArrayEx(th, indexes, argCnt); } -static OBJECTREF InvokeArrayConstructor(TypeHandle th, Span* objs, int argCnt) -{ - CONTRACTL { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - // Validate the argCnt an the Rank. Also allow nested SZARRAY's. - _ASSERTE(argCnt == (int) th.GetRank() || argCnt == (int) th.GetRank() * 2 || - th.GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY); - - // Validate all of the parameters. These all typed as integers - int allocSize = 0; - if (!ClrSafeInt::multiply(sizeof(INT32), argCnt, allocSize)) - COMPlusThrow(kArgumentException, IDS_EE_SIGTOOCOMPLEX); - - INT32* indexes = (INT32*) _alloca((size_t)allocSize); - ZeroMemory(indexes, allocSize); - - for (DWORD i=0; i<(DWORD)argCnt; i++) - { - if (!objs->GetAt(i)) - COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); - - 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); - memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); - } - - return AllocateArrayEx(th, indexes, argCnt); -} - static BOOL IsActivationNeededForMethodInvoke(MethodDesc * pMD) { CONTRACTL { @@ -576,7 +536,7 @@ class ArgIteratorForMethodInvoke : public ArgIteratorTemplateNumFixedArgs()); goto Done; @@ -946,6 +906,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod2, } FCIMPLEND +<<<<<<< HEAD FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, Object *target, Span* objs, @@ -1361,6 +1322,8 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } FCIMPLEND +======= +>>>>>>> 96d47f8bff6 (Remove scaffolding; remove Assert) struct SkipStruct { StackCrawlMark* pStackMark; MethodDesc* pMeth; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 948feb536f95fd..e1bf52fcdc947c 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -222,8 +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* pRethrow); - static FCDECL5(Object*, InvokeMethod2, Object *target, OBJECTREF** objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); + static FCDECL5(Object*, InvokeMethod, Object *target, OBJECTREF** objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); struct StreamingContextData { Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a From fc0c0447cf7c97b56b3ae58ca22fb6145cd793c2 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Mon, 14 Mar 2022 08:49:55 -0700 Subject: [PATCH 05/28] Add GCFrameRegistration API to Mono --- .../Runtime/CompilerServices/RuntimeHelpers.Mono.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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. From 7605f5dac5e6f680bb9e2d4a87ddb131cf1b2572 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 14 Mar 2022 17:47:46 -0500 Subject: [PATCH 06/28] Refactor invoker classes to move code to runtime to avoid stackwalk issues --- .../System.Private.CoreLib.csproj | 7 +- .../Reflection/ConstructorInvoker.CoreCLR.cs | 45 ----------- .../System/Reflection/DynamicMethodInvoker.cs | 59 -------------- .../System/Reflection/Emit/DynamicMethod.cs | 57 ++++++++++--- .../Reflection/Emit/DynamicMethodInvoker.cs | 37 +++++++++ .../Reflection/FieldAccessor.CoreCLR.cs | 80 ------------------- .../Reflection/MethodInvoker.CoreCLR.cs | 41 ---------- .../src/System/Reflection/RtFieldInfo.cs | 77 +++++++++++++++--- .../RuntimeConstructorInfo.CoreCLR.cs | 28 +++++++ .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 31 ++++++- src/coreclr/vm/reflectioninvocation.cpp | 4 +- .../System/Reflection/ConstructorInvoker.cs | 10 +++ .../src/System/Reflection/FieldAccessor.cs | 18 +++++ .../src/System/Reflection/MethodBase.cs | 4 +- .../src/System/Reflection/MethodInvoker.cs | 12 ++- .../Reflection/RuntimeConstructorInfo.cs | 28 +++---- .../System/Reflection/RuntimeMethodInfo.cs | 15 ++-- .../src/System/Reflection/RuntimeFieldInfo.cs | 47 +++++++++-- .../Reflection/RuntimeMethodInfo.Mono.cs | 67 +++++++++++++--- .../src/System/RuntimeType.Mono.cs | 16 ++-- 20 files changed, 375 insertions(+), 308 deletions(-) delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index e70407c1513d46..5da3c74faa4d09 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -1,4 +1,4 @@ - + false @@ -155,13 +155,12 @@ - - + @@ -179,7 +178,6 @@ - @@ -189,7 +187,6 @@ - diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs deleted file mode 100644 index 6eaefc3aa5960b..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.CoreCLR.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Reflection -{ - internal partial class ConstructorInvoker - { - [DebuggerStepThrough] - [DebuggerHidden] - public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) - { - // Todo: add strategy for calling IL Emit-based version - return InvokeNonEmitUnsafe(obj, args, invokeAttr); - } - - [DebuggerStepThrough] - [DebuggerHidden] - private unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - bool rethrow = false; - - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _constructorInfo.Signature, isConstructor: obj is null, out rethrow)!; - } - catch (OutOfMemoryException) - { - throw; // Re-throw for backward compatibility. - } - catch (Exception ex) when (!rethrow) - { - throw new TargetInvocationException(ex); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, _constructorInfo.Signature, isConstructor: obj is null, out _)!; - } - } - } -} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs deleted file mode 100644 index de4feacd626e17..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/DynamicMethodInvoker.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Reflection -{ - internal sealed partial class DynamicMethodInvoker - { - private readonly bool _hasRefs; - - public Signature Signature { get; } - - public DynamicMethodInvoker(Signature signature) - { - Signature = signature; - - RuntimeType[] sigTypes = signature.Arguments; - for (int i = 0; i < sigTypes.Length; i++) - { - if (sigTypes[i].IsByRef) - { - _hasRefs = true; - break; - } - } - } - - public bool HasRefs => _hasRefs; - - public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) - { - // Todo: add strategy for calling IL Emit-based version - return InvokeNonEmitUnsafe(obj, args, invokeAttr); - } - - private unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - bool rethrow = false; - - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); - } - catch (Exception e) when (!rethrow) - { - throw new TargetInvocationException(e); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); - } - } - } -} 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 25b0dec56a418a..771962e25adfdf 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 @@ -24,6 +24,7 @@ public sealed class DynamicMethod : MethodInfo 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 @@ -424,13 +425,24 @@ private DynamicMethodInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - _invoker ??= new DynamicMethodInvoker( - new Signature(m_methodHandle!, m_parameterTypes!, m_returnType, CallingConvention)); + _invoker ??= new DynamicMethodInvoker(this); return _invoker; } } + internal Signature Signature + { + get + { + Debug.Assert(m_methodHandle != null); + Debug.Assert(m_parameterTypes != null); + + _signature ??= new Signature(m_methodHandle, m_parameterTypes, m_returnType, CallingConvention); + return _signature; + } + } + public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture) { if ((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs) @@ -447,7 +459,7 @@ private DynamicMethodInvoker Invoker // verify arguments int argCount = (parameters != null) ? parameters.Length : 0; - if (Invoker.Signature.Arguments.Length != argCount) + if (Signature.Arguments.Length != argCount) throw new TargetParameterCountException(SR.Arg_ParmCnt); object? retValue; @@ -467,42 +479,42 @@ private DynamicMethodInvoker Invoker if (argCount <= MaxStackAllocArgCount) { StackAllocatedByRefs byrefStorage = default; - IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; parametersOut = new Span(ref argStorage._arg0, argCount); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, - Invoker.Signature.Arguments, + Signature.Arguments, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } else { parametersOut = new Span(new object[argCount]); - IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; - GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; + GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, - Invoker.Signature.Arguments, + Signature.Arguments, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } finally { @@ -526,6 +538,27 @@ private DynamicMethodInvoker Invoker return retValue; } + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** arguments, BindingFlags invokeAttr) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); + } + catch (Exception e) when (!rethrow) + { + throw new TargetInvocationException(e); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); + } + } + 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..d31e00add4b341 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs @@ -0,0 +1,37 @@ +// 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 bool _hasRefs; + private readonly DynamicMethod _dynamicMethod; + + public DynamicMethodInvoker(DynamicMethod dynamicMethod) + { + _dynamicMethod = dynamicMethod; + + RuntimeType[] sigTypes = dynamicMethod.Signature.Arguments; + for (int i = 0; i < sigTypes.Length; i++) + { + if (sigTypes[i].IsByRef) + { + _hasRefs = true; + break; + } + } + } + + public bool HasRefs => _hasRefs; + + 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/FieldAccessor.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs deleted file mode 100644 index 149dc7057467c8..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/FieldAccessor.CoreCLR.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Reflection -{ - internal partial class FieldAccessor - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public object? InvokeGetter(object? obj) - { - // Todo: add strategy for calling IL Emit-based version - return InvokeGetterNonEmit(obj); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InvokeSetter(object? obj, object? value) - { - // Todo: add strategy for calling IL Emit-based version - InvokeSetterNonEmit(obj, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal object? InvokeGetterNonEmit(object? obj) - { - RuntimeType? declaringType = _fieldInfo.DeclaringType as RuntimeType; - RuntimeType fieldType = (RuntimeType)_fieldInfo.FieldType; - bool domainInitialized = false; - - if (declaringType == null) - { - return RuntimeFieldHandle.GetValue(_fieldInfo, obj, fieldType, null, ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - object? retVal = RuntimeFieldHandle.GetValue(_fieldInfo, obj, fieldType, declaringType, ref domainInitialized); - declaringType.DomainInitialized = domainInitialized; - return retVal; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void InvokeSetterNonEmit(object? obj, object? value) - { - RuntimeType? declaringType = _fieldInfo.DeclaringType as RuntimeType; - RuntimeType fieldType = (RuntimeType)_fieldInfo.FieldType; - bool domainInitialized = false; - - if (declaringType == null) - { - RuntimeFieldHandle.SetValue( - _fieldInfo, - obj, - value, - fieldType, - _fieldInfo.Attributes, - declaringType: null, - ref domainInitialized); - } - else - { - domainInitialized = declaringType.DomainInitialized; - - RuntimeFieldHandle.SetValue( - _fieldInfo, - obj, - value, - fieldType, - _fieldInfo.Attributes, - declaringType, - ref domainInitialized); - - declaringType.DomainInitialized = domainInitialized; - } - } - - } -} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs deleted file mode 100644 index d3d1918e4e3e51..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodInvoker.CoreCLR.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace System.Reflection -{ - internal partial class MethodInvoker - { - [DebuggerStepThrough] - [DebuggerHidden] - public unsafe object? InvokeUnsafe(object? obj, IntPtr* args, BindingFlags invokeAttr) - { - // Todo: add strategy for calling IL Emit-based version - return InvokeNonEmitUnsafe(obj, args, invokeAttr); - } - - [DebuggerStepThrough] - [DebuggerHidden] - private unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - bool rethrow = false; - - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _methodInfo.Signature, isConstructor: false, out rethrow); - } - catch (Exception e) when (!rethrow) - { - throw new TargetInvocationException(e); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, _methodInfo.Signature, isConstructor: false, out _); - } - } - } -} 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 ab40881b1046d4..955bcbe487a53d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -112,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 = this.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 @@ -129,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; @@ -146,13 +203,13 @@ internal override RuntimeModule GetRuntimeModule() CheckConsistency(obj); - return Invoker.InvokeGetter(obj); + 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) @@ -162,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,11 +240,11 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt bool _ = false; fieldType.CheckValue(ref value, ref _, binder, culture, invokeAttr); - Invoker.InvokeSetter(obj, value); + 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 f50c9daa2a10b3..009f45643ee036 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 @@ -94,6 +94,34 @@ Signature LazyCreateSignature() private RuntimeType ReflectedTypeInternal => m_reflectedTypeCache.GetRuntimeType(); internal BindingFlags BindingFlags => m_bindingFlags; + + + [DebuggerStepThrough] + [DebuggerHidden] + internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, Signature, isConstructor: obj is null, out rethrow)!; + } + catch (OutOfMemoryException) + { + throw; // Re-throw for backward compatibility. + } + catch (Exception ex) when (!rethrow) + { + throw new TargetInvocationException(ex); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)args, Signature, isConstructor: obj is null, out _)!; + } + } #endregion #region Object Overrides diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index 439c9d4bd84f3a..5c835e1218912f 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 @@ -107,6 +107,8 @@ Signature LazyCreateSignature() } } + internal RuntimeType[] Arguments => Signature.Arguments; + internal BindingFlags BindingFlags => m_bindingFlags; internal RuntimeMethodInfo? GetParentDefinition() @@ -308,7 +310,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() @@ -349,14 +351,14 @@ public override MethodImplAttributes GetMethodImplementationFlags() unsafe { StackAllocatedByRefs byrefStorage = default; - IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; Span parametersOut = new(ref argStorage._arg0, 1); Span parameters = new(ref parameter, 1); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref _, parameters, ArgumentTypes, @@ -364,12 +366,33 @@ public override MethodImplAttributes GetMethodImplementationFlags() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } return retValue; } + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** arguments, BindingFlags invokeAttr) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); + } + catch (Exception e) when (!rethrow) + { + throw new TargetInvocationException(e); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); + } + } + #endregion #region MethodInfo Overrides diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 302f4763ae6b76..b85eeac3334275 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -392,10 +392,10 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, OBJECTREF** objs, int arg for (DWORD i=0; i<(DWORD)argCnt; i++) { - if (!objs[i]) + if (!*objs[i]) COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); - MethodTable* pMT = (*objs[i])->GetMethodTable(); + MethodTable* pMT = *objs[i]->GetMethodTable(); CorElementType oType = TypeHandle(pMT).GetVerifierCorElementType(); if (!InvokeUtil::IsPrimitiveType(oType) || !InvokeUtil::CanPrimitiveWiden(ELEMENT_TYPE_I4,oType)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index 28c1c619fede8d..4eb9b4ba603684 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -1,6 +1,8 @@ // 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 @@ -25,5 +27,13 @@ public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) } public bool HasRefs => _hasRefs; + + [DebuggerStepThrough] + [DebuggerHidden] + public unsafe object? InvokeUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return _constructorInfo.InvokeNonEmitUnsafe(obj, args, 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 index 32118ef0047ef0..530c5e767f7745 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/FieldAccessor.cs @@ -1,6 +1,8 @@ // 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 @@ -12,5 +14,21 @@ 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/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index df5a3277d5c2e2..f7b064043101ee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -142,7 +142,7 @@ private protected void ValidateInvokeTarget(object? target) private protected unsafe void CheckArguments( ref Span parametersOut, - IntPtr* unsafeParameters, + IntPtr** unsafeByrefParameters, ref bool copyBack, ReadOnlySpan parameters, RuntimeType[] sigTypes, @@ -179,7 +179,7 @@ BindingFlags invokeAttr // considered user-visible to threads which may still be holding on to returned instances. parametersOut[i] = arg; - unsafeParameters[i] = (IntPtr)Unsafe.AsPointer(ref parametersOut[i]); + unsafeByrefParameters[i] = (IntPtr*)Unsafe.AsPointer(ref parametersOut[i]); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index 14774347be3178..0f29a7662141cf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -1,6 +1,8 @@ // 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 @@ -13,7 +15,7 @@ public MethodInvoker(RuntimeMethodInfo methodInfo) { _methodInfo = methodInfo; - RuntimeType[] sigTypes = methodInfo.Signature.Arguments; + RuntimeType[] sigTypes = methodInfo.ArgumentTypes; for (int i = 0; i < sigTypes.Length; i++) { if (sigTypes[i].IsByRef) @@ -25,5 +27,13 @@ public MethodInvoker(RuntimeMethodInfo methodInfo) } public bool HasRefs => _hasRefs; + + [DebuggerStepThrough] + [DebuggerHidden] + public unsafe object? InvokeUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + { + // Todo: add strategy for calling IL Emit-based version + return _methodInfo.InvokeNonEmitUnsafe(obj, args, 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 92f27a73977593..dd3d9bc40533b6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -139,13 +139,13 @@ internal void ThrowNoInvokeException() if (argCount <= MaxStackAllocArgCount) { StackAllocatedByRefs byrefStorage = default; - IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; parametersOut = new Span(ref argStorage._arg0, argCount); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, ArgumentTypes, @@ -153,20 +153,20 @@ internal void ThrowNoInvokeException() culture, invokeAttr); - Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } else { parametersOut = new Span(new object[argCount]); - IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; - GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; + GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, ArgumentTypes, @@ -174,7 +174,7 @@ internal void ThrowNoInvokeException() culture, invokeAttr); - Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } finally { @@ -232,13 +232,13 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] if (argCount <= MaxStackAllocArgCount) { StackAllocatedByRefs byrefStorage = default; - IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; parametersOut = new Span(ref argStorage._arg0, argCount); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, ArgumentTypes, @@ -246,20 +246,20 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj: null, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj: null, unsafeByrefParameters, invokeAttr); } else { parametersOut = new Span(new object[argCount]); - IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; - GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; + GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, ArgumentTypes, @@ -267,7 +267,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj: null, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj: null, unsafeByrefParameters, invokeAttr); } finally { 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 ad1780b6a7c91f..1c954715239f5c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; -using static System.Runtime.CompilerServices.RuntimeHelpers; namespace System.Reflection { @@ -137,13 +136,13 @@ private void ThrowNoInvokeException() if (argCount <= MaxStackAllocArgCount) { StackAllocatedByRefs byrefStorage = default; - IntPtr* unsafeParameters = (IntPtr*)&byrefStorage; + IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; parametersOut = new Span(ref argStorage._arg0, argCount); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, ArgumentTypes, @@ -151,20 +150,20 @@ private void ThrowNoInvokeException() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } else { parametersOut = new Span(new object[argCount]); - IntPtr* unsafeParameters = stackalloc IntPtr[argCount]; - GCFrameRegistration reg = new(unsafeParameters, (uint)argCount, areByRefs: true); + IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; + GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( ref parametersOut, - unsafeParameters, + unsafeByrefParameters, ref copyBack, parameters, ArgumentTypes, @@ -172,7 +171,7 @@ private void ThrowNoInvokeException() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); } finally { 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 38be1cca5688db..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() @@ -251,7 +281,8 @@ public override void SetValue(object? obj, object? val, BindingFlags 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 f433c88bd6da88..7840a4cd31c549 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 @@ -172,7 +172,7 @@ private MethodInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - invoker ??= new MethodInvoker(InvokeNonEmit, ArgumentTypes); + invoker ??= new MethodInvoker(this); return invoker; } } @@ -354,7 +354,7 @@ internal override int GetParametersCount() return MonoMethodInfo.GetParametersInfo(mhandle, this).Length; } - private RuntimeType[] ArgumentTypes + internal RuntimeType[] ArgumentTypes { get { @@ -382,19 +382,47 @@ private RuntimeType[] ArgumentTypes * Exceptions thrown by the called method propagate normally. */ [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal extern object? InternalInvoke(object? obj, in Span parameters, out Exception? exc); + internal extern object? InternalInvoke(object? obj, in Span parameters, out Exception? exc); + + internal object? InternalInvoke(object? obj, Span parameters, out Exception? exc) + { + unsafe + { + // Convert to Span. + int parametersLength = parameters.Length; + IntPtr* stackStorage = stackalloc IntPtr[parametersLength]; + for (int i = 0; i < parametersLength; i++) + { + stackStorage[i] = (IntPtr)Unsafe.AsPointer(ref parameters[i]); + } + + Span refParameters = new(stackStorage, parametersLength); + return InternalInvoke(obj, refParameters, out exc); + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object? InvokeNonEmit(object? obj, Span arguments, BindingFlags invokeAttr) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** byrefParameters, BindingFlags invokeAttr) { Exception? exc; object? o; + // Convert the byref array to a ref array. + // The native code could also be adapted to take byref pointers to avoid this conversion. + int parametersLength = ArgumentTypes.Length; + IntPtr* stackStorage = stackalloc IntPtr[parametersLength]; + for (int i = 0; i < parametersLength; i++) + { + stackStorage[i] = *byrefParameters[i]; + } + + Span refParameters = new(stackStorage, parametersLength); + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { - o = InternalInvoke(obj, arguments, out exc); + o = InternalInvoke(obj, refParameters, out exc); } catch (Mono.NullByRefReturnException) { @@ -413,7 +441,7 @@ private RuntimeType[] ArgumentTypes { try { - o = InternalInvoke(obj, arguments, out exc); + o = InternalInvoke(obj, refParameters, out exc); } catch (Mono.NullByRefReturnException) { @@ -786,7 +814,7 @@ internal ConstructorInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - invoker ??= new ConstructorInvoker(InvokeNonEmit, ArgumentTypes); + invoker ??= new ConstructorInvoker(this); return invoker; } } @@ -825,7 +853,7 @@ internal override int GetParametersCount() return pi == null ? 0 : pi.Length; } - private RuntimeType[] ArgumentTypes + internal RuntimeType[] ArgumentTypes { get { @@ -857,18 +885,33 @@ private static void InvokeClassConstructor() * to match the types of the method signature. */ [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); + internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); - private object? InvokeNonEmit(object? obj, Span parameters, BindingFlags invokeAttr) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** byrefParameters, BindingFlags invokeAttr) { Exception exc; object? o; + Span refParameters = default; + + if (byrefParameters != null) + { + // Convert the byref array to a ref array. + // The native code could also be adapted to take byref pointers to avoid this conversion. + int parametersLength = ArgumentTypes.Length; + IntPtr* refParametersStorage = stackalloc IntPtr[parametersLength]; + for (int i = 0; i < parametersLength; i++) + { + refParametersStorage[i] = *byrefParameters[i]; + } + + refParameters = new(refParametersStorage, parametersLength); + } if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, refParameters, out exc); } catch (MethodAccessException) { @@ -885,7 +928,7 @@ private static void InvokeClassConstructor() } else { - o = InternalInvoke(obj, parameters, out exc); + o = InternalInvoke(obj, refParameters, out exc); } if (exc != null) 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 345a5bee94c469..97eeedcaf5b8d3 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1656,10 +1656,13 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) throw new MissingMethodException("Cannot create an abstract class '{0}'.", FullName); } - return ctor.Invoker.Invoke( - obj: null, - Span.Empty, - wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); + unsafe + { + return ctor.Invoker.InvokeUnsafe( + obj: null, + args: default, + wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); + } } internal void CheckValue( @@ -2005,7 +2008,10 @@ internal static object CreateInstanceForAnotherGenericParameter( if (ctor is null || !ctor.IsPublic) throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, gt!)); - return ctor.Invoker.Invoke(obj: null, Span.Empty, BindingFlags.Default)!; + unsafe + { + return ctor.Invoker.InvokeUnsafe(obj: null, args: default, BindingFlags.Default)!; + } } [MethodImplAttribute(MethodImplOptions.InternalCall)] From 54c79709c77384156169447cbce43061e6178d30 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 14 Mar 2022 19:32:00 -0500 Subject: [PATCH 07/28] Misc changes for review prep --- .../Reflection/Emit/DynamicMethodInvoker.cs | 5 +- .../src/System/Reflection/RtFieldInfo.cs | 2 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 9 +- src/coreclr/vm/invokeutil.cpp | 2 + src/coreclr/vm/reflectioninvocation.cpp | 2 +- .../System/Reflection/ConstructorInvoker.cs | 5 +- .../src/System/Reflection/MethodInvoker.cs | 5 +- .../System/Reflection/RuntimeMethodInfo.cs | 1 + .../tests/System.Runtime.Tests.csproj | 3 +- .../System/Reflection/InvokeRefReturn.cs | 125 ------------------ 10 files changed, 14 insertions(+), 145 deletions(-) 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 index d31e00add4b341..741995bca99b5e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs @@ -8,7 +8,6 @@ namespace System.Reflection.Emit { internal sealed partial class DynamicMethodInvoker { - private readonly bool _hasRefs; private readonly DynamicMethod _dynamicMethod; public DynamicMethodInvoker(DynamicMethod dynamicMethod) @@ -20,13 +19,13 @@ public DynamicMethodInvoker(DynamicMethod dynamicMethod) { if (sigTypes[i].IsByRef) { - _hasRefs = true; + HasRefs = true; break; } } } - public bool HasRefs => _hasRefs; + public bool HasRefs { get; } public unsafe object? InvokeUnsafe(object? obj, IntPtr** args, BindingFlags 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 955bcbe487a53d..037aeb3c2e7f3e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -116,7 +116,7 @@ internal override bool CacheEquals(object? o) [DebuggerHidden] internal object? GetValueNonEmit(object? obj) { - RuntimeType? declaringType = this.DeclaringType as RuntimeType; + RuntimeType? declaringType = DeclaringType as RuntimeType; RuntimeType fieldType = (RuntimeType)FieldType; bool domainInitialized = false; 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 5c835e1218912f..2541bdffcb2b4e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -48,11 +48,8 @@ private MethodInvoker Invoker [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - unsafe // todo:remove - { - m_invoker ??= new MethodInvoker(this); - return m_invoker; - } + m_invoker ??= new MethodInvoker(this); + return m_invoker; } } #endregion @@ -107,8 +104,6 @@ Signature LazyCreateSignature() } } - internal RuntimeType[] Arguments => Signature.Arguments; - internal BindingFlags BindingFlags => m_bindingFlags; internal RuntimeMethodInfo? GetParentDefinition() diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index f43ce585032dd5..b9779762d40cd7 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -1236,3 +1236,5 @@ OBJECTREF InvokeUtil::GetFieldValue(FieldDesc* pField, TypeHandle fieldType, OBJ return obj; } + + diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index b85eeac3334275..8ec86a88411b12 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -395,7 +395,7 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, OBJECTREF** objs, int arg if (!*objs[i]) COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); - MethodTable* pMT = *objs[i]->GetMethodTable(); + MethodTable* pMT = (*objs[i])->GetMethodTable(); CorElementType oType = TypeHandle(pMT).GetVerifierCorElementType(); if (!InvokeUtil::IsPrimitiveType(oType) || !InvokeUtil::CanPrimitiveWiden(ELEMENT_TYPE_I4,oType)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index 4eb9b4ba603684..2d1bba2551e64a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -7,7 +7,6 @@ namespace System.Reflection { internal sealed partial class ConstructorInvoker { - private readonly bool _hasRefs; private readonly RuntimeConstructorInfo _constructorInfo; public InvocationFlags _invocationFlags; @@ -20,13 +19,13 @@ public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) { if (argTypes[i].IsByRef) { - _hasRefs = true; + HasRefs = true; break; } } } - public bool HasRefs => _hasRefs; + public bool HasRefs { get; } [DebuggerStepThrough] [DebuggerHidden] diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index 0f29a7662141cf..5cc232836c9ea6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -9,7 +9,6 @@ internal sealed partial class MethodInvoker { internal InvocationFlags _invocationFlags; private readonly RuntimeMethodInfo _methodInfo; - private readonly bool _hasRefs; public MethodInvoker(RuntimeMethodInfo methodInfo) { @@ -20,13 +19,13 @@ public MethodInvoker(RuntimeMethodInfo methodInfo) { if (sigTypes[i].IsByRef) { - _hasRefs = true; + HasRefs = true; break; } } } - public bool HasRefs => _hasRefs; + public bool HasRefs { get; } [DebuggerStepThrough] [DebuggerHidden] 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 1c954715239f5c..bad7c6c729f835 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 { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index b0e18106f469f4..42939fce748365 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -4,8 +4,7 @@ $(NoWarn),1718,SYSLIB0013 true true - - $(NetCoreAppCurrent)-windows + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser disable $(Features.Replace('nullablePublicOnly', '') diff --git a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs index c070287bf12e44..885e17992c1768 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.CompilerServices; using Xunit; @@ -11,77 +10,6 @@ namespace System.Reflection.Tests { public class InvokeRefReturnNetcoreTests { - [Fact] - public void Test_Class() - { - MethodInfo mi = typeof(TestClass).GetMethod(nameof(TestClass.Initialize), BindingFlags.Instance | BindingFlags.Public); - - int intField = 42; - int intProperty = 43; - string stringProperty = "Hello"; - - var obj = new TestClass(); - - Stopwatch sw = new Stopwatch(); - sw.Start(); - for (long l = 0; l < 10_000_000; l++) - { - object ret = mi.Invoke(obj, BindingFlags.Default, null, new object[] { intField, intProperty, stringProperty }, null); //1977 - //string str = (string)ret; - //object ret = mi.Invoke(obj, BindingFlags.Default, null, new object[] { intField, intProperty, stringProperty }, null); //1977 - - //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField, intProperty, stringProperty }, null); //804 456 (new) - - //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField }, null); //631 - //object ret = mi.Invoke(obj, new object[] { intField, intProperty, stringProperty }); - //object ret = mi.InvokeDirect(obj, args); - - //Assert.Equal(42, obj.MyIntField); - //Assert.Equal(43, obj.MyIntProperty); - //Assert.Equal("Hello", obj.MyStringProperty); - } - sw.Stop(); - - long ticks = sw.ElapsedMilliseconds; - throw new Exception("TIME:" + ticks); - } - - [Fact] - public void Test_Class2() - { - MethodInfo mi = typeof(TestClass).GetMethod(nameof(TestClass.Initialize), BindingFlags.Instance | BindingFlags.Public); - - int intField = 42; - int intProperty = 43; - string stringProperty = "Hello"; - - var obj = new TestClass(); - - Stopwatch sw = new Stopwatch(); - sw.Start(); - for (long l = 0; l < 10_000_000; l++) - { - //obj = new TestClass(); - object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField, intProperty, stringProperty }, null); //1977 - - //object ret = mi.Invoke(obj, BindingFlags.Default, null, new object[] { intField, intProperty, stringProperty }, null); //1977 - - //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField, intProperty, stringProperty }, null); //804 456 (new) - - //object ret = mi.Invoke(obj, BindingFlags.SuppressChangeType, null, new object[] { intField }, null); //631 - //object ret = mi.Invoke(obj, new object[] { intField, intProperty, stringProperty }); - //object ret = mi.InvokeDirect(obj, args); - - //Assert.Equal(42, obj.MyIntField); - //Assert.Equal(43, obj.MyIntProperty); - //Assert.Equal("Hello", obj.MyStringProperty); - } - sw.Stop(); - - long ticks = sw.ElapsedMilliseconds; - throw new Exception("TIME:" + ticks); - } - [Theory] [MemberData(nameof(RefReturnInvokeTestData))] public static void TestRefReturnPropertyGetValue(T value) @@ -250,57 +178,4 @@ private sealed unsafe class TestClassIntPointer public ref int* NullRefReturningProp => ref *(int**)null; } } - internal sealed class TestClass - { - public int MyIntField; - public int MyIntProperty { get; set; } - public string MyStringProperty { get; set; } - - // public void Initialize(out int myIntField, int myIntProperty, string myStringProperty) - public void Initialize(int myIntField, int myIntProperty, string myStringProperty) - { - MyIntField = myIntField; - MyIntProperty = myIntProperty; - MyStringProperty = myStringProperty; - //return "Hello"; - } - - - public void Initialize1(int i) - { - string s = "SDF"; - s += "sdg"; - } - - public void Initialize5(int myIntField, int myIntProperty, string myStringProperty, int p4, long p5) - { - MyIntField = myIntField; - MyIntProperty = myIntProperty; - MyStringProperty = myStringProperty; - p5 = 100; - } - - public string InitializeAndReturn(int myIntField, int myIntProperty, string myStringProperty) - { - MyIntField = myIntField; - MyIntProperty = myIntProperty; - MyStringProperty = myStringProperty; - return MyStringProperty; - } - - public string InitializeAndThrow(int myIntField, int myIntProperty, string myStringProperty) - { - throw new Exception("Hello"); - } - - public void AddToMyIntField(int value) - { - MyIntField += value; - } - - public string ConcatToMyStringProperty(string value) - { - return MyStringProperty + value; - } - } } From 99d750d81aefd212fd518296a4378376c179ed9d Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 23 Mar 2022 19:56:08 -0500 Subject: [PATCH 08/28] Initial support for passing byrefs to raw value types --- .../System/Reflection/Emit/DynamicMethod.cs | 55 +- .../Reflection/Emit/DynamicMethodInvoker.cs | 14 +- .../src/System/Reflection/RtFieldInfo.cs | 7 +- .../RuntimeConstructorInfo.CoreCLR.cs | 2 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 38 +- .../src/System/RuntimeType.CoreCLR.cs | 161 +++--- src/coreclr/vm/invokeutil.cpp | 205 +------ src/coreclr/vm/invokeutil.h | 4 +- src/coreclr/vm/reflectioninvocation.cpp | 51 +- src/coreclr/vm/runtimehandles.h | 2 +- .../System.Private.CoreLib.Shared.projitems | 1 + .../System/Reflection/ConstructorInvoker.cs | 16 +- .../src/System/Reflection/InvokeUtils.cs | 100 ++++ .../src/System/Reflection/MethodBase.cs | 54 +- .../src/System/Reflection/MethodInvoker.cs | 16 +- .../Reflection/RuntimeConstructorInfo.cs | 110 ++-- .../System/Reflection/RuntimeMethodInfo.cs | 66 ++- .../System.Reflection/tests/FieldInfoTests.cs | 12 + .../tests/MethodInfoTests.cs | 500 ++++++++++++------ .../Reflection/RuntimeMethodInfo.Mono.cs | 59 +-- .../src/System/RuntimeType.Mono.cs | 3 +- .../src/System/RuntimeTypeHandle.cs | 2 + 22 files changed, 803 insertions(+), 675 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs 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 771962e25adfdf..5fb8f4497b49f8 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 @@ -473,48 +473,60 @@ internal Signature Signature else { Debug.Assert(parameters != null); - Span parametersOut; - bool copyBack = Invoker.HasRefs; + Span copyOfParameters; + Span shouldCopyBackParameters; + Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { - StackAllocatedByRefs byrefStorage = default; - IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; - parametersOut = new Span(ref argStorage._arg0, argCount); + copyOfParameters = new Span(ref argStorage._arg0, argCount); + shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, Signature.Arguments, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void**)&byrefStorage, invokeAttr); } else { - parametersOut = new Span(new object[argCount]); - IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; - GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); + object[] objHolder = new object[argCount]; + copyOfParameters = new Span(objHolder, 0, argCount); + + // We don't check a max stack size since we are invoking a method which + // natually requires a stack size that is dependent on the arg count\size. + IntPtr* byrefStorage = stackalloc IntPtr[argCount]; + byrefParameters = new Span(byrefStorage, argCount); + + bool* boolHolder = stackalloc bool[argCount]; + shouldCopyBackParameters = new Span(boolHolder, argCount); + + GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, Signature.Arguments, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)byrefStorage, invokeAttr); } finally { @@ -522,13 +534,12 @@ internal Signature Signature } } - if (copyBack) + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + for (int i = 0; i < argCount; i++) { - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int i = 0; i < argCount; i++) + if (shouldCopyBackParameters[i]) { - parameters[i] = parametersOut[i]; + parameters[i] = copyOfParameters[i]; } } } @@ -538,7 +549,7 @@ internal Signature Signature return retValue; } - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** arguments, BindingFlags invokeAttr) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { 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 index 741995bca99b5e..9e5a5e17be4e80 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethodInvoker.cs @@ -13,21 +13,9 @@ internal sealed partial class DynamicMethodInvoker public DynamicMethodInvoker(DynamicMethod dynamicMethod) { _dynamicMethod = dynamicMethod; - - RuntimeType[] sigTypes = dynamicMethod.Signature.Arguments; - for (int i = 0; i < sigTypes.Length; i++) - { - if (sigTypes[i].IsByRef) - { - HasRefs = true; - break; - } - } } - public bool HasRefs { get; } - - public unsafe object? InvokeUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + 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 037aeb3c2e7f3e..bb90a27e32f9bd 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -237,8 +237,11 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); RuntimeType fieldType = (RuntimeType)FieldType; - bool _ = false; - fieldType.CheckValue(ref value, ref _, binder, culture, invokeAttr); + if (!ReferenceEquals(value?.GetType(), fieldType)) + { + bool _b = false; + fieldType.CheckValue(ref value, ref _b, binder, culture, invokeAttr); + } Invoker.SetValue(obj, value); } 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 009f45643ee036..7e78902fad3553 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 @@ -98,7 +98,7 @@ Signature LazyCreateSignature() [DebuggerStepThrough] [DebuggerHidden] - internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSuppor, BindingFlags invokeAttr) { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { 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 2541bdffcb2b4e..1d0c1abf9ace95 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 @@ -340,35 +340,57 @@ public override MethodImplAttributes GetMethodImplementationFlags() throw new TargetParameterCountException(SR.Arg_ParmCnt); } - bool _ = false; object? retValue; unsafe { - StackAllocatedByRefs byrefStorage = default; - IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; - Span parametersOut = new(ref argStorage._arg0, 1); + Span copyOfParameters = new(ref argStorage._arg0, 1); Span parameters = new(ref parameter, 1); + Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); + + StackAllocatedByRefs byrefStorage = default; + Span unsafeByrefParameters = new Span(&byrefStorage, 1); CheckArguments( - ref parametersOut, + copyOfParameters, unsafeByrefParameters, - ref _, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void**)&byrefStorage, copyOfParameters, invokeAttr); } return retValue; } - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** arguments, BindingFlags invokeAttr) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { + if ((invokeAttr & BindingFlags.SuppressChangeType) == 0) + { + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) + { + bool rethrow = false; + + try + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); + } + catch (Exception e) when (!rethrow) + { + throw new TargetInvocationException(e); + } + } + else + { + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); + } + } + if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { bool rethrow = false; 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 c0e925e79cf8a9..f0194ce0f9f92a 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.")] @@ -3474,9 +3474,8 @@ public override Type MakeArrayType(int rank) private static extern object AllocateValueType(RuntimeType type, object? value, bool fForceTypeChange); /// - /// Verify and optionally change it for special cases. + /// Verify and optionally convert the value for special cases. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void CheckValue( ref object? value, ref bool copyBack, @@ -3484,16 +3483,16 @@ internal void CheckValue( CultureInfo? culture, BindingFlags invokeAttr) { - // This method is used by invocation in reflection to check whether a value can be assigned to type. + Debug.Assert(!ReferenceEquals(value?.GetType(), this)); + + // Check whether a value can be assigned to type. 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 (!ReferenceEquals(type, this) && RuntimeTypeHandle.IsValueType(this)) + if (RuntimeTypeHandle.IsValueType(this)) { // Must be an equivalent type, re-box to the target type value = AllocateValueType(this, value, fForceTypeChange: true); @@ -3503,39 +3502,79 @@ internal void CheckValue( return; } - TryChangeType(ref value, ref copyBack, binder, culture, invokeAttr); + if (TryChangeType(ref value, ref copyBack)) + { + return; + } + + Debug.Assert(value != null); + + if ((invokeAttr & BindingFlags.ExactBinding) == 0) + { + // Use the binder + if (binder != null && binder != DefaultBinder) + { + value = binder.ChangeType(value, this, culture); + if (IsInstanceOfType(value)) + { + copyBack = true; + return; + } + + if (TryChangeType(ref value, ref copyBack)) + { + return; + } + } + } + + Debug.Assert(value != null); + throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), this)); } - private void TryChangeType( + private bool TryChangeType( ref object? value, - ref bool copyBack, - Binder? binder, - CultureInfo? culture, - BindingFlags invokeAttr) + ref bool copyBack) { + RuntimeType sigElementType = this; + // If this is a ByRef get the element type and check if it's compatible bool isByRef = IsByRef; if (isByRef) { - RuntimeType elementType = RuntimeTypeHandle.GetElementType(this); - if (elementType.IsInstanceOfType(value) || value == null) + sigElementType = RuntimeTypeHandle.GetElementType(this); + if (sigElementType.IsInstanceOfType(value)) { // Need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type - value = AllocateValueType(elementType, value, fForceTypeChange: false); copyBack = true; - return; + value = AllocateValueType(sigElementType, value, fForceTypeChange: false); + return true; } } - else if (value == null) - { - return; - } else if (this == s_typedRef) { // Everything works for a typedref - return; + return true; + } + + if (value == null) + { + if (RuntimeTypeHandle.IsValueType(sigElementType)) + { + // Need to create an instance of the value type if null was provided. + // For a byref-like parameter pass null to the runtime which will create a default value. + if (!RuntimeTypeHandle.IsByRefLike(sigElementType)) + { + value = AllocateValueType(sigElementType, value: null, fForceTypeChange: false); + // Don't copy the value back + } + } + + return true; } + Debug.Assert(value != null); + // Check the strange ones courtesy of reflection: // - implicit cast between primitives // - enum treated as underlying type @@ -3544,79 +3583,45 @@ private void TryChangeType( if (needsSpecialCast) { Pointer? pointer = value as Pointer; - RuntimeType valueType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); + RuntimeType srcType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); - if (CanValueSpecialCast(valueType, this)) + if (CanValueSpecialCast(srcType, this)) { if (pointer != null) { value = pointer.GetPointerValue(); copyBack = true; - return; + return true; } else { - return; + if (!isByRef) + { + CorElementType srcElementType = GetUnderlyingType(srcType); + CorElementType dstElementType = GetUnderlyingType(this); + if (dstElementType != srcElementType) + { + value = InvokeUtils.ConvertOrWiden(srcType, srcElementType, value, this, dstElementType); + // Do not copy back. + } + } } } - } - if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) - { - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), this)); + return true; } - // Use the binder - if (binder != null && binder != Type.DefaultBinder) - { - copyBack = true; - - value = binder.ChangeType(value, this, culture); - if (IsInstanceOfType(value)) - { - return; - } - - // if this is a ByRef get the element type and check if it's compatible - if (IsByRef) - { - RuntimeType elementType = RuntimeTypeHandle.GetElementType(this); - if (elementType.IsInstanceOfType(value) || value == null) - { - value = AllocateValueType(elementType, value, fForceTypeChange: false); - return; - } - } - else if (value == null) - { - return; - } - if (needsSpecialCast) - { - RuntimeType valueType; - Pointer? pointer = value as Pointer; - if (pointer != null) - { - valueType = pointer.GetPointerType(); - } - else - { - valueType = (RuntimeType)value.GetType(); - } - - if (CanValueSpecialCast(valueType, this)) - { - if (pointer != null) - { - value = pointer.GetPointerValue(); - } + return false; + } - return; - } - } + 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/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index b9779762d40cd7..a8c5d8d8f698a2 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,201 +124,6 @@ void *InvokeUtil::GetIntPtrValue(OBJECTREF pObj) { RETURN *(void **)((pObj)->UnBox()); } -void InvokeUtil::CopyArg(TypeHandle th, OBJECTREF *pObjUNSAFE, 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(); - - 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_I2: - case ELEMENT_TYPE_U2: - case ELEMENT_TYPE_CHAR: - 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:) - { - // 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; - } - 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; - } - 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)) - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); - } - break; - } - - 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) - *(PVOID *)pArgDst = 0; - else - *(PVOID *)pArgDst = OBJECTREFToObject(rObj); - 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); - } - - 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")); - } - break; - } - - case ELEMENT_TYPE_VOID: - default: - _ASSERTE(!"Unknown Type"); - COMPlusThrow(kNotSupportedException); - } -} - // CreatePrimitiveValue // This routine will validate the object and then place the value into // the destination @@ -342,8 +147,11 @@ 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 { @@ -613,7 +421,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..2a55db865ed98c 100644 --- a/src/coreclr/vm/invokeutil.h +++ b/src/coreclr/vm/invokeutil.h @@ -51,8 +51,6 @@ class InvokeUtil { public: - static void CopyArg(TypeHandle th, OBJECTREF *obj, 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 // value is wrapped in one of the Value classes. @@ -113,7 +111,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) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 8ec86a88411b12..e91bb3ee80c1a2 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -369,7 +369,7 @@ struct ByRefToNullable { } }; -static OBJECTREF InvokeArrayConstructor(TypeHandle th, OBJECTREF** objs, int argCnt) +static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) { CONTRACTL { THROWS; @@ -392,18 +392,10 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, OBJECTREF** objs, int arg for (DWORD i=0; i<(DWORD)argCnt; i++) { - if (!*objs[i]) + if (!args[i]) COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); - MethodTable* pMT = (*objs[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[i], &value); - memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); + memcpyNoGCRefs(indexes + i, *(PVOID **)args[i], sizeof(INT32)); } return AllocateArrayEx(th, indexes, argCnt); @@ -538,7 +530,7 @@ class ArgIteratorForMethodInvoke : public ArgIteratorTemplateNumFixedArgs()); goto Done; } @@ -700,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(); @@ -741,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(); @@ -769,6 +760,8 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, bool needsStackCopy = false; + MethodTable * pMT = th.GetMethodTable(); + // 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. @@ -778,6 +771,11 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, structSize = th.GetSize(); needsStackCopy = true; } + else if (pMT->IsByRefLike()) { + // A byref-like type can't be boxed but can be passed as a null reference in which + // case we return a zero-filled ref struct and not do call the default ctor. + needsStackCopy = true; + } #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE else if (argit.IsArgPassedByRef()) { @@ -787,16 +785,11 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - if(needsStackCopy) + if (needsStackCopy) { - MethodTable * pMT = th.GetMethodTable(); _ASSERTE(pMT && pMT->IsValueType()); - PVOID pArgDst = argDest.GetDestinationAddress(); - PVOID pStackCopy = _alloca(structSize); - *(PVOID *)pArgDst = pStackCopy; - pArgDst = pStackCopy; if (!nullableType.IsNull()) { @@ -813,7 +806,15 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, argDest = ArgDestination(pStackCopy, 0, NULL); } - InvokeUtil::CopyArg(th, objs[i], &argDest); + if (pMT->IsByRefLike()) + { + InitValueClassArg(&argDest, pMT); + } + else + { + PVOID pArgDst = argDest.GetDestinationAddress(); + *(PVOID *)pArgDst = *(PVOID **)args[i]; + } } ENDFORBIDGC(); @@ -889,7 +890,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, while (byRefToNullables != NULL) { OBJECTREF obj = Nullable::Box(byRefToNullables->data, byRefToNullables->type.GetMethodTable()); - SetObjectReference(objs[byRefToNullables->argNum], obj); + SetObjectReference((OBJECTREF*)args[byRefToNullables->argNum], obj); byRefToNullables = byRefToNullables->next; } @@ -1945,7 +1946,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 e1bf52fcdc947c..3e53d766ca882b 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, OBJECTREF** objs, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); + static FCDECL5(Object*, InvokeMethod, Object *target, PVOID* args, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); struct StreamingContextData { Object * additionalContext; // additionalContex was changed from OBJECTREF to Object to avoid having a 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 070ad232c22837..1c1d0a2c570055 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 @@ -614,6 +614,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs index 2d1bba2551e64a..48fe08e491026a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs @@ -13,26 +13,14 @@ internal sealed partial class ConstructorInvoker public ConstructorInvoker(RuntimeConstructorInfo constructorInfo) { _constructorInfo = constructorInfo; - - RuntimeType[] argTypes = constructorInfo.ArgumentTypes; - for (int i = 0; i < argTypes.Length; i++) - { - if (argTypes[i].IsByRef) - { - HasRefs = true; - break; - } - } } - public bool HasRefs { get; } - [DebuggerStepThrough] [DebuggerHidden] - public unsafe object? InvokeUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + 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, invokeAttr); + return _constructorInfo.InvokeNonEmitUnsafe(obj, args, argsForTemporaryMonoSupport, invokeAttr); } } } 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..9d16e9255603cf --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -0,0 +1,100 @@ +// 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 CoreAOT method ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(). + public static object ConvertOrWiden(Type srcType, CorElementType srcElementType, object srcObject, Type dstType, CorElementType dstElementType) + { + object dstObject; + 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; + } + } +} 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 f7b064043101ee..a581cc745cbc84 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ObjectiveC; using System.Text; namespace System.Reflection @@ -141,9 +142,9 @@ private protected void ValidateInvokeTarget(object? target) } private protected unsafe void CheckArguments( - ref Span parametersOut, - IntPtr** unsafeByrefParameters, - ref bool copyBack, + Span copyOfParameters, + Span byrefParameters, + Span shouldCopyBack, ReadOnlySpan parameters, RuntimeType[] sigTypes, Binder? binder, @@ -156,6 +157,7 @@ BindingFlags invokeAttr ParameterInfo[]? p = null; for (int i = 0; i < parameters.Length; i++) { + bool copyBackArg = false; object? arg = parameters[i]; if (arg == Type.Missing) { @@ -165,11 +167,15 @@ BindingFlags invokeAttr throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); } - copyBack = true; - arg = p[i].DefaultValue!; + copyBackArg = true; + arg = p[i].DefaultValue; } - sigTypes[i].CheckValue(ref arg, ref copyBack, binder, culture, invokeAttr); + RuntimeType sigType = sigTypes[i]; + if (!ReferenceEquals(arg?.GetType(), sigType)) + { + sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); + } // 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!) @@ -177,9 +183,33 @@ BindingFlags invokeAttr // 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. - parametersOut[i] = arg; + shouldCopyBack[i] = copyBackArg; + copyOfParameters[i] = arg; + + if (RuntimeTypeHandle.IsValueType(sigType)) + { + if (arg is null) + { + Debug.Assert(sigType.IsByRefLike); + Debug.Assert(!copyBackArg); + + p ??= GetParametersNoCopy(); + if (p[i].IsOut) + { + throw new NotSupportedException(SR.NotSupported_ByRefLike); + } - unsafeByrefParameters[i] = (IntPtr*)Unsafe.AsPointer(ref parametersOut[i]); + byrefParameters[i] = (IntPtr)Unsafe.AsPointer(ref copyOfParameters[i]); + } + else + { + byrefParameters[i] = (IntPtr)Unsafe.AsPointer(ref copyOfParameters[i]!.GetRawData()); + } + } + else + { + byrefParameters[i] = (IntPtr)Unsafe.AsPointer(ref copyOfParameters[i]); + } } } @@ -190,6 +220,7 @@ BindingFlags invokeAttr // 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 object? _arg0; @@ -197,10 +228,17 @@ private protected ref struct StackAllocedArguments 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; diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs index 5cc232836c9ea6..65c4e2bac2e746 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodInvoker.cs @@ -13,26 +13,14 @@ internal sealed partial class MethodInvoker public MethodInvoker(RuntimeMethodInfo methodInfo) { _methodInfo = methodInfo; - - RuntimeType[] sigTypes = methodInfo.ArgumentTypes; - for (int i = 0; i < sigTypes.Length; i++) - { - if (sigTypes[i].IsByRef) - { - HasRefs = true; - break; - } - } } - public bool HasRefs { get; } - [DebuggerStepThrough] [DebuggerHidden] - public unsafe object? InvokeUnsafe(object? obj, IntPtr** args, BindingFlags invokeAttr) + 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, invokeAttr); + 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 dd3d9bc40533b6..3cfe6f6368d330 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -128,53 +128,65 @@ internal void ThrowNoInvokeException() { if (argCount == 0) { - Invoker.InvokeUnsafe(obj, args: default, invokeAttr); + Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); } else { Debug.Assert(parameters != null); - Span parametersOut; - bool copyBack = Invoker.HasRefs; + Span copyOfParameters; + Span shouldCopyBackParameters; + Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { - StackAllocatedByRefs byrefStorage = default; - IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; - parametersOut = new Span(ref argStorage._arg0, argCount); + copyOfParameters = new Span(ref argStorage._arg0, argCount); + shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + Invoker.InvokeUnsafe(obj, (IntPtr*)(void**)&byrefStorage, copyOfParameters, invokeAttr); } else { - parametersOut = new Span(new object[argCount]); - IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; - GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); + object[] objHolder = new object[argCount]; + copyOfParameters = new Span(objHolder, 0, argCount); + + // We don't check a max stack size since we are invoking a method which + // natually requires a stack size that is dependent on the arg count\size. + IntPtr* byrefStorage = stackalloc IntPtr[argCount]; + byrefParameters = new Span(byrefStorage, argCount); + + bool* boolHolder = stackalloc bool[argCount]; + shouldCopyBackParameters = new Span(boolHolder, argCount); + + GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)byrefStorage, copyOfParameters, invokeAttr); } finally { @@ -182,13 +194,12 @@ internal void ThrowNoInvokeException() } } - if (copyBack) + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + for (int i = 0; i < argCount; i++) { - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int i = 0; i < argCount; i++) + if (shouldCopyBackParameters[i]) { - parameters[i] = parametersOut[i]; + parameters[i] = copyOfParameters[i]; } } } @@ -221,53 +232,65 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] { if (argCount == 0) { - retValue = Invoker.InvokeUnsafe(obj: null, args: default, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, invokeAttr); } else { Debug.Assert(parameters != null); - Span parametersOut; - bool copyBack = Invoker.HasRefs; + Span copyOfParameters; + Span shouldCopyBackParameters; + Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { - StackAllocatedByRefs byrefStorage = default; - IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; - parametersOut = new Span(ref argStorage._arg0, argCount); + copyOfParameters = new Span(ref argStorage._arg0, argCount); + shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj: null, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj: null, (IntPtr*)(void**)&byrefStorage, copyOfParameters, invokeAttr); } else { - parametersOut = new Span(new object[argCount]); - IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; - GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); + object[] objHolder = new object[argCount]; + copyOfParameters = new Span(objHolder, 0, argCount); + + // We don't check a max stack size since we are invoking a method which + // natually requires a stack size that is dependent on the arg count\size. + IntPtr* byrefStorage = stackalloc IntPtr[argCount]; + byrefParameters = new Span(byrefStorage, argCount); + + bool* boolHolder = stackalloc bool[argCount]; + shouldCopyBackParameters = new Span(boolHolder, argCount); + + GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj: null, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj: null, (IntPtr*)(void*)byrefStorage, copyOfParameters, invokeAttr); } finally { @@ -275,13 +298,12 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] } } - if (copyBack) + // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. + for (int i = 0; i < argCount; i++) { - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) - for (int i = 0; i < argCount; i++) + if (shouldCopyBackParameters[i]) { - parameters[i] = parametersOut[i]; + parameters[i] = copyOfParameters[i]; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs index bad7c6c729f835..c9d2818c04c268 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -126,69 +126,91 @@ private void ThrowNoInvokeException() { if (argCount == 0) { - retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); } else { Debug.Assert(parameters != null); - Span parametersOut; - bool copyBack = Invoker.HasRefs; + Span copyOfParameters; + Span shouldCopyBackParameters; + Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { - StackAllocatedByRefs byrefStorage = default; - IntPtr** unsafeByrefParameters = (IntPtr**)&byrefStorage; StackAllocedArguments argStorage = default; - parametersOut = new Span(ref argStorage._arg0, argCount); + copyOfParameters = new Span(ref argStorage._arg0, argCount); + shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); + + StackAllocatedByRefs byrefStorage = default; + byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)&byrefStorage, 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]; + } + } } else { - parametersOut = new Span(new object[argCount]); - IntPtr** unsafeByrefParameters = stackalloc IntPtr*[argCount]; - GCFrameRegistration reg = new(unsafeByrefParameters, (uint)argCount, areByRefs: true); + object[] objHolder = new object[argCount]; + copyOfParameters = new Span(objHolder, 0, argCount); + + // We don't check a max stack size since we are invoking a method which + // natually requires a stack size that is dependent on the arg count\size. + IntPtr* byrefStorage = stackalloc IntPtr[argCount]; + byrefParameters = new Span(byrefStorage, argCount); + + bool* boolHolder = stackalloc bool[argCount]; + shouldCopyBackParameters = new Span(boolHolder, argCount); + + GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); try { RegisterForGCReporting(®); CheckArguments( - ref parametersOut, - unsafeByrefParameters, - ref copyBack, + copyOfParameters, + byrefParameters, + shouldCopyBackParameters, parameters, ArgumentTypes, binder, culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, unsafeByrefParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)byrefStorage, copyOfParameters, invokeAttr); } finally { UnregisterForGCReporting(®); } - } - if (copyBack) - { // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - // n.b. cannot use Span.CopyTo, as parameters.GetType() might not actually be typeof(object[]) for (int i = 0; i < argCount; i++) { - parameters[i] = parametersOut[i]; + if (shouldCopyBackParameters[i]) + { + parameters[i] = copyOfParameters[i]; + } } } + + } } 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..3fb402889ed7de 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 }; } @@ -633,240 +634,401 @@ private static MethodInfo GetMethod(Type type, string name) { return type.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).First(method => method.Name.Equals(name)); } - } -#pragma warning disable 0414 - public interface MI_Interface - { - int IMethod(); - int IMethodNew(); - } + [Fact] + public static 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)!; + } - public class MI_BaseClass : MI_Interface - { - public int IMethod() => 10; - public int IMethodNew() => 20; + [Fact] + public static 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)!; + } - public static bool StaticIntMethodReturningBool(int int4a) => int4a % 2 == 0; - public virtual int VirtualReturnIntMethod() => 0; + [Fact] + public static void InvokeEnum() + { + // Enums only need to match by primitive type. + Assert.True((bool)GetMethod(nameof(EnumMethods.PassColorsInt)). + Invoke(null, new object[] { OtherColorsInt.Red })); - public virtual int VirtualMethod() => 0; - private int PrivateInstanceMethod() => 21; - public static string PublicStaticMethod(string x) => x; - public string PublicStructMethod(DateTime dt) => dt.ToString(); - } + // Widening allowed + Assert.True((bool)GetMethod(nameof(EnumMethods.PassColorsInt)). + Invoke(null, new object[] { ColorsShort.Red })); - public class MI_SubClass : MI_BaseClass - { - public override int VirtualReturnIntMethod() => 2; + // Narrowing not allowed + Assert.Throws(() => GetMethod(nameof(EnumMethods.PassColorsShort)). + Invoke(null, new object[] { OtherColorsInt.Red })); - public PublicEnum EnumMethodReturningEnum(PublicEnum myenum) => myenum == PublicEnum.Case1 ? PublicEnum.Case2 : PublicEnum.Case1; - public string ObjectMethodReturningString(object obj) => obj.ToString(); - public int VoidMethodReturningInt() => 3; - public long VoidMethodReturningLong() => long.MaxValue; - public long IntLongMethodReturningLong(int i, long l) => i + l; - public static int StaticIntIntMethodReturningInt(int i1, int i2) => i1 + i2; + static MethodInfo GetMethod(string name) => typeof(EnumMethods).GetMethod( + name, BindingFlags.Public | BindingFlags.Static)!; + } - public static void StaticGenericMethod(T t) { } +#pragma warning disable 0414 + public interface MI_Interface + { + int IMethod(); + int IMethodNew(); + } - public new int IMethodNew() => 200; + public class MI_BaseClass : MI_Interface + { + public int IMethod() => 10; + public int IMethodNew() => 20; - public override int VirtualMethod() => 1; + public static bool StaticIntMethodReturningBool(int int4a) => int4a % 2 == 0; + public virtual int VirtualReturnIntMethod() => 0; - public void ReturnVoidMethod(DateTime dt) { } - public virtual string[] VirtualReturnStringArrayMethod() => new string[0]; - public virtual bool VirtualReturnBoolMethod() => true; + public virtual int VirtualMethod() => 0; + private int PrivateInstanceMethod() => 21; + public static string PublicStaticMethod(string x) => x; + public string PublicStructMethod(DateTime dt) => dt.ToString(); + } - public string Method2(string t2, T t1, S t3) => ""; + public class MI_SubClass : MI_BaseClass + { + public override int VirtualReturnIntMethod() => 2; - public IntPtr ReturnIntPtrMethod() => new IntPtr(200); - public int[] ReturnArrayMethod() => new int[] { 2, 3, 5, 7, 11 }; + public PublicEnum EnumMethodReturningEnum(PublicEnum myenum) => myenum == PublicEnum.Case1 ? PublicEnum.Case2 : PublicEnum.Case1; + public string ObjectMethodReturningString(object obj) => obj.ToString(); + public int VoidMethodReturningInt() => 3; + public long VoidMethodReturningLong() => long.MaxValue; + public long IntLongMethodReturningLong(int i, long l) => i + l; + public static int StaticIntIntMethodReturningInt(int i1, int i2) => i1 + i2; - public void GenericMethod1(T t) { } - public void GenericMethod2(T t, U u) { } + public static void StaticGenericMethod(T t) { } - public void StringArrayMethod(string[] strArray) { } + public new int IMethodNew() => 200; - [Attr(77, name = "AttrSimple"), - Int32Attr(77, name = "Int32AttrSimple"), - Int64Attr(77, name = "Int64AttrSimple"), - StringAttr("hello", name = "StringAttrSimple"), - EnumAttr(PublicEnum.Case1, name = "EnumAttrSimple"), - TypeAttr(typeof(object), name = "TypeAttrSimple")] - public void MethodWithAttributes() { } - } + public override int VirtualMethod() => 1; - public class MethodInfoDummySubClass : MI_BaseClass - { - public override int VirtualReturnIntMethod() => 1; - } + public void ReturnVoidMethod(DateTime dt) { } + public virtual string[] VirtualReturnStringArrayMethod() => new string[0]; + public virtual bool VirtualReturnBoolMethod() => true; - public class MI_Interlocked - { - public static int Increment(ref int location) => 0; - public static int Decrement(ref int location) => 0; - public static int Exchange(ref int location1, int value) => 0; - public static int CompareExchange(ref int location1, int value, int comparand) => 0; + public string Method2(string t2, T t1, S t3) => ""; - public static float Exchange(ref float location1, float value) => 0; - public static float CompareExchange(ref float location1, float value, float comparand) => 0; + public IntPtr ReturnIntPtrMethod() => new IntPtr(200); + public int[] ReturnArrayMethod() => new int[] { 2, 3, 5, 7, 11 }; - public static object Exchange(ref object location1, object value) => null; - public static object CompareExchange(ref object location1, object value, object comparand) => null; - } + public void GenericMethod1(T t) { } + public void GenericMethod2(T t, U u) { } - public class MI_GenericClass - { - public T GenericMethod1(T t) => t; - public T GenericMethod2(S s1, T t, string s2) => t; - public static S GenericMethod3(S s) => s; - } + public void StringArrayMethod(string[] strArray) { } - public interface MethodInfoBaseDefinitionInterface - { - void InterfaceMethod1(); - void InterfaceMethod2(); - } + [Attr(77, name = "AttrSimple"), + Int32Attr(77, name = "Int32AttrSimple"), + Int64Attr(77, name = "Int64AttrSimple"), + StringAttr("hello", name = "StringAttrSimple"), + EnumAttr(PublicEnum.Case1, name = "EnumAttrSimple"), + TypeAttr(typeof(object), name = "TypeAttrSimple")] + public void MethodWithAttributes() { } + } - public class MethodInfoBaseDefinitionBaseClass : MethodInfoBaseDefinitionInterface - { - public void InterfaceMethod1() { } - void MethodInfoBaseDefinitionInterface.InterfaceMethod2() { } + public class MethodInfoDummySubClass : MI_BaseClass + { + public override int VirtualReturnIntMethod() => 1; + } - public virtual void BaseClassVirtualMethod() { } - public virtual void BaseClassMethod() { } + public class MI_Interlocked + { + public static int Increment(ref int location) => 0; + public static int Decrement(ref int location) => 0; + public static int Exchange(ref int location1, int value) => 0; + public static int CompareExchange(ref int location1, int value, int comparand) => 0; - public override string ToString() => base.ToString(); - } + public static float Exchange(ref float location1, float value) => 0; + public static float CompareExchange(ref float location1, float value, float comparand) => 0; - public class MethodInfoBaseDefinitionSubClass : MethodInfoBaseDefinitionBaseClass - { - public override void BaseClassVirtualMethod() => base.BaseClassVirtualMethod(); - public new void BaseClassMethod() { } - public override string ToString() => base.ToString(); + public static object Exchange(ref object location1, object value) => null; + public static object CompareExchange(ref object location1, object value, object comparand) => null; + } - public void DerivedClassMethod() { } - } + public class MI_GenericClass + { + public T GenericMethod1(T t) => t; + public T GenericMethod2(S s1, T t, string s2) => t; + public static S GenericMethod3(S s) => s; + } - public abstract class MI_AbstractBaseClass - { - public abstract void AbstractMethod(); - public virtual void VirtualMethod() { } - } + public interface MethodInfoBaseDefinitionInterface + { + void InterfaceMethod1(); + void InterfaceMethod2(); + } - public class MI_AbstractSubClass : MI_AbstractBaseClass - { - public sealed override void VirtualMethod() { } - public override void AbstractMethod() { } - } + public class MethodInfoBaseDefinitionBaseClass : MethodInfoBaseDefinitionInterface + { + public void InterfaceMethod1() { } + void MethodInfoBaseDefinitionInterface.InterfaceMethod2() { } - public interface MethodInfoDefaultParametersInterface - { - string InterfaceMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m); - } + public virtual void BaseClassVirtualMethod() { } + public virtual void BaseClassMethod() { } - public class MethodInfoDefaultParameters : MethodInfoDefaultParametersInterface - { - public int Integer(int parameter = 1) - { - return parameter; + public override string ToString() => base.ToString(); } - public string AllPrimitives( - bool boolean = true, - string str = "test", - char character = 'c', - byte unsignedbyte = 2, - sbyte signedbyte = -1, - short int16 = -3, - ushort uint16 = 4, - int int32 = -5, - uint uint32 = 6, - long int64 = -7, - ulong uint64 = 8, - float single = 9.1f, - double dbl = 11.12) + public class MethodInfoBaseDefinitionSubClass : MethodInfoBaseDefinitionBaseClass { - return FormattableString.Invariant($"{boolean}, {str}, {character}, {unsignedbyte}, {signedbyte}, {int16}, {uint16}, {int32}, {uint32}, {int64}, {uint64}, {single}, {dbl}"); - } + public override void BaseClassVirtualMethod() => base.BaseClassVirtualMethod(); + public new void BaseClassMethod() { } + public override string ToString() => base.ToString(); - public string String(string parameter = "test") => parameter; + public void DerivedClassMethod() { } + } - public class CustomReferenceType + public abstract class MI_AbstractBaseClass { - public override bool Equals(object obj) => ReferenceEquals(this, obj); - public override int GetHashCode() => 0; + public abstract void AbstractMethod(); + public virtual void VirtualMethod() { } } - public CustomReferenceType Reference(CustomReferenceType parameter = null) => parameter; + public class MI_AbstractSubClass : MI_AbstractBaseClass + { + public sealed override void VirtualMethod() { } + public override void AbstractMethod() { } + } - public struct CustomValueType + public interface MethodInfoDefaultParametersInterface { - public int Id; - public override bool Equals(object obj) => Id == ((CustomValueType)obj).Id; - public override int GetHashCode() => Id.GetHashCode(); + string InterfaceMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m); } - public CustomValueType ValueType(CustomValueType parameter = default(CustomValueType)) => parameter; + public class MethodInfoDefaultParameters : MethodInfoDefaultParametersInterface + { + public int Integer(int parameter = 1) + { + return parameter; + } + + public string AllPrimitives( + bool boolean = true, + string str = "test", + char character = 'c', + byte unsignedbyte = 2, + sbyte signedbyte = -1, + short int16 = -3, + ushort uint16 = 4, + int int32 = -5, + uint uint32 = 6, + long int64 = -7, + ulong uint64 = 8, + float single = 9.1f, + double dbl = 11.12) + { + return FormattableString.Invariant($"{boolean}, {str}, {character}, {unsignedbyte}, {signedbyte}, {int16}, {uint16}, {int32}, {uint32}, {int64}, {uint64}, {single}, {dbl}"); + } + + public string String(string parameter = "test") => parameter; + + public class CustomReferenceType + { + public override bool Equals(object obj) => ReferenceEquals(this, obj); + public override int GetHashCode() => 0; + } + + public CustomReferenceType Reference(CustomReferenceType parameter = null) => parameter; + + public struct CustomValueType + { + public int Id; + public override bool Equals(object obj) => Id == ((CustomValueType)obj).Id; + public override int GetHashCode() => Id.GetHashCode(); + } + + public CustomValueType ValueType(CustomValueType parameter = default(CustomValueType)) => parameter; + + public DateTime DateTime([DateTimeConstant(42)] DateTime parameter) => parameter; + + public decimal DecimalWithAttribute([DecimalConstant(1, 1, 2, 3, 4)] decimal parameter) => parameter; + + public decimal Decimal(decimal parameter = 3.14m) => parameter; - public DateTime DateTime([DateTimeConstant(42)] DateTime parameter) => parameter; + public int? NullableInt(int? parameter = null) => parameter; - public decimal DecimalWithAttribute([DecimalConstant(1, 1, 2, 3, 4)] decimal parameter) => parameter; + public PublicEnum Enum(PublicEnum parameter = PublicEnum.Case1) => parameter; - public decimal Decimal(decimal parameter = 3.14m) => parameter; + string MethodInfoDefaultParametersInterface.InterfaceMethod(int p1, string p2, decimal p3) + { + return FormattableString.Invariant($"{p1}, {p2}, {p3}"); + } + + public static string StaticMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m) + { + return FormattableString.Invariant($"{p1}, {p2}, {p3}"); + } + + public object OptionalObjectParameter([Optional] object parameter) => parameter; + public string OptionalStringParameter([Optional] string parameter) => parameter; + } + + public delegate int Delegate_TC_Int(MI_BaseClass tc); + public delegate int Delegate_Void_Int(); + public delegate string Delegate_Str_Str(string x); + public delegate string Delegate_Void_Str(); + public delegate string Delegate_DateTime_Str(MI_BaseClass tc, DateTime dt); - public int? NullableInt(int? parameter = null) => parameter; + public delegate T Delegate_GC_T_T(MI_GenericClass gc, T x); + public delegate T Delegate_T_T(T x); + public delegate T Delegate_Void_T(); - public PublicEnum Enum(PublicEnum parameter = PublicEnum.Case1) => parameter; + public class DummyClass { } - string MethodInfoDefaultParametersInterface.InterfaceMethod(int p1, string p2, decimal p3) + public class Sample { - return FormattableString.Invariant($"{p1}, {p2}, {p3}"); + public string Method1(DateTime t) + { + return ""; + } + public string Method2(string t2, T t1, S t3) + { + return ""; + } } - public static string StaticMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m) + public class SampleG { - return FormattableString.Invariant($"{p1}, {p2}, {p3}"); + public T Method1(T t) + { + return t; + } + public T Method2(S t1, T t2, string t3) + { + return t2; + } } - public object OptionalObjectParameter([Optional]object parameter) => parameter; - public string OptionalStringParameter([Optional]string parameter) => parameter; - } + private static class NullableRefMethods + { + public static bool Null(ref int? i) + { + Assert.Null(i); + return true; + } - public delegate int Delegate_TC_Int(MI_BaseClass tc); - public delegate int Delegate_Void_Int(); - public delegate string Delegate_Str_Str(string x); - public delegate string Delegate_Void_Str(); - public delegate string Delegate_DateTime_Str(MI_BaseClass tc, DateTime dt); + public static bool NullBoxed(ref object? i) + { + Assert.Null(i); + return true; + } - public delegate T Delegate_GC_T_T(MI_GenericClass gc, T x); - public delegate T Delegate_T_T(T x); - public delegate T Delegate_Void_T(); + public static bool NullToValue(ref int? i, int value) + { + Assert.Null(i); + i = value; + return true; + } - public class DummyClass { } + public static bool NullToValueBoxed(ref object? i, int value) + { + Assert.Null(i); + i = value; + return true; + } - public class Sample - { - public string Method1(DateTime t) + 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; + } + } + + private enum ColorsInt : int { - return ""; + Red = 1 } - public string Method2(string t2, T t1, S t3) + + private enum ColorsShort : short { - return ""; + Red = 1 } - } - public class SampleG - { - public T Method1(T t) + private enum OtherColorsInt : int { - return t; + Red = 1 } - public T Method2(S t1, T t2, string t3) + + private static class EnumMethods { - return t2; + 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/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs index 7840a4cd31c549..5e25a349e7535a 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 @@ -382,47 +382,19 @@ internal RuntimeType[] ArgumentTypes * Exceptions thrown by the called method propagate normally. */ [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal extern object? InternalInvoke(object? obj, in Span parameters, out Exception? exc); - - internal object? InternalInvoke(object? obj, Span parameters, out Exception? exc) - { - unsafe - { - // Convert to Span. - int parametersLength = parameters.Length; - IntPtr* stackStorage = stackalloc IntPtr[parametersLength]; - for (int i = 0; i < parametersLength; i++) - { - stackStorage[i] = (IntPtr)Unsafe.AsPointer(ref parameters[i]); - } - - Span refParameters = new(stackStorage, parametersLength); - return InternalInvoke(obj, refParameters, out exc); - } - } + internal extern object? InternalInvoke(object? obj, in Span parameters, out Exception? exc); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** byrefParameters, BindingFlags invokeAttr) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { Exception? exc; object? o; - // Convert the byref array to a ref array. - // The native code could also be adapted to take byref pointers to avoid this conversion. - int parametersLength = ArgumentTypes.Length; - IntPtr* stackStorage = stackalloc IntPtr[parametersLength]; - for (int i = 0; i < parametersLength; i++) - { - stackStorage[i] = *byrefParameters[i]; - } - - Span refParameters = new(stackStorage, parametersLength); - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { - o = InternalInvoke(obj, refParameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } catch (Mono.NullByRefReturnException) { @@ -441,7 +413,7 @@ internal RuntimeType[] ArgumentTypes { try { - o = InternalInvoke(obj, refParameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } catch (Mono.NullByRefReturnException) { @@ -885,33 +857,18 @@ private static void InvokeClassConstructor() * to match the types of the method signature. */ [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); + internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); - internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr** byrefParameters, BindingFlags invokeAttr) + internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { Exception exc; object? o; - Span refParameters = default; - - if (byrefParameters != null) - { - // Convert the byref array to a ref array. - // The native code could also be adapted to take byref pointers to avoid this conversion. - int parametersLength = ArgumentTypes.Length; - IntPtr* refParametersStorage = stackalloc IntPtr[parametersLength]; - for (int i = 0; i < parametersLength; i++) - { - refParametersStorage[i] = *byrefParameters[i]; - } - - refParameters = new(refParametersStorage, parametersLength); - } if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { - o = InternalInvoke(obj, refParameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } catch (MethodAccessException) { @@ -928,7 +885,7 @@ private static void InvokeClassConstructor() } else { - o = InternalInvoke(obj, refParameters, out exc); + o = InternalInvoke(obj, argsForTemporaryMonoSupport, out exc); } if (exc != null) 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 97eeedcaf5b8d3..1f6d71e151bbde 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1661,6 +1661,7 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) return ctor.Invoker.InvokeUnsafe( obj: null, args: default, + argsForTemporaryMonoSupport: default, wrapExceptions ? BindingFlags.Default : BindingFlags.DoNotWrapExceptions); } } @@ -2010,7 +2011,7 @@ internal static object CreateInstanceForAnotherGenericParameter( unsafe { - return ctor.Invoker.InvokeUnsafe(obj: null, args: default, BindingFlags.Default)!; + return ctor.Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, BindingFlags.Default)!; } } 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); From 33346861a231614760b51d8e924f4c16a5ba71cf Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Fri, 25 Mar 2022 11:38:59 -0500 Subject: [PATCH 09/28] Continued by-ref support; still issues with nullables --- .../src/System/Reflection/RtFieldInfo.cs | 2 +- .../src/System/RuntimeType.CoreCLR.cs | 182 ++++++++++++----- src/coreclr/vm/invokeutil.cpp | 190 ++++++++++++++---- src/coreclr/vm/invokeutil.h | 2 + src/coreclr/vm/methodtable.h | 1 + src/coreclr/vm/methodtable.inl | 29 +++ src/coreclr/vm/object.cpp | 99 ++++++++- src/coreclr/vm/object.h | 2 + src/coreclr/vm/reflectioninvocation.cpp | 27 ++- .../src/System/Reflection/InvokeUtils.cs | 34 ++++ .../src/System/Reflection/MethodBase.cs | 34 ++-- .../System/Reflection/RuntimeMethodInfo.cs | 23 +-- 12 files changed, 480 insertions(+), 145 deletions(-) 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 bb90a27e32f9bd..1b9ee283fedcf4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -240,7 +240,7 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt if (!ReferenceEquals(value?.GetType(), fieldType)) { bool _b = false; - fieldType.CheckValue(ref value, ref _b, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, ref _b, paramInfo: null, binder, culture, invokeAttr); } Invoker.SetValue(obj, value); 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 f0194ce0f9f92a..7ad6388a048a9b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3473,19 +3473,28 @@ public override Type MakeArrayType(int rank) [MethodImpl(MethodImplOptions.InternalCall)] private static extern object AllocateValueType(RuntimeType type, object? value, bool fForceTypeChange); + private enum CheckValueStatus + { + Success = 0, + ArgumentException, + NotSupported_ByRefLike + } + /// /// Verify and optionally convert the value for special cases. /// - internal void CheckValue( + /// True if the value should be considered a value type, False otherwise + internal bool CheckValue( ref object? value, ref bool copyBack, + ParameterInfo? paramInfo, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) { Debug.Assert(!ReferenceEquals(value?.GetType(), this)); - // Check whether a value can be assigned to type. + // Fast path to whethern a value can be assigned to type. if (IsInstanceOfType(value)) { // Since this cannot be a generic parameter, we use RuntimeTypeHandle.IsValueType here @@ -3495,16 +3504,20 @@ internal void CheckValue( if (RuntimeTypeHandle.IsValueType(this)) { // Must be an equivalent type, re-box to the target type - value = AllocateValueType(this, value, fForceTypeChange: true); + object newValue = AllocateValueType(this, value, fForceTypeChange: true); + value = newValue; copyBack = true; + return true; } - return; + return false; } - if (TryChangeType(ref value, ref copyBack)) + bool isValueType; + CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo, allowNull: true); + if (result == CheckValueStatus.Success) { - return; + return isValueType; } Debug.Assert(value != null); @@ -3518,103 +3531,166 @@ internal void CheckValue( if (IsInstanceOfType(value)) { copyBack = true; - return; + return IsValueType; } - if (TryChangeType(ref value, ref copyBack)) + result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo, allowNull: false); + if (result == CheckValueStatus.Success) { - return; + return isValueType; } } } Debug.Assert(value != null); - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), this)); + 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 bool TryChangeType( + private CheckValueStatus TryChangeType( ref object? value, - ref bool copyBack) + out bool copyBack, + out bool isValueType, + ParameterInfo? paramInfo, + bool allowNull) { - RuntimeType sigElementType = this; - // If this is a ByRef get the element type and check if it's compatible bool isByRef = IsByRef; if (isByRef) { - sigElementType = RuntimeTypeHandle.GetElementType(this); + RuntimeType sigElementType = RuntimeTypeHandle.GetElementType(this); if (sigElementType.IsInstanceOfType(value)) { // Need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type + value = AllocateValueType(sigElementType, value, fForceTypeChange: false); + isValueType = sigElementType.IsValueType; copyBack = true; + return CheckValueStatus.Success; + } + else if (value == null) + { + if (IsByRefLike && paramInfo?.IsOut == true) + { + isValueType = copyBack = default; + return CheckValueStatus.NotSupported_ByRefLike; + } + value = AllocateValueType(sigElementType, value, fForceTypeChange: false); - return true; + isValueType = sigElementType.IsValueType; + copyBack = true; + return CheckValueStatus.Success; } - } - else if (this == s_typedRef) - { - // Everything works for a typedref - return true; + else 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(sigElementType)) + if (allowNull) { - // Need to create an instance of the value type if null was provided. - // For a byref-like parameter pass null to the runtime which will create a default value. - if (!RuntimeTypeHandle.IsByRefLike(sigElementType)) + if (RuntimeTypeHandle.IsValueType(this)) + { + isValueType = true; + + if (RuntimeTypeHandle.IsByRefLike(this)) + { + // For a byref-like parameter pass null to the runtime which will create a default value. + copyBack = true; // copy the null back to replace a possible Type.Missing value + } + else + { + // Need to create an instance of the value type if null was provided. + value = AllocateValueType(this, value: null, fForceTypeChange: false); + copyBack = false; + } + } + else { - value = AllocateValueType(sigElementType, value: null, fForceTypeChange: false); - // Don't copy the value back + isValueType = false; + copyBack = false; } + + return CheckValueStatus.Success; } - return true; + isValueType = copyBack = default; + return IsByRefLike ? CheckValueStatus.NotSupported_ByRefLike : CheckValueStatus.ArgumentException; } - Debug.Assert(value != null); + if (this == s_typedRef) + { + // Everything works for a typedref + isValueType = false; + copyBack = false; + return CheckValueStatus.Success; + } + + if (NeedsSpecialCast()) + { + if (SpecialCast(this, ref value) == CheckValueStatus.Success) + { + isValueType = true; + copyBack = false; + return CheckValueStatus.Success; + } + } + + isValueType = copyBack = default; + return CheckValueStatus.ArgumentException; // Check the strange ones courtesy of reflection: // - implicit cast between primitives // - enum treated as underlying type // - IntPtr and System.Reflection.Pointer to pointer types - bool needsSpecialCast = IsPointer || IsEnum || IsPrimitive; - if (needsSpecialCast) + bool NeedsSpecialCast() => IsPointer || IsEnum || IsPrimitive; + + static CheckValueStatus SpecialCast(RuntimeType type, ref object value) { Pointer? pointer = value as Pointer; RuntimeType srcType = pointer != null ? pointer.GetPointerType() : (RuntimeType)value.GetType(); - if (CanValueSpecialCast(srcType, this)) + if (!CanValueSpecialCast(srcType, type)) { - if (pointer != null) - { - value = pointer.GetPointerValue(); - copyBack = true; - return true; - } - else + return CheckValueStatus.ArgumentException; + } + + if (pointer != null) + { + value = pointer.GetPointerValue(); + } + else + { + CorElementType srcElementType = GetUnderlyingType(srcType); + CorElementType dstElementType = GetUnderlyingType(type); + if (dstElementType != srcElementType) { - if (!isByRef) - { - CorElementType srcElementType = GetUnderlyingType(srcType); - CorElementType dstElementType = GetUnderlyingType(this); - if (dstElementType != srcElementType) - { - value = InvokeUtils.ConvertOrWiden(srcType, srcElementType, value, this, dstElementType); - // Do not copy back. - } - } + value = InvokeUtils.ConvertOrWiden(srcType, srcElementType, value, type, dstElementType); } } - return true; + return CheckValueStatus.Success; } - - return false; } - private static CorElementType GetUnderlyingType(RuntimeType type) + private static CorElementType GetUnderlyingType(RuntimeType type) { if (type.IsEnum) { diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index a8c5d8d8f698a2..2675229dbb2ebd 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -124,6 +124,155 @@ void *InvokeUtil::GetIntPtrValue(OBJECTREF pObj) { RETURN *(void **)((pObj)->UnBox()); } +void InvokeUtil::CopyArg(TypeHandle th, PVOID *pArgUnsafe, ArgDestination *argDest) { + CONTRACTL { + THROWS; + GC_NOTRIGGER; // Caller does not protect object references + MODE_COOPERATIVE; + PRECONDITION(!th.IsNull()); + INJECT_FAULT(COMPlusThrowOM()); + } + CONTRACTL_END; + + void *pArgDst = argDest->GetDestinationAddress(); + + MethodTable* pMT = th.GetMethodTable(); + CorElementType oType; + CorElementType type; + + oType = pMT->GetInternalCorElementType(); + 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_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_CHAR: + 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:) + { + { + _ASSERTE(pArgUnsafe != NULL); + ARG_SLOT slot; + CreatePrimitiveValue(type, oType, pArgUnsafe, pMT, &slot); + *(PVOID *)pArgDst = (PVOID)slot; + } + 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:) + { + { + _ASSERTE(pArgUnsafe != NULL); + ARG_SLOT slot; + CreatePrimitiveValue(type, oType, pArgUnsafe, pMT, &slot); + *(INT64 *)pArgDst = (INT64)slot; + } + break; + } + + case ELEMENT_TYPE_VALUETYPE: + { + // pArgUnsafe can be NULL but only for Nullable types; UnBoxIntoArg verifies that. + { + if (!th.AsMethodTable()->UnBoxIntoArg(argDest, pArgUnsafe)) + COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + } + break; + } + + 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 (pArgUnsafe == NULL) + *(PVOID *)pArgDst = 0; + else + *(PVOID *)pArgDst = OBJECTREFToObject((OBJECTREF)(Object*)*pArgUnsafe); + break; + } + + case ELEMENT_TYPE_BYREF: + { + // We should never get here for nullable types. Instead invoke + // heads these off and morphs the type handle to not be byref anymore + _ASSERTE(!Nullable::IsNullableType(th.AsTypeDesc()->GetTypeParam())); + + *(PVOID *)pArgDst = pArgUnsafe; + 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); + // } + + // break; + //} + + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_FNPTR: + { + // If we got the univeral zero...Then assign it and exit. + if (pArgUnsafe == NULL) { + *(PVOID *)pArgDst = 0; + } + else { + OBJECTREF rObj = (OBJECTREF)(Object*)*pArgUnsafe; + 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")); + } + break; + } + + case ELEMENT_TYPE_VOID: + default: + _ASSERTE(!"Unknown Type"); + COMPlusThrow(kNotSupportedException); + } +} + // CreatePrimitiveValue // This routine will validate the object and then place the value into // the destination @@ -153,7 +302,6 @@ void InvokeUtil::CreatePrimitiveValue(CorElementType dstType, MethodTable *pSrcMT, ARG_SLOT* pDst) { - CONTRACTL { THROWS; GC_NOTRIGGER; @@ -307,46 +455,6 @@ void InvokeUtil::CreatePrimitiveValue(CorElementType dstType, } } -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) diff --git a/src/coreclr/vm/invokeutil.h b/src/coreclr/vm/invokeutil.h index 2a55db865ed98c..4ff2ca8d80c766 100644 --- a/src/coreclr/vm/invokeutil.h +++ b/src/coreclr/vm/invokeutil.h @@ -51,6 +51,8 @@ class InvokeUtil { public: + static void CopyArg(TypeHandle th, PVOID *pArgRef, 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 // value is wrapped in one of the Value classes. diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 039b5c06e14c86..68d5a1bb1a5072 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -2387,6 +2387,7 @@ class MethodTable #ifndef DACCESS_COMPILE BOOL UnBoxInto(void *dest, OBJECTREF src); BOOL UnBoxIntoArg(ArgDestination *argDest, OBJECTREF src); + BOOL UnBoxIntoArg(ArgDestination *argDest, PVOID pSrcUnsafe); void UnBoxIntoUnchecked(void *dest, OBJECTREF src); #endif diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index 72b628dae9545a..19bc734504f5a8 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -1307,6 +1307,35 @@ inline BOOL MethodTable::UnBoxIntoArg(ArgDestination *argDest, OBJECTREF src) return TRUE; } +inline BOOL MethodTable::UnBoxIntoArg(ArgDestination *argDest, PVOID pSrcUnsafe) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + TypeHandle th = TypeHandle(this); + if (Nullable::IsNullableType(th)) + { + // ASSUMPTION: we only receive T or NULL values, not Nullable values. + TypeHandle thSrc = th.AsTypeDesc()->GetTypeParam(); + MethodTable *srcMT = thSrc.GetMethodTable(); + + return Nullable::UnBoxIntoArgNoGC(argDest, pSrcUnsafe, this, srcMT); + } + else + { + if (pSrcUnsafe == NULL) + return FALSE; + + CopyValueClassArg(argDest, pSrcUnsafe, this, 0); + } + return TRUE; +} + //========================================================================================== // unbox src into dest, No checks are done diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 8071392c9b4cce..862499ccf2e100 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); } @@ -1736,6 +1736,51 @@ BOOL Nullable::UnBoxNoGC(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) return TRUE; } +BOOL Nullable::UnBoxNoGC(void* destPtr, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + Nullable* dest = (Nullable*) destPtr; + + // We should only get here if we are unboxing a T as a Nullable + _ASSERTE(IsNullableType(destMT)); + + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!destMT->ContainsGenericVariables()); + + if (pValUnsafe == NULL) + { + // Logically we are doing *dest->HasValueAddr(destMT) = false; + // We zero out the whole structure because it may contain GC references + // and these need to be initialized to zero. (could optimize in the non-GC case) + InitValueClass(destPtr, destMT); + } + else + { + if (!IsNullableForTypeNoGC(destMT, srcMT)) + { + // For safety's sake, also allow true nullables to be unboxed normally. + // This should not happen normally, but we want to be robust + if (destMT == srcMT) + { + CopyValueClass(dest, pValUnsafe, destMT); + return TRUE; + } + return FALSE; + } + + *dest->HasValueAddr(destMT) = true; + CopyValueClass(dest->ValueAddr(destMT), pValUnsafe, srcMT); + } + + return TRUE; +} + //=============================================================================== // Special Logic to unbox a boxed T as a nullable into an argument // specified by the argDest. @@ -1762,7 +1807,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); } @@ -1793,6 +1838,48 @@ BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, Met return UnBoxNoGC(argDest->GetDestinationAddress(), boxedVal, destMT); } +BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + +#if defined(UNIX_AMD64_ABI) + if (argDest->IsStructPassedInRegs()) + { + // We should only get here if we are unboxing a T as a Nullable + _ASSERTE(IsNullableType(destMT)); + + // We better have a concrete instantiation, or our field offset asserts are not useful + _ASSERTE(!destMT->ContainsGenericVariables()); + + if (pValUnsafe == NULL) + { + // Logically we are doing *dest->HasValueAddr(destMT) = false; + // We zero out the whole structure because it may contain GC references + // and these need to be initialized to zero. (could optimize in the non-GC case) + InitValueClassArg(argDest, destMT); + } + else + { + Nullable* dest = (Nullable*)argDest->GetStructGenRegDestinationAddress(); + *dest->HasValueAddr(destMT) = true; + int destOffset = (BYTE*)dest->ValueAddr(destMT) - (BYTE*)dest; + CopyValueClassArg(argDest, pValUnsafe, srcMT, destOffset); + } + + return TRUE; + } + +#endif // UNIX_AMD64_ABI + + return UnBoxNoGC(argDest->GetDestinationAddress(), pValUnsafe, destMT, srcMT); +} + //=============================================================================== // Special Logic to unbox a boxed T as a nullable // Does not do any type checks. @@ -1816,7 +1903,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 b98b520d4aded7..92c3ebfcf395f8 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -2669,7 +2669,9 @@ class Nullable { static OBJECTREF Box(void* src, MethodTable* nullable); static BOOL UnBox(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static BOOL UnBoxNoGC(void* dest, OBJECTREF boxedVal, MethodTable* destMT); + static BOOL UnBoxNoGC(void* dest, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT); static BOOL UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, MethodTable* destMT); + static BOOL UnBoxIntoArgNoGC(ArgDestination *argDest, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT); static void UnBoxNoCheck(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static OBJECTREF BoxedNullableNull(TypeHandle nullableType) { return 0; } diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index e91bb3ee80c1a2..222854d3656e63 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -389,13 +389,16 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) 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 (!args[i]) COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); - memcpyNoGCRefs(indexes + i, *(PVOID **)args[i], sizeof(INT32)); + ARG_SLOT value; + InvokeUtil::CreatePrimitiveValue(ELEMENT_TYPE_I4, ELEMENT_TYPE_I4, args[i], pMT, &value); + memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); } return AllocateArrayEx(th, indexes, argCnt); @@ -759,22 +762,23 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, UINT structSize = argit.GetArgSize(); bool needsStackCopy = false; + bool needsToInitValueClass = false; MethodTable * pMT = th.GetMethodTable(); - // 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; } - else if (pMT->IsByRefLike()) { - // A byref-like type can't be boxed but can be passed as a null reference in which - // case we return a zero-filled ref struct and not do call the default ctor. - needsStackCopy = true; + else if (args[i] == NULL && pMT->IsByRefLike()) { + // A byref-like type can't be boxed but the arg value can be passed as a null in which + // case we need to initialize the ref struct. + needsToInitValueClass = true; } #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE else if (argit.IsArgPassedByRef()) @@ -789,7 +793,9 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, { _ASSERTE(pMT && pMT->IsValueType()); + PVOID pArgDst = argDest.GetDestinationAddress(); PVOID pStackCopy = _alloca(structSize); + *(PVOID *)pArgDst = pStackCopy; if (!nullableType.IsNull()) { @@ -806,14 +812,13 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, argDest = ArgDestination(pStackCopy, 0, NULL); } - if (pMT->IsByRefLike()) + if (needsToInitValueClass) { InitValueClassArg(&argDest, pMT); } else { - PVOID pArgDst = argDest.GetDestinationAddress(); - *(PVOID *)pArgDst = *(PVOID **)args[i]; + InvokeUtil::CopyArg(th, (PVOID *)args[i], &argDest); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs index 9d16e9255603cf..3eb3a2a3526837 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -13,6 +13,18 @@ internal static class InvokeUtils 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: @@ -96,5 +108,27 @@ public static object ConvertOrWiden(Type srcType, CorElementType srcElementType, 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; + } + + if (srcObject is Pointer srcPointer) + { + //CorElementType dstElementType = RuntimeTypeHandle.GetCorElementType((RuntimeType)typeof(void*)); + //if (dstEEType == dstElementType || RuntimeImports.AreTypesAssignable(pSourceType: srcPointer.GetPointerType().TypeHandle.ToEETypePtr(), pTargetType: dstEEType)) + { + dstIntPtr = srcPointer.GetPointerValue(); + return true; + } + } + + 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 a581cc745cbc84..1bde98144de313 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -154,27 +154,35 @@ BindingFlags invokeAttr { Debug.Assert(!parameters.IsEmpty); - ParameterInfo[]? p = null; + ParameterInfo[]? paramInfos = null; for (int i = 0; i < parameters.Length; i++) { bool copyBackArg = false; object? arg = parameters[i]; if (arg == Type.Missing) { - p ??= GetParametersNoCopy(); - if (p[i].DefaultValue == DBNull.Value) + paramInfos ??= GetParametersNoCopy(); + if (paramInfos[i].DefaultValue == DBNull.Value) { throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); } copyBackArg = true; - arg = p[i].DefaultValue; + arg = paramInfos[i].DefaultValue; } + bool isValueType; RuntimeType sigType = sigTypes[i]; - if (!ReferenceEquals(arg?.GetType(), sigType)) + + if (ReferenceEquals(arg?.GetType(), sigType)) + { + // Fast path that avoids calling CheckValue() + isValueType = RuntimeTypeHandle.IsValueType(sigType); + } + else { - sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); + paramInfos ??= GetParametersNoCopy(); + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, paramInfos[i], binder, culture, invokeAttr); } // We need to perform type safety validation against the incoming arguments, but we also need @@ -186,20 +194,14 @@ BindingFlags invokeAttr shouldCopyBack[i] = copyBackArg; copyOfParameters[i] = arg; - if (RuntimeTypeHandle.IsValueType(sigType)) + if (isValueType) { if (arg is null) { - Debug.Assert(sigType.IsByRefLike); + // This is a special case where we pass null for a null Nullable and for + // the case to create a default ref struct when 'null' is passed. Debug.Assert(!copyBackArg); - - p ??= GetParametersNoCopy(); - if (p[i].IsOut) - { - throw new NotSupportedException(SR.NotSupported_ByRefLike); - } - - byrefParameters[i] = (IntPtr)Unsafe.AsPointer(ref copyOfParameters[i]); + byrefParameters[i] = IntPtr.Zero; } else { 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 c9d2818c04c268..8ff055b85c9990 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -155,15 +155,6 @@ private void ThrowNoInvokeException() invokeAttr); retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)&byrefStorage, 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]; - } - } } else { @@ -199,18 +190,16 @@ private void ThrowNoInvokeException() { UnregisterForGCReporting(®); } + } - // Copy modified values out. This should be done only with ByRef or Type.Missing parameters. - for (int i = 0; i < argCount; i++) + // 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]) { - if (shouldCopyBackParameters[i]) - { - parameters[i] = copyOfParameters[i]; - } + parameters[i] = copyOfParameters[i]; } } - - } } From 7891c3a3e07b20601cdb52ce69978db1f48dcccc Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sun, 27 Mar 2022 09:37:48 -0500 Subject: [PATCH 10/28] Address nullable issues and other clean up --- .../System/Reflection/Emit/DynamicMethod.cs | 26 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 3 +- .../src/System/RuntimeType.CoreCLR.cs | 57 +- src/coreclr/vm/invokeutil.cpp | 111 ++-- src/coreclr/vm/invokeutil.h | 5 +- src/coreclr/vm/methodtable.h | 1 - src/coreclr/vm/methodtable.inl | 29 - src/coreclr/vm/object.cpp | 87 --- src/coreclr/vm/object.h | 2 - src/coreclr/vm/reflectioninvocation.cpp | 27 +- .../src/System/Reflection/InvokeUtils.cs | 13 +- .../src/System/Reflection/MethodBase.cs | 23 +- .../Reflection/RuntimeConstructorInfo.cs | 20 +- .../System/Reflection/RuntimeMethodInfo.cs | 10 +- .../tests/MethodInfoTests.cs | 498 +++++++++--------- .../src/System/Reflection/RuntimeFieldInfo.cs | 2 +- .../src/System/RuntimeType.Mono.cs | 11 +- 17 files changed, 392 insertions(+), 533 deletions(-) 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 5fb8f4497b49f8..adb97d4a9cbf31 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 @@ -433,13 +433,21 @@ private DynamicMethodInvoker Invoker internal Signature Signature { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - Debug.Assert(m_methodHandle != null); - Debug.Assert(m_parameterTypes != null); + [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; + } - _signature ??= new Signature(m_methodHandle, m_parameterTypes, m_returnType, CallingConvention); - return _signature; + return _signature ?? LazyCreateSignature(); } } @@ -475,7 +483,6 @@ internal Signature Signature Debug.Assert(parameters != null); Span copyOfParameters; Span shouldCopyBackParameters; - Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { @@ -484,11 +491,10 @@ internal Signature Signature shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; - byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( copyOfParameters, - byrefParameters, + (IntPtr*)&byrefStorage, shouldCopyBackParameters, parameters, Signature.Arguments, @@ -504,9 +510,9 @@ internal Signature Signature copyOfParameters = new Span(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which - // natually requires a stack size that is dependent on the arg count\size. + // naturally requires a stack size that is dependent on the arg count\size. IntPtr* byrefStorage = stackalloc IntPtr[argCount]; - byrefParameters = new Span(byrefStorage, argCount); + Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); bool* boolHolder = stackalloc bool[argCount]; shouldCopyBackParameters = new Span(boolHolder, argCount); @@ -518,7 +524,7 @@ internal Signature Signature RegisterForGCReporting(®); CheckArguments( copyOfParameters, - byrefParameters, + byrefStorage, shouldCopyBackParameters, parameters, Signature.Arguments, 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 1d0c1abf9ace95..0d8e209681ee66 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 @@ -350,11 +350,10 @@ public override MethodImplAttributes GetMethodImplementationFlags() Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); StackAllocatedByRefs byrefStorage = default; - Span unsafeByrefParameters = new Span(&byrefStorage, 1); CheckArguments( copyOfParameters, - unsafeByrefParameters, + (IntPtr*)&byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, 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 7ad6388a048a9b..c805928e8e2819 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3443,6 +3443,8 @@ public override Type[] GetGenericParameterConstraints() #endregion #region Misc + internal bool IsNullableOfT => Nullable.GetUnderlyingType(this) != null; + public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore(other); public override Type MakePointerType() => new RuntimeTypeHandle(this).MakePointer(); @@ -3494,7 +3496,7 @@ internal bool CheckValue( { Debug.Assert(!ReferenceEquals(value?.GetType(), this)); - // Fast path to whethern a value can be assigned to type. + // Fast path to whether a value can be assigned to type. if (IsInstanceOfType(value)) { // Since this cannot be a generic parameter, we use RuntimeTypeHandle.IsValueType here @@ -3503,6 +3505,14 @@ internal bool CheckValue( if (RuntimeTypeHandle.IsValueType(this)) { + // If a nullable, pass the object even though it's a value type. + if (IsNullableOfT) + { + // Treat as a reference type. + copyBack = false; + return false; + } + // Must be an equivalent type, re-box to the target type object newValue = AllocateValueType(this, value, fForceTypeChange: true); value = newValue; @@ -3567,6 +3577,16 @@ private CheckValueStatus TryChangeType( 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)) { // Need to create an instance of the ByRef if null was provided, but only if primitive, enum or value type @@ -3604,12 +3624,24 @@ private CheckValueStatus TryChangeType( if (value == null) { - if (allowNull) + if (!allowNull) { - if (RuntimeTypeHandle.IsValueType(this)) + isValueType = copyBack = default; + return IsByRefLike ? CheckValueStatus.NotSupported_ByRefLike : CheckValueStatus.ArgumentException; + } + + if (RuntimeTypeHandle.IsValueType(this)) + { + // If a nullable, pass the null as an object even though it's a value type. + if (IsNullableOfT) + { + // Treat as a reference type. + isValueType = false; + copyBack = false; + } + else { isValueType = true; - if (RuntimeTypeHandle.IsByRefLike(this)) { // For a byref-like parameter pass null to the runtime which will create a default value. @@ -3622,17 +3654,14 @@ private CheckValueStatus TryChangeType( copyBack = false; } } - else - { - isValueType = false; - copyBack = false; - } - - return CheckValueStatus.Success; + } + else + { + isValueType = false; + copyBack = false; } - isValueType = copyBack = default; - return IsByRefLike ? CheckValueStatus.NotSupported_ByRefLike : CheckValueStatus.ArgumentException; + return CheckValueStatus.Success; } if (this == s_typedRef) @@ -3674,7 +3703,7 @@ static CheckValueStatus SpecialCast(RuntimeType type, ref object value) if (pointer != null) { - value = pointer.GetPointerValue(); + value = pointer.GetPointerValue(); // Convert source pointer to IntPtr } else { diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index 2675229dbb2ebd..61e7c46b85c2c8 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -124,7 +124,7 @@ void *InvokeUtil::GetIntPtrValue(OBJECTREF pObj) { RETURN *(void **)((pObj)->UnBox()); } -void InvokeUtil::CopyArg(TypeHandle th, PVOID *pArgUnsafe, ArgDestination *argDest) { +void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest) { CONTRACTL { THROWS; GC_NOTRIGGER; // Caller does not protect object references @@ -135,35 +135,24 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID *pArgUnsafe, ArgDestination *argDe CONTRACTL_END; void *pArgDst = argDest->GetDestinationAddress(); + PVOID *rArg = *pArgRef; + CorElementType type = th.GetVerifierCorElementType(); - MethodTable* pMT = th.GetMethodTable(); - CorElementType oType; - CorElementType type; - - oType = pMT->GetInternalCorElementType(); - 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_I4: case ELEMENT_TYPE_BOOLEAN: - case ELEMENT_TYPE_I1: case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I1: case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: - 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:) { - { - _ASSERTE(pArgUnsafe != NULL); - ARG_SLOT slot; - CreatePrimitiveValue(type, oType, pArgUnsafe, pMT, &slot); - *(PVOID *)pArgDst = (PVOID)slot; - } + _ASSERTE(rArg != NULL); + *(PVOID *)pArgDst = (PVOID)*rArg; break; } @@ -173,36 +162,45 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID *pArgUnsafe, ArgDestination *argDe IN_TARGET_64BIT(case ELEMENT_TYPE_I:) IN_TARGET_64BIT(case ELEMENT_TYPE_U:) { - { - _ASSERTE(pArgUnsafe != NULL); - ARG_SLOT slot; - CreatePrimitiveValue(type, oType, pArgUnsafe, pMT, &slot); - *(INT64 *)pArgDst = (INT64)slot; - } + _ASSERTE(rArg != NULL); + *(INT64 *)pArgDst = (INT64)*rArg; break; } case ELEMENT_TYPE_VALUETYPE: { - // pArgUnsafe can be NULL but only for Nullable types; UnBoxIntoArg verifies that. { - if (!th.AsMethodTable()->UnBoxIntoArg(argDest, pArgUnsafe)) - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + if (Nullable::IsNullableType(th)) + { + // ASSUMPTION: we only receive T or NULL values, not Nullable values + // and the values are boxed, unlike other value types. + OBJECTREF src = (OBJECTREF)(Object*)*rArg; + if (!th.AsMethodTable()->UnBoxIntoArg(argDest, src)) + COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + } + else + { + if (rArg == NULL) + COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + + MethodTable* pMT = th.GetMethodTable(); + CopyValueClassArg(argDest, rArg, 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 (pArgUnsafe == NULL) + if (rArg == NULL) *(PVOID *)pArgDst = 0; else - *(PVOID *)pArgDst = OBJECTREFToObject((OBJECTREF)(Object*)*pArgUnsafe); + *(PVOID *)pArgDst = OBJECTREFToObject((OBJECTREF)(Object*)*rArg); break; } @@ -212,57 +210,16 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID *pArgUnsafe, ArgDestination *argDe // heads these off and morphs the type handle to not be byref anymore _ASSERTE(!Nullable::IsNullableType(th.AsTypeDesc()->GetTypeParam())); - *(PVOID *)pArgDst = pArgUnsafe; + *(PVOID *)pArgDst = rArg; 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); - // } - - // break; - //} - case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_FNPTR: { - // If we got the univeral zero...Then assign it and exit. - if (pArgUnsafe == NULL) { - *(PVOID *)pArgDst = 0; - } - else { - OBJECTREF rObj = (OBJECTREF)(Object*)*pArgUnsafe; - 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(rArg != NULL); + MethodTable* pMT = th.GetMethodTable(); + CopyValueClassArg(argDest, rArg, pMT, 0); break; } diff --git a/src/coreclr/vm/invokeutil.h b/src/coreclr/vm/invokeutil.h index 4ff2ca8d80c766..3b1de3b0faa63a 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, PVOID *pArgRef, ArgDestination *argDest); + static void CopyArg(TypeHandle th, PVOID **pArgRef, 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 @@ -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/methodtable.h b/src/coreclr/vm/methodtable.h index 68d5a1bb1a5072..039b5c06e14c86 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -2387,7 +2387,6 @@ class MethodTable #ifndef DACCESS_COMPILE BOOL UnBoxInto(void *dest, OBJECTREF src); BOOL UnBoxIntoArg(ArgDestination *argDest, OBJECTREF src); - BOOL UnBoxIntoArg(ArgDestination *argDest, PVOID pSrcUnsafe); void UnBoxIntoUnchecked(void *dest, OBJECTREF src); #endif diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index 19bc734504f5a8..72b628dae9545a 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -1307,35 +1307,6 @@ inline BOOL MethodTable::UnBoxIntoArg(ArgDestination *argDest, OBJECTREF src) return TRUE; } -inline BOOL MethodTable::UnBoxIntoArg(ArgDestination *argDest, PVOID pSrcUnsafe) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - TypeHandle th = TypeHandle(this); - if (Nullable::IsNullableType(th)) - { - // ASSUMPTION: we only receive T or NULL values, not Nullable values. - TypeHandle thSrc = th.AsTypeDesc()->GetTypeParam(); - MethodTable *srcMT = thSrc.GetMethodTable(); - - return Nullable::UnBoxIntoArgNoGC(argDest, pSrcUnsafe, this, srcMT); - } - else - { - if (pSrcUnsafe == NULL) - return FALSE; - - CopyValueClassArg(argDest, pSrcUnsafe, this, 0); - } - return TRUE; -} - //========================================================================================== // unbox src into dest, No checks are done diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp index 862499ccf2e100..703e1edabf5bf1 100644 --- a/src/coreclr/vm/object.cpp +++ b/src/coreclr/vm/object.cpp @@ -1736,51 +1736,6 @@ BOOL Nullable::UnBoxNoGC(void* destPtr, OBJECTREF boxedVal, MethodTable* destMT) return TRUE; } -BOOL Nullable::UnBoxNoGC(void* destPtr, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - Nullable* dest = (Nullable*) destPtr; - - // We should only get here if we are unboxing a T as a Nullable - _ASSERTE(IsNullableType(destMT)); - - // We better have a concrete instantiation, or our field offset asserts are not useful - _ASSERTE(!destMT->ContainsGenericVariables()); - - if (pValUnsafe == NULL) - { - // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure because it may contain GC references - // and these need to be initialized to zero. (could optimize in the non-GC case) - InitValueClass(destPtr, destMT); - } - else - { - if (!IsNullableForTypeNoGC(destMT, srcMT)) - { - // For safety's sake, also allow true nullables to be unboxed normally. - // This should not happen normally, but we want to be robust - if (destMT == srcMT) - { - CopyValueClass(dest, pValUnsafe, destMT); - return TRUE; - } - return FALSE; - } - - *dest->HasValueAddr(destMT) = true; - CopyValueClass(dest->ValueAddr(destMT), pValUnsafe, srcMT); - } - - return TRUE; -} - //=============================================================================== // Special Logic to unbox a boxed T as a nullable into an argument // specified by the argDest. @@ -1838,48 +1793,6 @@ BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, Met return UnBoxNoGC(argDest->GetDestinationAddress(), boxedVal, destMT); } -BOOL Nullable::UnBoxIntoArgNoGC(ArgDestination *argDest, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - -#if defined(UNIX_AMD64_ABI) - if (argDest->IsStructPassedInRegs()) - { - // We should only get here if we are unboxing a T as a Nullable - _ASSERTE(IsNullableType(destMT)); - - // We better have a concrete instantiation, or our field offset asserts are not useful - _ASSERTE(!destMT->ContainsGenericVariables()); - - if (pValUnsafe == NULL) - { - // Logically we are doing *dest->HasValueAddr(destMT) = false; - // We zero out the whole structure because it may contain GC references - // and these need to be initialized to zero. (could optimize in the non-GC case) - InitValueClassArg(argDest, destMT); - } - else - { - Nullable* dest = (Nullable*)argDest->GetStructGenRegDestinationAddress(); - *dest->HasValueAddr(destMT) = true; - int destOffset = (BYTE*)dest->ValueAddr(destMT) - (BYTE*)dest; - CopyValueClassArg(argDest, pValUnsafe, srcMT, destOffset); - } - - return TRUE; - } - -#endif // UNIX_AMD64_ABI - - return UnBoxNoGC(argDest->GetDestinationAddress(), pValUnsafe, destMT, srcMT); -} - //=============================================================================== // Special Logic to unbox a boxed T as a nullable // Does not do any type checks. diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 92c3ebfcf395f8..b98b520d4aded7 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -2669,9 +2669,7 @@ class Nullable { static OBJECTREF Box(void* src, MethodTable* nullable); static BOOL UnBox(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static BOOL UnBoxNoGC(void* dest, OBJECTREF boxedVal, MethodTable* destMT); - static BOOL UnBoxNoGC(void* dest, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT); static BOOL UnBoxIntoArgNoGC(ArgDestination *argDest, OBJECTREF boxedVal, MethodTable* destMT); - static BOOL UnBoxIntoArgNoGC(ArgDestination *argDest, PVOID pValUnsafe, MethodTable* destMT, MethodTable* srcMT); static void UnBoxNoCheck(void* dest, OBJECTREF boxedVal, MethodTable* destMT); static OBJECTREF BoxedNullableNull(TypeHandle nullableType) { return 0; } diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 222854d3656e63..4a741a63112423 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -762,10 +762,9 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, UINT structSize = argit.GetArgSize(); bool needsStackCopy = false; - bool needsToInitValueClass = false; - - MethodTable * pMT = th.GetMethodTable(); + ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); + MethodTable* pMT = NULL; TypeHandle nullableType = NullableTypeOfByref(th); if (!nullableType.IsNull()) { // A boxed Nullable is represented as boxed T. So to pass a Nullable by reference, @@ -775,10 +774,11 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, structSize = th.GetSize(); needsStackCopy = true; } - else if (args[i] == NULL && pMT->IsByRefLike()) { - // A byref-like type can't be boxed but the arg value can be passed as a null in which - // case we need to initialize the ref struct. - needsToInitValueClass = true; + else if (args[i] == NULL && ((pMT = th.GetMethodTable())->IsByRefLike())) { + // A byref-like type can't be boxed but the Invoke() arg value can be passed as a null object where that is + // marshalled as a null reference to indicate we need to initialize the byref-like type here. + InitValueClassArg(&argDest, pMT); + continue; } #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE else if (argit.IsArgPassedByRef()) @@ -787,13 +787,13 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } #endif - ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - if (needsStackCopy) { + pMT = th.GetMethodTable(); _ASSERTE(pMT && pMT->IsValueType()); PVOID pArgDst = argDest.GetDestinationAddress(); + PVOID pStackCopy = _alloca(structSize); *(PVOID *)pArgDst = pStackCopy; @@ -812,14 +812,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, argDest = ArgDestination(pStackCopy, 0, NULL); } - if (needsToInitValueClass) - { - InitValueClassArg(&argDest, pMT); - } - else - { - InvokeUtil::CopyArg(th, (PVOID *)args[i], &argDest); - } + InvokeUtil::CopyArg(th, (PVOID **)&(args[i]), &argDest); } ENDFORBIDGC(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs index 3eb3a2a3526837..74cf3c81817815 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/InvokeUtils.cs @@ -9,7 +9,7 @@ namespace System.Reflection { internal static class InvokeUtils { - // This method is similar to the CoreAOT method ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(). + // 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; @@ -117,15 +117,8 @@ private static bool TryConvertPointer(object srcObject, CorElementType srcEEType return true; } - if (srcObject is Pointer srcPointer) - { - //CorElementType dstElementType = RuntimeTypeHandle.GetCorElementType((RuntimeType)typeof(void*)); - //if (dstEEType == dstElementType || RuntimeImports.AreTypesAssignable(pSourceType: srcPointer.GetPointerType().TypeHandle.ToEETypePtr(), pTargetType: dstEEType)) - { - dstIntPtr = srcPointer.GetPointerValue(); - 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 1bde98144de313..2366ae76657c61 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ObjectiveC; using System.Text; namespace System.Reflection @@ -143,7 +142,7 @@ private protected void ValidateInvokeTarget(object? target) private protected unsafe void CheckArguments( Span copyOfParameters, - Span byrefParameters, + IntPtr* byrefParameters, Span shouldCopyBack, ReadOnlySpan parameters, RuntimeType[] sigTypes, @@ -191,26 +190,32 @@ BindingFlags invokeAttr // 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 which should not be applied to the incoming array. shouldCopyBack[i] = copyBackArg; - copyOfParameters[i] = arg; if (isValueType) { - if (arg is null) + if (arg == null) { - // This is a special case where we pass null for a null Nullable and for - // the case to create a default ref struct when 'null' is passed. - Debug.Assert(!copyBackArg); + // Special case when passing a null to signal the native runtime to create a default ref struct + copyOfParameters[i] = null; byrefParameters[i] = IntPtr.Zero; } else { - byrefParameters[i] = (IntPtr)Unsafe.AsPointer(ref copyOfParameters[i]!.GetRawData()); + copyOfParameters[i] = arg; + + ByReference valueTypeRef = new(ref copyOfParameters[i]!.GetRawData()); + *(ByReference*)(byrefParameters + i) = valueTypeRef; } } else { - byrefParameters[i] = (IntPtr)Unsafe.AsPointer(ref copyOfParameters[i]); + copyOfParameters[i] = arg; + + ByReference objRef = new(ref copyOfParameters[i]); + *(ByReference*)(byrefParameters + i) = objRef; } } } 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 3cfe6f6368d330..67dcd8368fa115 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -135,7 +135,6 @@ internal void ThrowNoInvokeException() Debug.Assert(parameters != null); Span copyOfParameters; Span shouldCopyBackParameters; - Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { @@ -144,11 +143,10 @@ internal void ThrowNoInvokeException() shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; - byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( copyOfParameters, - byrefParameters, + (IntPtr*)&byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, @@ -164,9 +162,9 @@ internal void ThrowNoInvokeException() copyOfParameters = new Span(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which - // natually requires a stack size that is dependent on the arg count\size. + // naturally requires a stack size that is dependent on the arg count\size. IntPtr* byrefStorage = stackalloc IntPtr[argCount]; - byrefParameters = new Span(byrefStorage, argCount); + Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); bool* boolHolder = stackalloc bool[argCount]; shouldCopyBackParameters = new Span(boolHolder, argCount); @@ -178,7 +176,7 @@ internal void ThrowNoInvokeException() RegisterForGCReporting(®); CheckArguments( copyOfParameters, - byrefParameters, + byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, @@ -239,7 +237,6 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] Debug.Assert(parameters != null); Span copyOfParameters; Span shouldCopyBackParameters; - Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { @@ -248,11 +245,10 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; - byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( copyOfParameters, - byrefParameters, + (IntPtr*)&byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, @@ -268,9 +264,9 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] copyOfParameters = new Span(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which - // natually requires a stack size that is dependent on the arg count\size. + // naturally requires a stack size that is dependent on the arg count\size. IntPtr* byrefStorage = stackalloc IntPtr[argCount]; - byrefParameters = new Span(byrefStorage, argCount); + Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); bool* boolHolder = stackalloc bool[argCount]; shouldCopyBackParameters = new Span(boolHolder, argCount); @@ -282,7 +278,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] RegisterForGCReporting(®); CheckArguments( copyOfParameters, - byrefParameters, + byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, 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 8ff055b85c9990..5c2ec1c1070ea4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -133,7 +133,6 @@ private void ThrowNoInvokeException() Debug.Assert(parameters != null); Span copyOfParameters; Span shouldCopyBackParameters; - Span byrefParameters; if (argCount <= MaxStackAllocArgCount) { @@ -142,11 +141,10 @@ private void ThrowNoInvokeException() shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); StackAllocatedByRefs byrefStorage = default; - byrefParameters = new Span(&byrefStorage, argCount); CheckArguments( copyOfParameters, - byrefParameters, + (IntPtr*)&byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, @@ -162,9 +160,9 @@ private void ThrowNoInvokeException() copyOfParameters = new Span(objHolder, 0, argCount); // We don't check a max stack size since we are invoking a method which - // natually requires a stack size that is dependent on the arg count\size. + // naturally requires a stack size that is dependent on the arg count\size. IntPtr* byrefStorage = stackalloc IntPtr[argCount]; - byrefParameters = new Span(byrefStorage, argCount); + Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); bool* boolHolder = stackalloc bool[argCount]; shouldCopyBackParameters = new Span(boolHolder, argCount); @@ -176,7 +174,7 @@ private void ThrowNoInvokeException() RegisterForGCReporting(®); CheckArguments( copyOfParameters, - byrefParameters, + byrefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index 3fb402889ed7de..bdc8cfd07a0e86 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -620,23 +620,8 @@ public void ToStringTest_ByMethodInfo(MethodInfo methodInfo, string expected) Assert.Equal(expected, methodInfo.ToString()); } - - //Methods for Reflection Metadata - private void DummyMethod1(string str, int iValue, long lValue) - { - } - - private void DummyMethod2() - { - } - - private static MethodInfo GetMethod(Type type, string name) - { - return type.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).First(method => method.Name.Equals(name)); - } - [Fact] - public static void InvokeNullableRefs() + public void InvokeNullableRefs() { object?[] args; @@ -671,7 +656,7 @@ static MethodInfo GetMethod(string name) => typeof(NullableRefMethods).GetMethod } [Fact] - public static void InvokeBoxedNullableRefs() + public void InvokeBoxedNullableRefs() { object?[] args; @@ -705,7 +690,7 @@ static MethodInfo GetMethod(string name) => typeof(NullableRefMethods).GetMethod } [Fact] - public static void InvokeEnum() + public void InvokeEnum() { // Enums only need to match by primitive type. Assert.True((bool)GetMethod(nameof(EnumMethods.PassColorsInt)). @@ -723,312 +708,327 @@ static MethodInfo GetMethod(string name) => typeof(EnumMethods).GetMethod( name, BindingFlags.Public | BindingFlags.Static)!; } -#pragma warning disable 0414 - public interface MI_Interface + //Methods for Reflection Metadata + private void DummyMethod1(string str, int iValue, long lValue) { - int IMethod(); - int IMethodNew(); } - public class MI_BaseClass : MI_Interface + private void DummyMethod2() { - public int IMethod() => 10; - public int IMethodNew() => 20; - - public static bool StaticIntMethodReturningBool(int int4a) => int4a % 2 == 0; - public virtual int VirtualReturnIntMethod() => 0; - - public virtual int VirtualMethod() => 0; - private int PrivateInstanceMethod() => 21; - public static string PublicStaticMethod(string x) => x; - public string PublicStructMethod(DateTime dt) => dt.ToString(); } - public class MI_SubClass : MI_BaseClass + private static MethodInfo GetMethod(Type type, string name) { - public override int VirtualReturnIntMethod() => 2; + return type.GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).First(method => method.Name.Equals(name)); + } + } - public PublicEnum EnumMethodReturningEnum(PublicEnum myenum) => myenum == PublicEnum.Case1 ? PublicEnum.Case2 : PublicEnum.Case1; - public string ObjectMethodReturningString(object obj) => obj.ToString(); - public int VoidMethodReturningInt() => 3; - public long VoidMethodReturningLong() => long.MaxValue; - public long IntLongMethodReturningLong(int i, long l) => i + l; - public static int StaticIntIntMethodReturningInt(int i1, int i2) => i1 + i2; +#pragma warning disable 0414 + public interface MI_Interface + { + int IMethod(); + int IMethodNew(); + } - public static void StaticGenericMethod(T t) { } + public class MI_BaseClass : MI_Interface + { + public int IMethod() => 10; + public int IMethodNew() => 20; - public new int IMethodNew() => 200; + public static bool StaticIntMethodReturningBool(int int4a) => int4a % 2 == 0; + public virtual int VirtualReturnIntMethod() => 0; - public override int VirtualMethod() => 1; + public virtual int VirtualMethod() => 0; + private int PrivateInstanceMethod() => 21; + public static string PublicStaticMethod(string x) => x; + public string PublicStructMethod(DateTime dt) => dt.ToString(); + } - public void ReturnVoidMethod(DateTime dt) { } - public virtual string[] VirtualReturnStringArrayMethod() => new string[0]; - public virtual bool VirtualReturnBoolMethod() => true; + public class MI_SubClass : MI_BaseClass + { + public override int VirtualReturnIntMethod() => 2; - public string Method2(string t2, T t1, S t3) => ""; + public PublicEnum EnumMethodReturningEnum(PublicEnum myenum) => myenum == PublicEnum.Case1 ? PublicEnum.Case2 : PublicEnum.Case1; + public string ObjectMethodReturningString(object obj) => obj.ToString(); + public int VoidMethodReturningInt() => 3; + public long VoidMethodReturningLong() => long.MaxValue; + public long IntLongMethodReturningLong(int i, long l) => i + l; + public static int StaticIntIntMethodReturningInt(int i1, int i2) => i1 + i2; - public IntPtr ReturnIntPtrMethod() => new IntPtr(200); - public int[] ReturnArrayMethod() => new int[] { 2, 3, 5, 7, 11 }; + public static void StaticGenericMethod(T t) { } - public void GenericMethod1(T t) { } - public void GenericMethod2(T t, U u) { } + public new int IMethodNew() => 200; - public void StringArrayMethod(string[] strArray) { } + public override int VirtualMethod() => 1; - [Attr(77, name = "AttrSimple"), - Int32Attr(77, name = "Int32AttrSimple"), - Int64Attr(77, name = "Int64AttrSimple"), - StringAttr("hello", name = "StringAttrSimple"), - EnumAttr(PublicEnum.Case1, name = "EnumAttrSimple"), - TypeAttr(typeof(object), name = "TypeAttrSimple")] - public void MethodWithAttributes() { } - } + public void ReturnVoidMethod(DateTime dt) { } + public virtual string[] VirtualReturnStringArrayMethod() => new string[0]; + public virtual bool VirtualReturnBoolMethod() => true; - public class MethodInfoDummySubClass : MI_BaseClass - { - public override int VirtualReturnIntMethod() => 1; - } + public string Method2(string t2, T t1, S t3) => ""; - public class MI_Interlocked - { - public static int Increment(ref int location) => 0; - public static int Decrement(ref int location) => 0; - public static int Exchange(ref int location1, int value) => 0; - public static int CompareExchange(ref int location1, int value, int comparand) => 0; + public IntPtr ReturnIntPtrMethod() => new IntPtr(200); + public int[] ReturnArrayMethod() => new int[] { 2, 3, 5, 7, 11 }; - public static float Exchange(ref float location1, float value) => 0; - public static float CompareExchange(ref float location1, float value, float comparand) => 0; + public void GenericMethod1(T t) { } + public void GenericMethod2(T t, U u) { } - public static object Exchange(ref object location1, object value) => null; - public static object CompareExchange(ref object location1, object value, object comparand) => null; - } + public void StringArrayMethod(string[] strArray) { } - public class MI_GenericClass - { - public T GenericMethod1(T t) => t; - public T GenericMethod2(S s1, T t, string s2) => t; - public static S GenericMethod3(S s) => s; - } + [Attr(77, name = "AttrSimple"), + Int32Attr(77, name = "Int32AttrSimple"), + Int64Attr(77, name = "Int64AttrSimple"), + StringAttr("hello", name = "StringAttrSimple"), + EnumAttr(PublicEnum.Case1, name = "EnumAttrSimple"), + TypeAttr(typeof(object), name = "TypeAttrSimple")] + public void MethodWithAttributes() { } + } - public interface MethodInfoBaseDefinitionInterface - { - void InterfaceMethod1(); - void InterfaceMethod2(); - } + public class MethodInfoDummySubClass : MI_BaseClass + { + public override int VirtualReturnIntMethod() => 1; + } - public class MethodInfoBaseDefinitionBaseClass : MethodInfoBaseDefinitionInterface - { - public void InterfaceMethod1() { } - void MethodInfoBaseDefinitionInterface.InterfaceMethod2() { } + public class MI_Interlocked + { + public static int Increment(ref int location) => 0; + public static int Decrement(ref int location) => 0; + public static int Exchange(ref int location1, int value) => 0; + public static int CompareExchange(ref int location1, int value, int comparand) => 0; - public virtual void BaseClassVirtualMethod() { } - public virtual void BaseClassMethod() { } + public static float Exchange(ref float location1, float value) => 0; + public static float CompareExchange(ref float location1, float value, float comparand) => 0; - public override string ToString() => base.ToString(); - } + public static object Exchange(ref object location1, object value) => null; + public static object CompareExchange(ref object location1, object value, object comparand) => null; + } - public class MethodInfoBaseDefinitionSubClass : MethodInfoBaseDefinitionBaseClass - { - public override void BaseClassVirtualMethod() => base.BaseClassVirtualMethod(); - public new void BaseClassMethod() { } - public override string ToString() => base.ToString(); + public class MI_GenericClass + { + public T GenericMethod1(T t) => t; + public T GenericMethod2(S s1, T t, string s2) => t; + public static S GenericMethod3(S s) => s; + } - public void DerivedClassMethod() { } - } + public interface MethodInfoBaseDefinitionInterface + { + void InterfaceMethod1(); + void InterfaceMethod2(); + } + + public class MethodInfoBaseDefinitionBaseClass : MethodInfoBaseDefinitionInterface + { + public void InterfaceMethod1() { } + void MethodInfoBaseDefinitionInterface.InterfaceMethod2() { } + + public virtual void BaseClassVirtualMethod() { } + public virtual void BaseClassMethod() { } + + public override string ToString() => base.ToString(); + } + + public class MethodInfoBaseDefinitionSubClass : MethodInfoBaseDefinitionBaseClass + { + public override void BaseClassVirtualMethod() => base.BaseClassVirtualMethod(); + public new void BaseClassMethod() { } + public override string ToString() => base.ToString(); + + public void DerivedClassMethod() { } + } - public abstract class MI_AbstractBaseClass + public abstract class MI_AbstractBaseClass + { + public abstract void AbstractMethod(); + public virtual void VirtualMethod() { } + } + + public class MI_AbstractSubClass : MI_AbstractBaseClass + { + public sealed override void VirtualMethod() { } + public override void AbstractMethod() { } + } + + public interface MethodInfoDefaultParametersInterface + { + string InterfaceMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m); + } + + public class MethodInfoDefaultParameters : MethodInfoDefaultParametersInterface + { + public int Integer(int parameter = 1) { - public abstract void AbstractMethod(); - public virtual void VirtualMethod() { } + return parameter; } - public class MI_AbstractSubClass : MI_AbstractBaseClass + public string AllPrimitives( + bool boolean = true, + string str = "test", + char character = 'c', + byte unsignedbyte = 2, + sbyte signedbyte = -1, + short int16 = -3, + ushort uint16 = 4, + int int32 = -5, + uint uint32 = 6, + long int64 = -7, + ulong uint64 = 8, + float single = 9.1f, + double dbl = 11.12) { - public sealed override void VirtualMethod() { } - public override void AbstractMethod() { } + return FormattableString.Invariant($"{boolean}, {str}, {character}, {unsignedbyte}, {signedbyte}, {int16}, {uint16}, {int32}, {uint32}, {int64}, {uint64}, {single}, {dbl}"); } - public interface MethodInfoDefaultParametersInterface + public string String(string parameter = "test") => parameter; + + public class CustomReferenceType { - string InterfaceMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m); + public override bool Equals(object obj) => ReferenceEquals(this, obj); + public override int GetHashCode() => 0; } - public class MethodInfoDefaultParameters : MethodInfoDefaultParametersInterface - { - public int Integer(int parameter = 1) - { - return parameter; - } + public CustomReferenceType Reference(CustomReferenceType parameter = null) => parameter; - public string AllPrimitives( - bool boolean = true, - string str = "test", - char character = 'c', - byte unsignedbyte = 2, - sbyte signedbyte = -1, - short int16 = -3, - ushort uint16 = 4, - int int32 = -5, - uint uint32 = 6, - long int64 = -7, - ulong uint64 = 8, - float single = 9.1f, - double dbl = 11.12) - { - return FormattableString.Invariant($"{boolean}, {str}, {character}, {unsignedbyte}, {signedbyte}, {int16}, {uint16}, {int32}, {uint32}, {int64}, {uint64}, {single}, {dbl}"); - } + public struct CustomValueType + { + public int Id; + public override bool Equals(object obj) => Id == ((CustomValueType)obj).Id; + public override int GetHashCode() => Id.GetHashCode(); + } - public string String(string parameter = "test") => parameter; + public CustomValueType ValueType(CustomValueType parameter = default(CustomValueType)) => parameter; - public class CustomReferenceType - { - public override bool Equals(object obj) => ReferenceEquals(this, obj); - public override int GetHashCode() => 0; - } + public DateTime DateTime([DateTimeConstant(42)] DateTime parameter) => parameter; - public CustomReferenceType Reference(CustomReferenceType parameter = null) => parameter; + public decimal DecimalWithAttribute([DecimalConstant(1, 1, 2, 3, 4)] decimal parameter) => parameter; - public struct CustomValueType - { - public int Id; - public override bool Equals(object obj) => Id == ((CustomValueType)obj).Id; - public override int GetHashCode() => Id.GetHashCode(); - } + public decimal Decimal(decimal parameter = 3.14m) => parameter; - public CustomValueType ValueType(CustomValueType parameter = default(CustomValueType)) => parameter; + public int? NullableInt(int? parameter = null) => parameter; - public DateTime DateTime([DateTimeConstant(42)] DateTime parameter) => parameter; + public PublicEnum Enum(PublicEnum parameter = PublicEnum.Case1) => parameter; - public decimal DecimalWithAttribute([DecimalConstant(1, 1, 2, 3, 4)] decimal parameter) => parameter; + string MethodInfoDefaultParametersInterface.InterfaceMethod(int p1, string p2, decimal p3) + { + return FormattableString.Invariant($"{p1}, {p2}, {p3}"); + } - public decimal Decimal(decimal parameter = 3.14m) => parameter; + public static string StaticMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m) + { + return FormattableString.Invariant($"{p1}, {p2}, {p3}"); + } - public int? NullableInt(int? parameter = null) => parameter; + public object OptionalObjectParameter([Optional] object parameter) => parameter; + public string OptionalStringParameter([Optional] string parameter) => parameter; + } - public PublicEnum Enum(PublicEnum parameter = PublicEnum.Case1) => parameter; + public delegate int Delegate_TC_Int(MI_BaseClass tc); + public delegate int Delegate_Void_Int(); + public delegate string Delegate_Str_Str(string x); + public delegate string Delegate_Void_Str(); + public delegate string Delegate_DateTime_Str(MI_BaseClass tc, DateTime dt); - string MethodInfoDefaultParametersInterface.InterfaceMethod(int p1, string p2, decimal p3) - { - return FormattableString.Invariant($"{p1}, {p2}, {p3}"); - } + public delegate T Delegate_GC_T_T(MI_GenericClass gc, T x); + public delegate T Delegate_T_T(T x); + public delegate T Delegate_Void_T(); - public static string StaticMethod(int p1 = 1, string p2 = "test", decimal p3 = 3.14m) - { - return FormattableString.Invariant($"{p1}, {p2}, {p3}"); - } + public class DummyClass { } - public object OptionalObjectParameter([Optional] object parameter) => parameter; - public string OptionalStringParameter([Optional] string parameter) => parameter; + public class Sample + { + public string Method1(DateTime t) + { + return ""; } - - public delegate int Delegate_TC_Int(MI_BaseClass tc); - public delegate int Delegate_Void_Int(); - public delegate string Delegate_Str_Str(string x); - public delegate string Delegate_Void_Str(); - public delegate string Delegate_DateTime_Str(MI_BaseClass tc, DateTime dt); - - public delegate T Delegate_GC_T_T(MI_GenericClass gc, T x); - public delegate T Delegate_T_T(T x); - public delegate T Delegate_Void_T(); - - public class DummyClass { } - - public class Sample + public string Method2(string t2, T t1, S t3) { - public string Method1(DateTime t) - { - return ""; - } - public string Method2(string t2, T t1, S t3) - { - return ""; - } + return ""; } + } - public class SampleG + public class SampleG + { + public T Method1(T t) { - public T Method1(T t) - { - return t; - } - public T Method2(S t1, T t2, string t3) - { - return t2; - } + return t; } - - private static class NullableRefMethods + public T Method2(S t1, T t2, string t3) { - 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; - } + return t2; + } + } - public static bool NullToValueBoxed(ref object? i, int value) - { - Assert.Null(i); - i = value; - return true; - } + public static class NullableRefMethods + { + public static bool Null(ref int? i) + { + Assert.Null(i); + return true; + } - public static bool ValueToNull(ref int? i, int expected) - { - Assert.Equal(expected, i); - i = null; - return true; - } + public static bool NullBoxed(ref object? i) + { + Assert.Null(i); + return true; + } - public static bool ValueToNullBoxed(ref int? i, int expected) - { - Assert.Equal(expected, i); - i = null; - return true; - } + public static bool NullToValue(ref int? i, int value) + { + Assert.Null(i); + i = value; + return true; } - private enum ColorsInt : int + public static bool NullToValueBoxed(ref object? i, int value) { - Red = 1 + Assert.Null(i); + i = value; + return true; } - private enum ColorsShort : short + public static bool ValueToNull(ref int? i, int expected) { - Red = 1 + Assert.Equal(expected, i); + i = null; + return true; } - private enum OtherColorsInt : int + public static bool ValueToNullBoxed(ref int? i, int expected) { - Red = 1 + Assert.Equal(expected, i); + i = null; + return true; } + } - private static class EnumMethods + 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) { - public static bool PassColorsInt(ColorsInt color) - { - Assert.Equal(ColorsInt.Red, color); - return true; - } + Assert.Equal(ColorsInt.Red, color); + return true; + } - public static bool PassColorsShort(ColorsShort color) - { - Assert.Equal(ColorsShort.Red, color); - return true; - } + public static bool PassColorsShort(ColorsShort color) + { + Assert.Equal(ColorsShort.Red, color); + return true; } -#pragma warning restore 0414 } +#pragma warning restore 0414 } + 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 36b9b0ac99eca9..da434bfa48de64 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -279,7 +279,7 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, { RuntimeType fieldType = (RuntimeType)FieldType; bool _ = false; - fieldType.CheckValue(ref val, ref _, binder, culture, invokeAttr); + fieldType.CheckValue(ref val, ref _, default, binder, culture, invokeAttr); } Invoker.SetValue(obj, val); 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 1f6d71e151bbde..cf286090ef0553 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1666,15 +1666,20 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) } } - internal void CheckValue( + /// + /// 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, + ParameterInfo? _, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) { if (TryConvertToType(ref value, ref copyBack)) - return; + return false; if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); @@ -1683,7 +1688,7 @@ internal void CheckValue( { value = binder.ChangeType(value!, this, culture); copyBack = true; - return; + return false; } throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); From 3635c16d922dd1273ba96a1b2f9c18e57070d5b4 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sun, 27 Mar 2022 16:48:47 -0500 Subject: [PATCH 11/28] Fix merge issue --- src/coreclr/vm/reflectioninvocation.cpp | 418 ------------------------ 1 file changed, 418 deletions(-) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 4a741a63112423..7c4946ac1913d7 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -905,424 +905,6 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } FCIMPLEND -<<<<<<< HEAD -FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, - Object *target, - Span* objs, - SignatureNative* pSigUNSAFE, - CLR_BOOL fConstructor, - CLR_BOOL* pRethrow) -{ - FCALL_CONTRACT; - - struct { - OBJECTREF target; - SIGNATURENATIVEREF pSig; - OBJECTREF retVal; - } gc; - - gc.target = ObjectToOBJECTREF(target); - gc.pSig = (SIGNATURENATIVEREF)pSigUNSAFE; - gc.retVal = NULL; - - *pRethrow = TRUE; - MethodDesc* pMeth = gc.pSig->GetMethod(); - TypeHandle ownerType = gc.pSig->GetDeclaringType(); - - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); - - if (ownerType.IsSharedByGenericInstantiations()) - { - COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); - } - -#ifdef _DEBUG - if (g_pConfig->ShouldInvokeHalt(pMeth)) - { - _ASSERTE(!"InvokeHalt"); - } -#endif - - BOOL fCtorOfVariableSizedObject = FALSE; - - if (fConstructor) - { - // If we are invoking a constructor on an array then we must - // handle this specially. - if (ownerType.IsArray()) { - gc.retVal = InvokeArrayConstructor(ownerType, - objs, - gc.pSig->NumFixedArgs()); - goto Done; - } - - // Variable sized objects, like String instances, allocate themselves - // so they are a special case. - MethodTable * pMT = ownerType.AsMethodTable(); - fCtorOfVariableSizedObject = pMT->HasComponentSize(); - if (!fCtorOfVariableSizedObject) - gc.retVal = pMT->Allocate(); - } - - { - ArgIteratorForMethodInvoke argit(&gc.pSig, fCtorOfVariableSizedObject); - - if (argit.IsActivationNeeded()) - pMeth->EnsureActive(); - CONSISTENCY_CHECK(pMeth->CheckActivated()); - - UINT nStackBytes = argit.SizeOfFrameArgumentArray(); - - // Note that SizeOfFrameArgumentArray does overflow checks with sufficient margin to prevent overflows here - SIZE_T nAllocaSize = TransitionBlock::GetNegSpaceSize() + sizeof(TransitionBlock) + nStackBytes; - - Thread * pThread = GET_THREAD(); - - LPBYTE pAlloc = (LPBYTE)_alloca(nAllocaSize); - - LPBYTE pTransitionBlock = pAlloc + TransitionBlock::GetNegSpaceSize(); - - CallDescrData callDescrData; - - callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); - _ASSERTE((nStackBytes % TARGET_POINTER_SIZE) == 0); - callDescrData.numStackSlots = nStackBytes / TARGET_POINTER_SIZE; -#ifdef CALLDESCR_ARGREGS - callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); -#endif -#ifdef CALLDESCR_RETBUFFARGREG - callDescrData.pRetBuffArg = (UINT64*)(pTransitionBlock + TransitionBlock::GetOffsetOfRetBuffArgReg()); -#endif -#ifdef CALLDESCR_FPARGREGS - callDescrData.pFloatArgumentRegisters = NULL; -#endif -#ifdef CALLDESCR_REGTYPEMAP - callDescrData.dwRegTypeMap = 0; -#endif - callDescrData.fpReturnSize = argit.GetFPReturnSize(); - - // This is duplicated logic from MethodDesc::GetCallTarget - PCODE pTarget; - if (pMeth->IsVtableMethod()) - { - pTarget = pMeth->GetSingleCallableAddrOfVirtualizedCode(&gc.target, ownerType); - } - else - { - pTarget = pMeth->GetSingleCallableAddrOfCode(); - } - callDescrData.pTarget = pTarget; - - // Build the arguments on the stack - - GCStress::MaybeTrigger(); - - FrameWithCookie *pProtectValueClassFrame = NULL; - ValueClassInfo *pValueClasses = NULL; - ByRefToNullable* byRefToNullables = NULL; - - // if we have the magic Value Class return, we need to allocate that class - // and place a pointer to it on the stack. - - BOOL hasRefReturnAndNeedsBoxing = FALSE; // Indicates that the method has a BYREF return type and the target type needs to be copied into a preallocated boxed object. - - TypeHandle retTH = gc.pSig->GetReturnTypeHandle(); - - TypeHandle refReturnTargetTH; // Valid only if retType == ELEMENT_TYPE_BYREF. Caches the TypeHandle of the byref target. - BOOL fHasRetBuffArg = argit.HasRetBuffArg(); - CorElementType retType = retTH.GetSignatureCorElementType(); - BOOL hasValueTypeReturn = retTH.IsValueType() && retType != ELEMENT_TYPE_VOID; - _ASSERTE(hasValueTypeReturn || !fHasRetBuffArg); // only valuetypes are returned via a return buffer. - if (hasValueTypeReturn) { - gc.retVal = retTH.GetMethodTable()->Allocate(); - } - else if (retType == ELEMENT_TYPE_BYREF) - { - refReturnTargetTH = retTH.AsTypeDesc()->GetTypeParam(); - - // If the target of the byref is a value type, we need to preallocate a boxed object to hold the managed return value. - if (refReturnTargetTH.IsValueType()) - { - _ASSERTE(refReturnTargetTH.GetSignatureCorElementType() != ELEMENT_TYPE_VOID); // Managed Reflection layer has a bouncer for "ref void" returns. - hasRefReturnAndNeedsBoxing = TRUE; - gc.retVal = refReturnTargetTH.GetMethodTable()->Allocate(); - } - } - - // Copy "this" pointer - if (!pMeth->IsStatic() && !fCtorOfVariableSizedObject) { - PVOID pThisPtr; - - if (fConstructor) - { - // Copy "this" pointer: only unbox if type is value type and method is not unboxing stub - if (ownerType.IsValueType() && !pMeth->IsUnboxingStub()) { - // Note that we create a true boxed nullabe and then convert it to a T below - pThisPtr = gc.retVal->GetData(); - } - else - pThisPtr = OBJECTREFToObject(gc.retVal); - } - else - if (!pMeth->GetMethodTable()->IsValueType()) - pThisPtr = OBJECTREFToObject(gc.target); - else { - if (pMeth->IsUnboxingStub()) - pThisPtr = OBJECTREFToObject(gc.target); - else { - // Create a true boxed Nullable and use that as the 'this' pointer. - // since what is passed in is just a boxed T - MethodTable* pMT = pMeth->GetMethodTable(); - if (Nullable::IsNullableType(pMT)) { - OBJECTREF bufferObj = pMT->Allocate(); - void* buffer = bufferObj->GetData(); - Nullable::UnBox(buffer, gc.target, pMT); - pThisPtr = buffer; - } - else - pThisPtr = gc.target->UnBox(); - } - } - - *((LPVOID*) (pTransitionBlock + argit.GetThisOffset())) = pThisPtr; - } - - // NO GC AFTER THIS POINT. The object references in the method frame are not protected. - // - // We have already copied "this" pointer so we do not want GC to happen even sooner. Unfortunately, - // we may allocate in the process of copying this pointer that makes it hard to express using contracts. - // - // If an exception occurs a gc may happen but we are going to dump the stack anyway and we do - // not need to protect anything. - - { - BEGINFORBIDGC(); -#ifdef _DEBUG - GCForbidLoaderUseHolder forbidLoaderUse; -#endif - - // Take care of any return arguments - if (fHasRetBuffArg) - { - PVOID pRetBuff = gc.retVal->GetData(); - *((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pRetBuff; - } - - // copy args - UINT nNumArgs = gc.pSig->NumFixedArgs(); - for (UINT i = 0 ; i < nNumArgs; i++) { - - TypeHandle th = gc.pSig->GetArgumentAt(i); - - int ofs = argit.GetNextOffset(); - _ASSERTE(ofs != TransitionBlock::InvalidOffset); - -#ifdef CALLDESCR_REGTYPEMAP - FillInRegTypeMap(ofs, argit.GetArgType(), (BYTE *)&callDescrData.dwRegTypeMap); -#endif - -#ifdef CALLDESCR_FPARGREGS - // Under CALLDESCR_FPARGREGS -ve offsets indicate arguments in floating point registers. If we have at - // least one such argument we point the call worker at the floating point area of the frame (we leave - // it null otherwise since the worker can perform a useful optimization if it knows no floating point - // registers need to be set up). - - if (TransitionBlock::HasFloatRegister(ofs, argit.GetArgLocDescForStructInRegs()) && - (callDescrData.pFloatArgumentRegisters == NULL)) - { - callDescrData.pFloatArgumentRegisters = (FloatArgumentRegisters*) (pTransitionBlock + - TransitionBlock::GetOffsetOfFloatArgumentRegisters()); - } -#endif - - UINT structSize = argit.GetArgSize(); - - bool needsStackCopy = false; - - // 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()) { - th = nullableType; - structSize = th.GetSize(); - needsStackCopy = true; - } -#ifdef ENREGISTERED_PARAMTYPE_MAXSIZE - else if (argit.IsArgPassedByRef()) - { - needsStackCopy = true; - } -#endif - - ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - - if(needsStackCopy) - { - MethodTable * pMT = th.GetMethodTable(); - _ASSERTE(pMT && pMT->IsValueType()); - - PVOID pArgDst = argDest.GetDestinationAddress(); - - PVOID pStackCopy = _alloca(structSize); - *(PVOID *)pArgDst = pStackCopy; - pArgDst = pStackCopy; - - if (!nullableType.IsNull()) - { - byRefToNullables = new(_alloca(sizeof(ByRefToNullable))) ByRefToNullable(i, pStackCopy, nullableType, byRefToNullables); - } - - // save the info into ValueClassInfo - if (pMT->ContainsPointers()) - { - pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pStackCopy, pMT, pValueClasses); - } - - // We need a new ArgDestination that points to the stack copy - argDest = ArgDestination(pStackCopy, 0, NULL); - } - - InvokeUtil::CopyArg(th, &objs->GetAt(i), &argDest); - } - - ENDFORBIDGC(); - } - - if (pValueClasses != NULL) - { - pProtectValueClassFrame = new (_alloca (sizeof (FrameWithCookie))) - FrameWithCookie(pThread, pValueClasses); - } - - // Call the method -<<<<<<< HEAD - 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); - } -======= - *pRethrow = FALSE; - CallDescrWorkerWithHandler(&callDescrData); - *pRethrow = TRUE; ->>>>>>> 7014012a69b (Refactoring and perf of reflection Invoke) - - // It is still illegal to do a GC here. The return type might have/contain GC pointers. - if (fConstructor) - { - // We have a special case for Strings...The object is returned... - if (fCtorOfVariableSizedObject) { - PVOID pReturnValue = &callDescrData.returnValue; - gc.retVal = *(OBJECTREF *)pReturnValue; - } - - // If it is a Nullable, box it using Nullable conventions. - // TODO: this double allocates on constructions which is wasteful - gc.retVal = Nullable::NormalizeBox(gc.retVal); - } - else - if (hasValueTypeReturn || hasRefReturnAndNeedsBoxing) - { - _ASSERTE(gc.retVal != NULL); - - if (hasRefReturnAndNeedsBoxing) - { - // Method has BYREF return and the target type is one that needs boxing. We need to copy into the boxed object we have allocated for this purpose. - LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; - if (pReturnedReference == NULL) - { - COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); - } - CopyValueClass(gc.retVal->GetData(), pReturnedReference, gc.retVal->GetMethodTable()); - } - // if the structure is returned by value, then we need to copy in the boxed object - // we have allocated for this purpose. - else if (!fHasRetBuffArg) - { - CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable()); - } - // From here on out, it is OK to have GCs since the return object (which may have had - // GC pointers has been put into a GC object and thus protected. - - // TODO this creates two objects which is inefficient - // If the return type is a Nullable box it into the correct form - gc.retVal = Nullable::NormalizeBox(gc.retVal); - } - else if (retType == ELEMENT_TYPE_BYREF) - { - // WARNING: pReturnedReference is an unprotected inner reference so we must not trigger a GC until the referenced value has been safely captured. - LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue; - if (pReturnedReference == NULL) - { - COMPlusThrow(kNullReferenceException, W("NullReference_InvokeNullRefReturned")); - } - - gc.retVal = InvokeUtil::CreateObjectAfterInvoke(refReturnTargetTH, pReturnedReference); - } - else - { - gc.retVal = InvokeUtil::CreateObjectAfterInvoke(retTH, &callDescrData.returnValue); - } - - while (byRefToNullables != NULL) { - OBJECTREF obj = Nullable::Box(byRefToNullables->data, byRefToNullables->type.GetMethodTable()); - SetObjectReference(&objs->GetAt(byRefToNullables->argNum), obj); - byRefToNullables = byRefToNullables->next; - } - - if (pProtectValueClassFrame != NULL) - pProtectValueClassFrame->Pop(pThread); - - } - -Done: - ; - HELPER_METHOD_FRAME_END(); - - return OBJECTREFToObject(gc.retVal); -} -FCIMPLEND - -======= ->>>>>>> 96d47f8bff6 (Remove scaffolding; remove Assert) struct SkipStruct { StackCrawlMark* pStackMark; MethodDesc* pMeth; From 408fea3bf72fe74ee9efcdd305c0c737ebfb9b43 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sun, 27 Mar 2022 19:04:22 -0500 Subject: [PATCH 12/28] Misc mono and caching --- .../src/System/RuntimeType.CoreCLR.cs | 22 ++++++++----------- .../src/System/RuntimeType.Mono.cs | 7 ++++-- 2 files changed, 14 insertions(+), 15 deletions(-) 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 c805928e8e2819..47ca6f453f7d49 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -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,7 +3447,7 @@ public override Type[] GetGenericParameterConstraints() #endregion #region Misc - internal bool IsNullableOfT => Nullable.GetUnderlyingType(this) != null; + internal bool IsNullableOfT => Cache.IsNullableOfT; public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore(other); @@ -3524,7 +3528,7 @@ internal bool CheckValue( } bool isValueType; - CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo, allowNull: true); + CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo); if (result == CheckValueStatus.Success) { return isValueType; @@ -3544,7 +3548,7 @@ internal bool CheckValue( return IsValueType; } - result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo, allowNull: false); + result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo); if (result == CheckValueStatus.Success) { return isValueType; @@ -3569,12 +3573,10 @@ private CheckValueStatus TryChangeType( ref object? value, out bool copyBack, out bool isValueType, - ParameterInfo? paramInfo, - bool allowNull) + ParameterInfo? paramInfo) { // If this is a ByRef get the element type and check if it's compatible - bool isByRef = IsByRef; - if (isByRef) + if (IsByRef) { RuntimeType sigElementType = RuntimeTypeHandle.GetElementType(this); @@ -3624,12 +3626,6 @@ private CheckValueStatus TryChangeType( if (value == null) { - if (!allowNull) - { - isValueType = copyBack = default; - return IsByRefLike ? CheckValueStatus.NotSupported_ByRefLike : CheckValueStatus.ArgumentException; - } - if (RuntimeTypeHandle.IsValueType(this)) { // If a nullable, pass the null as an object even though it's a value type. 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 cf286090ef0553..2a0e5120703043 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1679,7 +1679,7 @@ internal bool CheckValue( BindingFlags invokeAttr) { if (TryConvertToType(ref value, ref copyBack)) - return false; + return true; if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); @@ -1688,7 +1688,7 @@ internal bool CheckValue( { value = binder.ChangeType(value!, this, culture); copyBack = true; - return false; + return true; } throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); @@ -1710,7 +1710,10 @@ private bool TryConvertToType(ref object? value, ref bool copyBack) } if (value == null) + { + copyBack = true; return true; + } if (IsEnum) { From 1ab5f601074a8cb4787ec85c4b4a745c5a9ce379 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 28 Mar 2022 13:28:16 -0500 Subject: [PATCH 13/28] Address some CI issues --- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 21 ------------------- src/coreclr/vm/invokeutil.cpp | 15 +++++++++---- src/coreclr/vm/reflectioninvocation.cpp | 9 +------- .../src/System/Reflection/MethodBase.cs | 2 +- .../src/System/RuntimeType.Mono.cs | 12 ++++------- 5 files changed, 17 insertions(+), 42 deletions(-) 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 0d8e209681ee66..361788a61e7207 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 @@ -369,27 +369,6 @@ public override MethodImplAttributes GetMethodImplementationFlags() internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { - if ((invokeAttr & BindingFlags.SuppressChangeType) == 0) - { - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) - { - bool rethrow = false; - - try - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); - } - catch (Exception e) when (!rethrow) - { - throw new TargetInvocationException(e); - } - } - else - { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); - } - } - if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { bool rethrow = false; diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index 61e7c46b85c2c8..e0ea05d8099cf4 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -174,15 +174,22 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest { // 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*)*rArg; - if (!th.AsMethodTable()->UnBoxIntoArg(argDest, src)) + if (!pMT->UnBoxIntoArg(argDest, src)) + COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + } + else if (rArg == NULL) { + // A byref-like type can't be boxed but the Invoke() arg value can be passed as a null object where that is + // marshalled as a null reference to indicate we need to initialize the byref-like type here. + MethodTable* pMT = th.GetMethodTable(); + if (pMT->IsByRefLike()) + InitValueClassArg(argDest, pMT); + else COMPlusThrow(kArgumentException, W("Arg_ObjObj")); } else { - if (rArg == NULL) - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); - MethodTable* pMT = th.GetMethodTable(); CopyValueClassArg(argDest, rArg, pMT, 0); } diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 7c4946ac1913d7..5e65405d709ec6 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -764,7 +764,6 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, bool needsStackCopy = false; ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); - MethodTable* pMT = NULL; TypeHandle nullableType = NullableTypeOfByref(th); if (!nullableType.IsNull()) { // A boxed Nullable is represented as boxed T. So to pass a Nullable by reference, @@ -774,12 +773,6 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, structSize = th.GetSize(); needsStackCopy = true; } - else if (args[i] == NULL && ((pMT = th.GetMethodTable())->IsByRefLike())) { - // A byref-like type can't be boxed but the Invoke() arg value can be passed as a null object where that is - // marshalled as a null reference to indicate we need to initialize the byref-like type here. - InitValueClassArg(&argDest, pMT); - continue; - } #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE else if (argit.IsArgPassedByRef()) { @@ -789,7 +782,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, if (needsStackCopy) { - pMT = th.GetMethodTable(); + MethodTable* pMT = th.GetMethodTable(); _ASSERTE(pMT && pMT->IsValueType()); PVOID pArgDst = argDest.GetDestinationAddress(); 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 2366ae76657c61..609e83fed72293 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -199,7 +199,7 @@ BindingFlags invokeAttr if (arg == null) { // Special case when passing a null to signal the native runtime to create a default ref struct - copyOfParameters[i] = null; + Debug.Assert(copyOfParameters[i] == null); byrefParameters[i] = IntPtr.Zero; } else 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 2a0e5120703043..b725fe62765512 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1678,7 +1678,9 @@ internal bool CheckValue( CultureInfo? culture, BindingFlags invokeAttr) { - if (TryConvertToType(ref value, ref copyBack)) + copyBack = true; + + if (TryConvertToType(ref value)) return true; if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) @@ -1687,14 +1689,13 @@ internal bool CheckValue( if (binder != null && binder != DefaultBinder) { value = binder.ChangeType(value!, this, culture); - copyBack = true; return true; } throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); } - private bool TryConvertToType(ref object? value, ref bool copyBack) + private bool TryConvertToType(ref object? value) { if (IsInstanceOfType(value)) return true; @@ -1704,14 +1705,12 @@ private bool TryConvertToType(ref object? value, ref bool copyBack) Type? elementType = GetElementType(); if (value == null || elementType.IsInstanceOfType(value)) { - copyBack = true; return true; } } if (value == null) { - copyBack = true; return true; } @@ -1725,7 +1724,6 @@ private bool TryConvertToType(ref object? value, ref bool copyBack) if (res != null) { value = res; - copyBack = true; return true; } } @@ -1735,7 +1733,6 @@ private bool TryConvertToType(ref object? value, ref bool copyBack) if (res != null) { value = res; - copyBack = true; return true; } } @@ -1750,7 +1747,6 @@ private bool TryConvertToType(ref object? value, ref bool copyBack) if (pointerType == this) { value = pointer.GetPointerValue(); - copyBack = true; return true; } } From 8e9e2a3b41fd0dcd7032fba14908a1c77cb79744 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 28 Mar 2022 18:12:22 -0500 Subject: [PATCH 14/28] Disable new Nullable tests on Mono --- src/libraries/System.Reflection/tests/MethodInfoTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index bdc8cfd07a0e86..890befefd88e48 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -620,6 +620,7 @@ 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() { @@ -655,6 +656,7 @@ 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() { From 65ccbee51bbecf1b484fde7f9de6d4d410b87120 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 28 Mar 2022 20:33:05 -0500 Subject: [PATCH 15/28] Assume no padding on arg values --- .../System/Reflection/Emit/DynamicMethod.cs | 2 ++ .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 2 ++ src/coreclr/vm/invokeutil.cpp | 18 ++++++++++++------ .../Reflection/RuntimeMethodInfo.Mono.cs | 2 ++ 4 files changed, 18 insertions(+), 6 deletions(-) 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 adb97d4a9cbf31..a2229decd3142a 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 @@ -555,6 +555,8 @@ Signature LazyCreateSignature() return retValue; } + [DebuggerHidden] + [DebuggerStepThrough] internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, BindingFlags invokeAttr) { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) 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 361788a61e7207..72433e99d8f921 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 @@ -367,6 +367,8 @@ public override MethodImplAttributes GetMethodImplementationFlags() return retValue; } + [DebuggerHidden] + [DebuggerStepThrough] internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* arguments, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index e0ea05d8099cf4..8e82bd82c89d3c 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -139,22 +139,28 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest CorElementType type = th.GetVerifierCorElementType(); switch (type) { - case ELEMENT_TYPE_I4: case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: case ELEMENT_TYPE_U1: case ELEMENT_TYPE_I1: + _ASSERTE(rArg != NULL); + *(PVOID *)pArgDst = (INT8*)*rArg; + break; + case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: - case ELEMENT_TYPE_CHAR: + _ASSERTE(rArg != NULL); + *(PVOID *)pArgDst = (INT16*)*rArg; + 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:) _ASSERTE(rArg != NULL); - *(PVOID *)pArgDst = (PVOID)*rArg; + *(PVOID *)pArgDst = (INT32*)*rArg; break; - } case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: 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 5e25a349e7535a..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 @@ -859,6 +859,8 @@ private static void InvokeClassConstructor() [MethodImplAttribute(MethodImplOptions.InternalCall)] internal extern object InternalInvoke(object? obj, in Span parameters, out Exception exc); + [DebuggerHidden] + [DebuggerStepThrough] internal unsafe object? InvokeNonEmitUnsafe(object? obj, IntPtr* byrefParameters, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { Exception exc; From 8c18cb90c77e026d3569bda69d1f8676f719375a Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 28 Mar 2022 23:03:46 -0500 Subject: [PATCH 16/28] Review feedback --- .../System/Reflection/Emit/DynamicMethod.cs | 2 +- .../RuntimeConstructorInfo.CoreCLR.cs | 2 +- src/coreclr/vm/invokeutil.cpp | 44 +++++++++---------- .../Reflection/RuntimeConstructorInfo.cs | 4 +- .../System/Reflection/RuntimeMethodInfo.cs | 2 +- 5 files changed, 26 insertions(+), 28 deletions(-) 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 a2229decd3142a..c9510b297514d1 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 @@ -532,7 +532,7 @@ Signature LazyCreateSignature() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)byrefStorage, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, byrefStorage, invokeAttr); } finally { 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 7e78902fad3553..c5c9dfa4acd656 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 @@ -98,7 +98,7 @@ Signature LazyCreateSignature() [DebuggerStepThrough] [DebuggerHidden] - internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSuppor, BindingFlags invokeAttr) + internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span argsForTemporaryMonoSupport, BindingFlags invokeAttr) { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index 8e82bd82c89d3c..4d937ad8529b74 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -140,7 +140,6 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest switch (type) { case ELEMENT_TYPE_BOOLEAN: - case ELEMENT_TYPE_CHAR: case ELEMENT_TYPE_U1: case ELEMENT_TYPE_I1: _ASSERTE(rArg != NULL); @@ -149,6 +148,7 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_CHAR: _ASSERTE(rArg != NULL); *(PVOID *)pArgDst = (INT16*)*rArg; break; @@ -175,30 +175,28 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_VALUETYPE: { + if (Nullable::IsNullableType(th)) { - 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*)*rArg; - if (!pMT->UnBoxIntoArg(argDest, src)) - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); - } - else if (rArg == NULL) { - // A byref-like type can't be boxed but the Invoke() arg value can be passed as a null object where that is - // marshalled as a null reference to indicate we need to initialize the byref-like type here. - MethodTable* pMT = th.GetMethodTable(); - if (pMT->IsByRefLike()) - InitValueClassArg(argDest, pMT); - else - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); - } + // 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*)*rArg; + if (!pMT->UnBoxIntoArg(argDest, src)) + COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + } + else if (rArg == NULL) { + // A byref-like type can't be boxed but the Invoke() arg value can be passed as a null object where that is + // marshalled as a null reference to indicate we need to initialize the byref-like type here. + MethodTable* pMT = th.GetMethodTable(); + if (pMT->IsByRefLike()) + InitValueClassArg(argDest, pMT); else - { - MethodTable* pMT = th.GetMethodTable(); - CopyValueClassArg(argDest, rArg, pMT, 0); - } + COMPlusThrow(kArgumentException, W("Arg_ObjObj")); + } + else + { + MethodTable* pMT = th.GetMethodTable(); + CopyValueClassArg(argDest, rArg, pMT, 0); } break; } 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 67dcd8368fa115..34a2c0997f1804 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -184,7 +184,7 @@ internal void ThrowNoInvokeException() culture, invokeAttr); - Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)byrefStorage, copyOfParameters, invokeAttr); + Invoker.InvokeUnsafe(obj, byrefStorage, copyOfParameters, invokeAttr); } finally { @@ -286,7 +286,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj: null, (IntPtr*)(void*)byrefStorage, copyOfParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj: null, byrefStorage, copyOfParameters, invokeAttr); } finally { 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 5c2ec1c1070ea4..1a9bf2bdcf53d5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -182,7 +182,7 @@ private void ThrowNoInvokeException() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)byrefStorage, copyOfParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, byrefStorage, copyOfParameters, invokeAttr); } finally { From 791bdb27ecbf11a143c9e3eb335953126cb500ca Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 29 Mar 2022 11:16:23 -0500 Subject: [PATCH 17/28] Align\pad work; remove TypedReference since it can't be boxed --- .../src/System/RuntimeType.CoreCLR.cs | 13 +----------- src/coreclr/vm/invokeutil.cpp | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 16 deletions(-) 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 47ca6f453f7d49..6bf40c7eebf8d8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3471,8 +3471,6 @@ 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); @@ -3628,10 +3626,9 @@ private CheckValueStatus TryChangeType( { if (RuntimeTypeHandle.IsValueType(this)) { - // If a nullable, pass the null as an object even though it's a value type. if (IsNullableOfT) { - // Treat as a reference type. + // Treat as a boxed value. isValueType = false; copyBack = false; } @@ -3660,14 +3657,6 @@ private CheckValueStatus TryChangeType( return CheckValueStatus.Success; } - if (this == s_typedRef) - { - // Everything works for a typedref - isValueType = false; - copyBack = false; - return CheckValueStatus.Success; - } - if (NeedsSpecialCast()) { if (SpecialCast(this, ref value) == CheckValueStatus.Success) diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index 4d937ad8529b74..dd96051cc5b269 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -142,25 +142,37 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_BOOLEAN: case ELEMENT_TYPE_U1: case ELEMENT_TYPE_I1: + { _ASSERTE(rArg != NULL); - *(PVOID *)pArgDst = (INT8*)*rArg; + ARG_SLOT data = 0; + data = *(INT8*)rArg; + *(PVOID *)pArgDst = (PVOID)data; break; + } case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: + { _ASSERTE(rArg != NULL); - *(PVOID *)pArgDst = (INT16*)*rArg; + ARG_SLOT data = 0; + data = *(INT16*)rArg; + *(PVOID *)pArgDst = (PVOID)data; break; + } case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_R4: IN_TARGET_32BIT(case ELEMENT_TYPE_U:) IN_TARGET_32BIT(case ELEMENT_TYPE_I:) + { _ASSERTE(rArg != NULL); - *(PVOID *)pArgDst = (INT32*)*rArg; + ARG_SLOT data = 0; + data = *(INT32*)rArg; + *(PVOID *)pArgDst = (PVOID)data; break; + } case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: @@ -169,7 +181,7 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest IN_TARGET_64BIT(case ELEMENT_TYPE_U:) { _ASSERTE(rArg != NULL); - *(INT64 *)pArgDst = (INT64)*rArg; + *(INT64 *)pArgDst = *(INT64 *)rArg; break; } From b27db5ccca9c05d3523f93e1b5f845bd4ea6d9d1 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 30 Mar 2022 22:06:45 -0500 Subject: [PATCH 18/28] Misc micro optimizations --- .../src/System/Reflection/RtFieldInfo.cs | 11 +++++++-- .../src/System/RuntimeType.CoreCLR.cs | 14 ++++++++++- src/coreclr/vm/invokeutil.cpp | 12 +++------- .../src/System/Reflection/MethodBase.cs | 24 +++++++++---------- .../src/System/Reflection/RuntimeFieldInfo.cs | 2 +- .../src/System/RuntimeType.Mono.cs | 16 ++++++++++++- 6 files changed, 52 insertions(+), 27 deletions(-) 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 1b9ee283fedcf4..55c9ac057becc5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -236,10 +236,17 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); + bool _b = false; RuntimeType fieldType = (RuntimeType)FieldType; - if (!ReferenceEquals(value?.GetType(), fieldType)) + if (value is null) + { + if (RuntimeTypeHandle.IsValueType(fieldType)) + { + fieldType.CheckValue(ref value, ref _b, paramInfo: null, binder, culture, invokeAttr); + } + } + else if (!ReferenceEquals(value.GetType(), fieldType)) { - bool _b = false; fieldType.CheckValue(ref value, ref _b, paramInfo: null, binder, culture, invokeAttr); } 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 6bf40c7eebf8d8..fe2b995ad54c5c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3496,7 +3496,19 @@ internal bool CheckValue( CultureInfo? culture, BindingFlags invokeAttr) { - Debug.Assert(!ReferenceEquals(value?.GetType(), this)); + // These are already fast-pathed by the caller. + Debug.Assert(!(value == null && (RuntimeTypeHandle.IsValueType(this) || RuntimeTypeHandle.IsByRef(this))) || + !ReferenceEquals(value?.GetType(), this)); + + if (ReferenceEquals(value, Type.Missing) && paramInfo != null) + { + if (paramInfo.DefaultValue == DBNull.Value) + { + throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); + } + + value = paramInfo.DefaultValue; + } // Fast path to whether a value can be assigned to type. if (IsInstanceOfType(value)) diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index dd96051cc5b269..4fff7004a928b3 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -144,9 +144,7 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_I1: { _ASSERTE(rArg != NULL); - ARG_SLOT data = 0; - data = *(INT8*)rArg; - *(PVOID *)pArgDst = (PVOID)data; + *(INT8 *)pArgDst = *(INT8 *)rArg; break; } @@ -155,9 +153,7 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_CHAR: { _ASSERTE(rArg != NULL); - ARG_SLOT data = 0; - data = *(INT16*)rArg; - *(PVOID *)pArgDst = (PVOID)data; + *(INT16 *)pArgDst = *(INT16 *)rArg; break; } @@ -168,9 +164,7 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest IN_TARGET_32BIT(case ELEMENT_TYPE_I:) { _ASSERTE(rArg != NULL); - ARG_SLOT data = 0; - data = *(INT32*)rArg; - *(PVOID *)pArgDst = (PVOID)data; + *(INT32 *)pArgDst = *(INT32 *)rArg; break; } 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 609e83fed72293..8c673ce073820e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -157,25 +157,23 @@ BindingFlags invokeAttr for (int i = 0; i < parameters.Length; i++) { bool copyBackArg = false; + bool isValueType; object? arg = parameters[i]; - if (arg == Type.Missing) + RuntimeType sigType = sigTypes[i]; + + if (arg is null) { - paramInfos ??= GetParametersNoCopy(); - if (paramInfos[i].DefaultValue == DBNull.Value) + // Fast path that avoids calling CheckValue() for reference types. + isValueType = RuntimeTypeHandle.IsValueType(sigType); + if (isValueType || RuntimeTypeHandle.IsByRef(sigType)) { - throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); + paramInfos ??= GetParametersNoCopy(); + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, paramInfos[i], binder, culture, invokeAttr); } - - copyBackArg = true; - arg = paramInfos[i].DefaultValue; } - - bool isValueType; - RuntimeType sigType = sigTypes[i]; - - if (ReferenceEquals(arg?.GetType(), sigType)) + else if (ReferenceEquals(arg.GetType(), sigType)) { - // Fast path that avoids calling CheckValue() + // Fast path that avoids calling CheckValue() when argument value matches the signature type. isValueType = RuntimeTypeHandle.IsValueType(sigType); } else 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 da434bfa48de64..ea89f2d2b2a41d 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -279,7 +279,7 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, { RuntimeType fieldType = (RuntimeType)FieldType; bool _ = false; - fieldType.CheckValue(ref val, ref _, default, binder, culture, invokeAttr); + fieldType.CheckValue(ref val, ref _, paramInfo: default, binder, culture, invokeAttr); } Invoker.SetValue(obj, val); 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 b725fe62765512..f2cb334faee13d 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1673,13 +1673,27 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) internal bool CheckValue( ref object? value, ref bool copyBack, - ParameterInfo? _, + ParameterInfo? paramInfo, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) { + // These are already fast-pathed by the caller. + Debug.Assert(!ReferenceEquals(value?.GetType(), this)); + Debug.Assert(value != null || RuntimeTypeHandle.IsValueType(this)); + copyBack = true; + if (value == Type.Missing && paramInfo != null) + { + if (paramInfo.DefaultValue == DBNull.Value) + { + throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); + } + + value = paramInfo.DefaultValue; + } + if (TryConvertToType(ref value)) return true; From 61f9debc2bf849b96e20e17c61ac8f0a4103df3d Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 31 Mar 2022 11:58:51 -0500 Subject: [PATCH 19/28] Misc naming --- .../src/System/Reflection/RtFieldInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 55c9ac057becc5..37ed8d993b4045 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -236,18 +236,18 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt CheckConsistency(obj); - bool _b = false; + bool _ref = false; RuntimeType fieldType = (RuntimeType)FieldType; if (value is null) { if (RuntimeTypeHandle.IsValueType(fieldType)) { - fieldType.CheckValue(ref value, ref _b, paramInfo: null, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, copyBack: ref _ref, paramInfo: null, binder, culture, invokeAttr); } } else if (!ReferenceEquals(value.GetType(), fieldType)) { - fieldType.CheckValue(ref value, ref _b, paramInfo: null, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, copyBack: ref _ref, paramInfo: null, binder, culture, invokeAttr); } Invoker.SetValue(obj, value); From be216b5f1d3d2a64e3fc3efc979557e5fa048852 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 31 Mar 2022 19:48:02 -0500 Subject: [PATCH 20/28] Remove support for ByRefLike + null; misc feedback --- .../src/System/RuntimeType.CoreCLR.cs | 65 +++++++++---------- src/coreclr/vm/invokeutil.cpp | 42 +++++------- src/coreclr/vm/invokeutil.h | 2 +- src/coreclr/vm/reflectioninvocation.cpp | 2 +- .../System.Memory/tests/Span/Reflection.cs | 10 +-- .../src/System/Reflection/MethodBase.cs | 19 ++---- .../Reflection/InvokeWithRefLikeArgs.cs | 22 ++----- 7 files changed, 62 insertions(+), 100 deletions(-) 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 fe2b995ad54c5c..1381939027466f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3538,16 +3538,16 @@ internal bool CheckValue( } bool isValueType; - CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo); + CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType); if (result == CheckValueStatus.Success) { return isValueType; } - Debug.Assert(value != null); - - if ((invokeAttr & BindingFlags.ExactBinding) == 0) + if (result == CheckValueStatus.ArgumentException && (invokeAttr & BindingFlags.ExactBinding) == 0) { + Debug.Assert(value != null); + // Use the binder if (binder != null && binder != DefaultBinder) { @@ -3558,7 +3558,7 @@ internal bool CheckValue( return IsValueType; } - result = TryChangeType(ref value, out copyBack, out isValueType, paramInfo); + result = TryChangeType(ref value, out copyBack, out isValueType); if (result == CheckValueStatus.Success) { return isValueType; @@ -3566,11 +3566,10 @@ internal bool CheckValue( } } - Debug.Assert(value != null); switch (result) { case CheckValueStatus.ArgumentException: - throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value.GetType(), this)); + throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this)); case CheckValueStatus.NotSupported_ByRefLike: throw new NotSupportedException(SR.NotSupported_ByRefLike); } @@ -3582,8 +3581,7 @@ internal bool CheckValue( private CheckValueStatus TryChangeType( ref object? value, out bool copyBack, - out bool isValueType, - ParameterInfo? paramInfo) + out bool isValueType) { // If this is a ByRef get the element type and check if it's compatible if (IsByRef) @@ -3607,9 +3605,10 @@ private CheckValueStatus TryChangeType( copyBack = true; return CheckValueStatus.Success; } - else if (value == null) + + if (value == null) { - if (IsByRefLike && paramInfo?.IsOut == true) + if (IsByRefLike) { isValueType = copyBack = default; return CheckValueStatus.NotSupported_ByRefLike; @@ -3620,7 +3619,8 @@ private CheckValueStatus TryChangeType( copyBack = true; return CheckValueStatus.Success; } - else if (NeedsSpecialCast()) + + if (NeedsSpecialCast()) { if (SpecialCast(sigElementType, ref value) == CheckValueStatus.Success) { @@ -3636,36 +3636,31 @@ private CheckValueStatus TryChangeType( if (value == null) { - if (RuntimeTypeHandle.IsValueType(this)) + if (!RuntimeTypeHandle.IsValueType(this)) { - if (IsNullableOfT) - { - // Treat as a boxed value. - isValueType = false; - copyBack = false; - } - else - { - isValueType = true; - if (RuntimeTypeHandle.IsByRefLike(this)) - { - // For a byref-like parameter pass null to the runtime which will create a default value. - copyBack = true; // copy the null back to replace a possible Type.Missing value - } - else - { - // Need to create an instance of the value type if null was provided. - value = AllocateValueType(this, value: null, fForceTypeChange: false); - copyBack = false; - } - } + isValueType = false; + copyBack = false; + return CheckValueStatus.Success; } - else + + 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; } diff --git a/src/coreclr/vm/invokeutil.cpp b/src/coreclr/vm/invokeutil.cpp index 4fff7004a928b3..e50cdc7f1511d5 100644 --- a/src/coreclr/vm/invokeutil.cpp +++ b/src/coreclr/vm/invokeutil.cpp @@ -124,7 +124,7 @@ void *InvokeUtil::GetIntPtrValue(OBJECTREF pObj) { RETURN *(void **)((pObj)->UnBox()); } -void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest) { +void InvokeUtil::CopyArg(TypeHandle th, PVOID argRef, ArgDestination *argDest) { CONTRACTL { THROWS; GC_NOTRIGGER; // Caller does not protect object references @@ -135,7 +135,6 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest CONTRACTL_END; void *pArgDst = argDest->GetDestinationAddress(); - PVOID *rArg = *pArgRef; CorElementType type = th.GetVerifierCorElementType(); switch (type) { @@ -143,8 +142,8 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_U1: case ELEMENT_TYPE_I1: { - _ASSERTE(rArg != NULL); - *(INT8 *)pArgDst = *(INT8 *)rArg; + _ASSERTE(argRef != NULL); + *(INT8 *)pArgDst = *(INT8 *)argRef; break; } @@ -152,8 +151,8 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: { - _ASSERTE(rArg != NULL); - *(INT16 *)pArgDst = *(INT16 *)rArg; + _ASSERTE(argRef != NULL); + *(INT16 *)pArgDst = *(INT16 *)argRef; break; } @@ -163,8 +162,8 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest IN_TARGET_32BIT(case ELEMENT_TYPE_U:) IN_TARGET_32BIT(case ELEMENT_TYPE_I:) { - _ASSERTE(rArg != NULL); - *(INT32 *)pArgDst = *(INT32 *)rArg; + _ASSERTE(argRef != NULL); + *(INT32 *)pArgDst = *(INT32 *)argRef; break; } @@ -174,8 +173,8 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest IN_TARGET_64BIT(case ELEMENT_TYPE_I:) IN_TARGET_64BIT(case ELEMENT_TYPE_U:) { - _ASSERTE(rArg != NULL); - *(INT64 *)pArgDst = *(INT64 *)rArg; + _ASSERTE(argRef != NULL); + *(INT64 *)pArgDst = *(INT64 *)argRef; break; } @@ -186,23 +185,14 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest // 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*)*rArg; + OBJECTREF src = (OBJECTREF)(Object*)*(PVOID*)argRef; if (!pMT->UnBoxIntoArg(argDest, src)) COMPlusThrow(kArgumentException, W("Arg_ObjObj")); } - else if (rArg == NULL) { - // A byref-like type can't be boxed but the Invoke() arg value can be passed as a null object where that is - // marshalled as a null reference to indicate we need to initialize the byref-like type here. - MethodTable* pMT = th.GetMethodTable(); - if (pMT->IsByRefLike()) - InitValueClassArg(argDest, pMT); - else - COMPlusThrow(kArgumentException, W("Arg_ObjObj")); - } else { MethodTable* pMT = th.GetMethodTable(); - CopyValueClassArg(argDest, rArg, pMT, 0); + CopyValueClassArg(argDest, argRef, pMT, 0); } break; } @@ -214,10 +204,10 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest case ELEMENT_TYPE_ARRAY: // General Array case ELEMENT_TYPE_VAR: { - if (rArg == NULL) + if (argRef == NULL) *(PVOID *)pArgDst = 0; else - *(PVOID *)pArgDst = OBJECTREFToObject((OBJECTREF)(Object*)*rArg); + *(PVOID *)pArgDst = OBJECTREFToObject((OBJECTREF)(Object*)*(PVOID*)argRef); break; } @@ -227,16 +217,16 @@ void InvokeUtil::CopyArg(TypeHandle th, PVOID **pArgRef, ArgDestination *argDest // heads these off and morphs the type handle to not be byref anymore _ASSERTE(!Nullable::IsNullableType(th.AsTypeDesc()->GetTypeParam())); - *(PVOID *)pArgDst = rArg; + *(PVOID *)pArgDst = argRef; break; } case ELEMENT_TYPE_PTR: case ELEMENT_TYPE_FNPTR: { - _ASSERTE(rArg != NULL); + _ASSERTE(argRef != NULL); MethodTable* pMT = th.GetMethodTable(); - CopyValueClassArg(argDest, rArg, pMT, 0); + CopyValueClassArg(argDest, argRef, pMT, 0); break; } diff --git a/src/coreclr/vm/invokeutil.h b/src/coreclr/vm/invokeutil.h index 3b1de3b0faa63a..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, PVOID **pArgRef, 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 diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 5e65405d709ec6..e1132f352673ba 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -805,7 +805,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, argDest = ArgDestination(pStackCopy, 0, NULL); } - InvokeUtil::CopyArg(th, (PVOID **)&(args[i]), &argDest); + InvokeUtil::CopyArg(th, args[i], &argDest); } ENDFORBIDGC(); 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/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 8c673ce073820e..624f65358ce7fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -191,27 +191,16 @@ BindingFlags invokeAttr // This separate array is also used to hold default values when 'null' is specified for value // types which should not be applied to the incoming array. shouldCopyBack[i] = copyBackArg; + copyOfParameters[i] = arg; if (isValueType) { - if (arg == null) - { - // Special case when passing a null to signal the native runtime to create a default ref struct - Debug.Assert(copyOfParameters[i] == null); - byrefParameters[i] = IntPtr.Zero; - } - else - { - copyOfParameters[i] = arg; - - ByReference valueTypeRef = new(ref copyOfParameters[i]!.GetRawData()); - *(ByReference*)(byrefParameters + i) = valueTypeRef; - } + Debug.Assert(arg != null); + ByReference valueTypeRef = new(ref copyOfParameters[i]!.GetRawData()); + *(ByReference*)(byrefParameters + i) = valueTypeRef; } else { - copyOfParameters[i] = arg; - ByReference objRef = new(ref copyOfParameters[i]); *(ByReference*)(byrefParameters + i) = objRef; } 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 From 9ade961f73cf71e02719db7a880f06625285c3cd Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 31 Mar 2022 21:36:48 -0500 Subject: [PATCH 21/28] Remove support for ByRefLike + null on mono --- .../src/System/RuntimeType.Mono.cs | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) 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 f2cb334faee13d..ed0bab12e53db7 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1694,11 +1694,21 @@ internal bool CheckValue( value = paramInfo.DefaultValue; } - if (TryConvertToType(ref value)) + CheckValueStatus status = TryConvertToType(ref value); + if (status == CheckValueStatus.Success) + { return true; + } + + if (status == CheckValueStatus.NotSupported_ByRefLike) + { + throw new ArgumentException(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)); + } if (binder != null && binder != DefaultBinder) { @@ -1709,36 +1719,53 @@ internal bool CheckValue( throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value!.GetType(), this)); } - private bool TryConvertToType(ref object? value) + private enum CheckValueStatus + { + Success = 0, + ArgumentException, + NotSupported_ByRefLike + } + + private CheckValueStatus TryConvertToType(ref object? value) { if (IsInstanceOfType(value)) - return true; + return CheckValueStatus.Success; if (IsByRef) { Type? elementType = GetElementType(); + if (elementType.IsByRefLike) + { + return CheckValueStatus.NotSupported_ByRefLike; + } + if (value == null || elementType.IsInstanceOfType(value)) { - return true; + return CheckValueStatus.Success; } } if (value == null) { - return true; + if (IsByRefLike) + { + return CheckValueStatus.NotSupported_ByRefLike; + } + + return CheckValueStatus.Success; } if (IsEnum) { Type? type = Enum.GetUnderlyingType(this); if (type == value.GetType()) - return true; + return CheckValueStatus.Success; object? res = IsConvertibleToPrimitiveType(value, type); if (res != null) { value = res; - return true; + return CheckValueStatus.Success; } } else if (IsPrimitive) @@ -1747,26 +1774,26 @@ private bool TryConvertToType(ref object? value) if (res != null) { value = res; - return true; + return CheckValueStatus.Success; } } else if (IsPointer) { Type? vtype = value.GetType(); if (vtype == typeof(IntPtr) || vtype == typeof(UIntPtr)) - return true; + return CheckValueStatus.Success; if (value is Pointer pointer) { Type pointerType = pointer.GetPointerType(); if (pointerType == this) { value = pointer.GetPointerValue(); - return true; + return CheckValueStatus.Success; } } } - return false; + return CheckValueStatus.ArgumentException; } // Binder uses some incompatible conversion rules. For example From bfd1062cf7ef5339f181e92f9ab876841ff6aec9 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Fri, 1 Apr 2022 13:43:29 -0500 Subject: [PATCH 22/28] Wrap all exceptions thrown from ::InvokeMethod; misc perf refactor --- .../System/Reflection/Emit/DynamicMethod.cs | 129 +++++----- .../src/System/Reflection/RtFieldInfo.cs | 4 +- .../RuntimeConstructorInfo.CoreCLR.cs | 8 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 13 +- .../src/System/RuntimeHandles.cs | 2 +- .../src/System/RuntimeType.CoreCLR.cs | 13 +- src/coreclr/vm/reflectioninvocation.cpp | 17 +- src/coreclr/vm/runtimehandles.h | 2 +- .../src/Resources/Strings.resx | 3 - .../src/System/Reflection/MethodBase.cs | 16 +- .../Reflection/RuntimeConstructorInfo.cs | 243 ++++++++++-------- .../System/Reflection/RuntimeMethodInfo.cs | 121 +++++---- .../ConstructorInfoInvokeArrayTests.cs | 35 ++- .../tests/ConstructorInfoTests.cs | 7 +- .../System/Reflection/InvokeRefReturn.cs | 6 +- .../src/System/Reflection/RuntimeFieldInfo.cs | 2 +- .../src/System/RuntimeType.Mono.cs | 21 +- 17 files changed, 344 insertions(+), 298 deletions(-) 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 c9510b297514d1..497d85bfc32cc3 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 @@ -478,67 +478,32 @@ Signature LazyCreateSignature() { retValue = Invoker.InvokeUnsafe(obj, args: default, invokeAttr); } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + CheckManyArguments(); + } else { Debug.Assert(parameters != null); - Span copyOfParameters; - Span shouldCopyBackParameters; - - if (argCount <= MaxStackAllocArgCount) - { - StackAllocedArguments argStorage = default; - copyOfParameters = new Span(ref argStorage._arg0, argCount); - shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); - - StackAllocatedByRefs byrefStorage = default; - - CheckArguments( - copyOfParameters, - (IntPtr*)&byrefStorage, - shouldCopyBackParameters, - parameters, - Signature.Arguments, - binder, - culture, - invokeAttr); - - retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void**)&byrefStorage, invokeAttr); - } - else - { - object[] objHolder = new object[argCount]; - 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* byrefStorage = stackalloc IntPtr[argCount]; - Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); - bool* boolHolder = stackalloc bool[argCount]; - shouldCopyBackParameters = new Span(boolHolder, argCount); + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; - GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + Signature.Arguments, + binder, + culture, + invokeAttr); - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - byrefStorage, - shouldCopyBackParameters, - parameters, - Signature.Arguments, - binder, - culture, - invokeAttr); - - retValue = Invoker.InvokeUnsafe(obj, byrefStorage, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - } + 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++) @@ -553,6 +518,52 @@ Signature LazyCreateSignature() GC.KeepAlive(this); return retValue; + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + unsafe void CheckManyArguments() + { + 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); + + try + { + RegisterForGCReporting(®); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + Signature.Arguments, + binder, + culture, + invokeAttr); + + retValue = 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]; + } + } + } } [DebuggerHidden] @@ -561,20 +572,18 @@ Signature LazyCreateSignature() { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { - bool rethrow = false; - try { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } - catch (Exception e) when (!rethrow) + catch (Exception e) { throw new TargetInvocationException(e); } } else { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index 37ed8d993b4045..8eb09a7756e8f5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -242,12 +242,12 @@ public override void SetValue(object? obj, object? value, BindingFlags invokeAtt { if (RuntimeTypeHandle.IsValueType(fieldType)) { - fieldType.CheckValue(ref value, copyBack: ref _ref, paramInfo: null, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); } } else if (!ReferenceEquals(value.GetType(), fieldType)) { - fieldType.CheckValue(ref value, copyBack: ref _ref, paramInfo: null, binder, culture, invokeAttr); + fieldType.CheckValue(ref value, copyBack: ref _ref, binder, culture, invokeAttr); } Invoker.SetValue(obj, value); 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 c5c9dfa4acd656..98b7185a9f2ed5 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 @@ -102,24 +102,22 @@ internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; CheckArguments( copyOfParameters, - (IntPtr*)&byrefStorage, + pByRefStorage, shouldCopyBackParameters, parameters, ArgumentTypes, @@ -361,7 +362,7 @@ public override MethodImplAttributes GetMethodImplementationFlags() culture, invokeAttr); - retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void**)&byrefStorage, copyOfParameters, invokeAttr); + retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); } return retValue; @@ -373,20 +374,18 @@ public override MethodImplAttributes GetMethodImplementationFlags() { if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { - bool rethrow = false; - try { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out rethrow); + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } - catch (Exception e) when (!rethrow) + catch (Exception e) { throw new TargetInvocationException(e); } } else { - return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false, out _); + return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 5be5e55fad6a78..450f9a3441d86a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -979,7 +979,7 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method) [DebuggerStepThrough] [DebuggerHidden] [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor, out bool rethrow); + 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); 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 1381939027466f..dabaf2a32bf70e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3491,7 +3491,6 @@ private enum CheckValueStatus internal bool CheckValue( ref object? value, ref bool copyBack, - ParameterInfo? paramInfo, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) @@ -3500,16 +3499,6 @@ internal bool CheckValue( Debug.Assert(!(value == null && (RuntimeTypeHandle.IsValueType(this) || RuntimeTypeHandle.IsByRef(this))) || !ReferenceEquals(value?.GetType(), this)); - if (ReferenceEquals(value, Type.Missing) && paramInfo != null) - { - if (paramInfo.DefaultValue == DBNull.Value) - { - throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); - } - - value = paramInfo.DefaultValue; - } - // Fast path to whether a value can be assigned to type. if (IsInstanceOfType(value)) { @@ -3711,7 +3700,7 @@ static CheckValueStatus SpecialCast(RuntimeType type, ref object value) } } - private static CorElementType GetUnderlyingType(RuntimeType type) + private static CorElementType GetUnderlyingType(RuntimeType type) { if (type.IsEnum) { diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index e1132f352673ba..4a54520d6844e3 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -393,11 +393,12 @@ static OBJECTREF InvokeArrayConstructor(TypeHandle th, PVOID* args, int argCnt) for (DWORD i=0; i<(DWORD)argCnt; i++) { - if (!args[i]) - COMPlusThrowArgumentException(W("parameters"), W("Arg_NullIndex")); + _ASSERTE(args[i] != NULL); - ARG_SLOT value; - InvokeUtil::CreatePrimitiveValue(ELEMENT_TYPE_I4, ELEMENT_TYPE_I4, args[i], pMT, &value); + INT32 size = *(INT32*)args[i]; + _ASSERTE(size >= 0); + + ARG_SLOT value = size; memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); } @@ -531,12 +532,11 @@ class ArgIteratorForMethodInvoke : public ArgIteratorTemplateGetMethod(); TypeHandle ownerType = gc.pSig->GetDeclaringType(); @@ -818,9 +817,7 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, } // Call the method - *pRethrow = FALSE; CallDescrWorkerWithHandler(&callDescrData); - *pRethrow = TRUE; // It is still illegal to do a GC here. The return type might have/contain GC pointers. if (fConstructor) diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 3e53d766ca882b..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, PVOID* args, SignatureNative* pSig, CLR_BOOL fConstructor, CLR_BOOL* pRethrow); + 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.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/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 624f65358ce7fc..a16d31276b8073 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -167,8 +167,7 @@ BindingFlags invokeAttr isValueType = RuntimeTypeHandle.IsValueType(sigType); if (isValueType || RuntimeTypeHandle.IsByRef(sigType)) { - paramInfos ??= GetParametersNoCopy(); - isValueType = sigType.CheckValue(ref arg, ref copyBackArg, paramInfos[i], binder, culture, invokeAttr); + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } } else if (ReferenceEquals(arg.GetType(), sigType)) @@ -179,7 +178,18 @@ BindingFlags invokeAttr else { paramInfos ??= GetParametersNoCopy(); - isValueType = sigType.CheckValue(ref arg, ref copyBackArg, paramInfos[i], binder, culture, invokeAttr); + ParameterInfo paramInfo = paramInfos[i]; + if (ReferenceEquals(arg, Type.Missing)) + { + if (paramInfo.DefaultValue == DBNull.Value) + { + throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); + } + + arg = paramInfo.DefaultValue; + } + + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } // We need to perform type safety validation against the incoming arguments, but we also need 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 34a2c0997f1804..4ddd0b77c21c8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -130,67 +130,32 @@ internal void ThrowNoInvokeException() { Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + CheckManyArguments(); + } else { Debug.Assert(parameters != null); - Span copyOfParameters; - Span shouldCopyBackParameters; - - if (argCount <= MaxStackAllocArgCount) - { - StackAllocedArguments argStorage = default; - copyOfParameters = new Span(ref argStorage._arg0, argCount); - shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); - - StackAllocatedByRefs byrefStorage = default; - - CheckArguments( - copyOfParameters, - (IntPtr*)&byrefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - Invoker.InvokeUnsafe(obj, (IntPtr*)(void**)&byrefStorage, copyOfParameters, invokeAttr); - } - else - { - object[] objHolder = new object[argCount]; - copyOfParameters = new Span(objHolder, 0, argCount); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, 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* byrefStorage = stackalloc IntPtr[argCount]; - Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; - bool* boolHolder = stackalloc bool[argCount]; - shouldCopyBackParameters = new Span(boolHolder, argCount); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); - GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); - - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - byrefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - Invoker.InvokeUnsafe(obj, byrefStorage, copyOfParameters, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - } + 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++) @@ -202,7 +167,54 @@ internal void ThrowNoInvokeException() } } } + return null; + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + unsafe void CheckManyArguments() + { + 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); + + try + { + RegisterForGCReporting(®); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + Invoker.InvokeUnsafe(obj, 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]; + } + } + } } [DebuggerStepThrough] @@ -232,67 +244,32 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] { retValue = Invoker.InvokeUnsafe(obj: null, args: default, argsForTemporaryMonoSupport: default, invokeAttr); } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + CheckManyArguments(); + } else { Debug.Assert(parameters != null); - Span copyOfParameters; - Span shouldCopyBackParameters; - - if (argCount <= MaxStackAllocArgCount) - { - StackAllocedArguments argStorage = default; - copyOfParameters = new Span(ref argStorage._arg0, argCount); - shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); - - StackAllocatedByRefs byrefStorage = default; - - CheckArguments( - copyOfParameters, - (IntPtr*)&byrefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = Invoker.InvokeUnsafe(obj: null, (IntPtr*)(void**)&byrefStorage, copyOfParameters, invokeAttr); - } - else - { - object[] objHolder = new object[argCount]; - copyOfParameters = new Span(objHolder, 0, argCount); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, 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* byrefStorage = stackalloc IntPtr[argCount]; - Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; - bool* boolHolder = stackalloc bool[argCount]; - shouldCopyBackParameters = new Span(boolHolder, argCount); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); - GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); - - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - byrefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = Invoker.InvokeUnsafe(obj: null, byrefStorage, copyOfParameters, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - } + 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++) @@ -307,6 +284,52 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] Debug.Assert(retValue != null); return retValue; + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + unsafe void CheckManyArguments() + { + 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); + + try + { + RegisterForGCReporting(®); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = 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]; + } + } + } } } } 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 1a9bf2bdcf53d5..7dece61cdbd6a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -128,67 +128,32 @@ private void ThrowNoInvokeException() { retValue = Invoker.InvokeUnsafe(obj, args: default, argsForTemporaryMonoSupport: default, invokeAttr); } + else if (argCount > MaxStackAllocArgCount) + { + Debug.Assert(parameters != null); + CheckManyArguments(); + } else { Debug.Assert(parameters != null); - Span copyOfParameters; - Span shouldCopyBackParameters; - - if (argCount <= MaxStackAllocArgCount) - { - StackAllocedArguments argStorage = default; - copyOfParameters = new Span(ref argStorage._arg0, argCount); - shouldCopyBackParameters = new Span(ref argStorage._copyBack0, argCount); - - StackAllocatedByRefs byrefStorage = default; - - CheckArguments( - copyOfParameters, - (IntPtr*)&byrefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = Invoker.InvokeUnsafe(obj, (IntPtr*)(void*)&byrefStorage, copyOfParameters, invokeAttr); - } - else - { - object[] objHolder = new object[argCount]; - copyOfParameters = new Span(objHolder, 0, argCount); + StackAllocedArguments argStorage = default; + Span copyOfParameters = new Span(ref argStorage._arg0, argCount); + Span shouldCopyBackParameters = new Span(ref argStorage._copyBack0, 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* byrefStorage = stackalloc IntPtr[argCount]; - Buffer.ZeroMemory((byte*)byrefStorage, (uint)(argCount * sizeof(IntPtr))); + StackAllocatedByRefs byrefStorage = default; + IntPtr* pByRefStorage = (IntPtr*)&byrefStorage; - bool* boolHolder = stackalloc bool[argCount]; - shouldCopyBackParameters = new Span(boolHolder, argCount); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); - GCFrameRegistration reg = new(byrefStorage, (uint)argCount, areByRefs: true); - - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - byrefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); - - retValue = Invoker.InvokeUnsafe(obj, byrefStorage, copyOfParameters, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } - } + 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++) @@ -202,6 +167,52 @@ private void ThrowNoInvokeException() } return retValue; + + // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. + unsafe void CheckManyArguments() + { + 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); + + try + { + RegisterForGCReporting(®); + CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = Invoker.InvokeUnsafe(obj, 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]; + } + } + } } } } diff --git a/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs b/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs index a050476cee765c..f67acdae97ad55 100644 --- a/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs +++ b/src/libraries/System.Reflection.TypeExtensions/tests/ConstructorInfo/ConstructorInfoInvokeArrayTests.cs @@ -18,7 +18,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 +55,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 +81,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 }; @@ -117,7 +126,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 +162,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; @@ -245,7 +260,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 +282,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..4bc40b459e468d 100644 --- a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs @@ -107,7 +107,7 @@ 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); } @@ -121,8 +121,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.Runtime/tests/System/Reflection/InvokeRefReturn.cs b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs index 885e17992c1768..48ea6f4bdfdf8c 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs @@ -43,7 +43,8 @@ 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] @@ -65,7 +66,8 @@ public static unsafe void TestNullRefReturnOfPointer() 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/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index ea89f2d2b2a41d..36b9b0ac99eca9 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -279,7 +279,7 @@ public override void SetValue(object? obj, object? val, BindingFlags invokeAttr, { RuntimeType fieldType = (RuntimeType)FieldType; bool _ = false; - fieldType.CheckValue(ref val, ref _, paramInfo: default, binder, culture, invokeAttr); + fieldType.CheckValue(ref val, ref _, binder, culture, invokeAttr); } Invoker.SetValue(obj, val); 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 ed0bab12e53db7..b810e4bddedd9e 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1673,27 +1673,16 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) internal bool CheckValue( ref object? value, ref bool copyBack, - ParameterInfo? paramInfo, Binder? binder, CultureInfo? culture, BindingFlags invokeAttr) { // These are already fast-pathed by the caller. - Debug.Assert(!ReferenceEquals(value?.GetType(), this)); - Debug.Assert(value != null || RuntimeTypeHandle.IsValueType(this)); + Debug.Assert(!(value == null && (RuntimeTypeHandle.IsValueType(this) || RuntimeTypeHandle.IsByRef(this))) || + !ReferenceEquals(value?.GetType(), this)); copyBack = true; - if (value == Type.Missing && paramInfo != null) - { - if (paramInfo.DefaultValue == DBNull.Value) - { - throw new ArgumentException(SR.Arg_VarMissNull, "parameters"); - } - - value = paramInfo.DefaultValue; - } - CheckValueStatus status = TryConvertToType(ref value); if (status == CheckValueStatus.Success) { @@ -1702,12 +1691,12 @@ internal bool CheckValue( if (status == CheckValueStatus.NotSupported_ByRefLike) { - throw new ArgumentException(SR.Format(SR.NotSupported_ByRefLike, value!.GetType(), this)); + throw new ArgumentException(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) @@ -1716,7 +1705,7 @@ internal bool CheckValue( 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 enum CheckValueStatus From 6cb7837d4b366be26e25ebe2320ef0ef56e69867 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Fri, 1 Apr 2022 16:28:55 -0500 Subject: [PATCH 23/28] Mono tests; cont'd perf refactoring --- .../System/Reflection/Emit/DynamicMethod.cs | 88 +++++---- .../RuntimeConstructorInfo.CoreCLR.cs | 4 - src/coreclr/vm/reflectioninvocation.cpp | 2 - .../Reflection/RuntimeConstructorInfo.cs | 172 ++++++++++-------- .../System/Reflection/RuntimeMethodInfo.cs | 88 +++++---- .../ConstructorInfoInvokeArrayTests.cs | 3 + .../tests/ConstructorInfoTests.cs | 1 + .../System/Reflection/InvokeRefReturn.cs | 2 + .../src/System/RuntimeType.Mono.cs | 4 +- 9 files changed, 204 insertions(+), 160 deletions(-) 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 497d85bfc32cc3..b716c7cad42c8a 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 @@ -480,8 +480,7 @@ Signature LazyCreateSignature() } else if (argCount > MaxStackAllocArgCount) { - Debug.Assert(parameters != null); - CheckManyArguments(); + retValue = CheckManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else { @@ -518,52 +517,65 @@ Signature LazyCreateSignature() GC.KeepAlive(this); return retValue; + } - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - unsafe void CheckManyArguments() - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + // 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? CheckManyArguments( + DynamicMethod mi, + int argCount, + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) + { + Debug.Assert(parameters != null); - // 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))); + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(objHolder, 0, argCount); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, 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))); - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + bool* boolHolder = stackalloc bool[argCount]; + Span shouldCopyBackParameters = new Span(boolHolder, argCount); - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - Signature.Arguments, - binder, - culture, - invokeAttr); + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } + 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++) + // 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]) { - if (shouldCopyBackParameters[i]) - { - parameters[i] = copyOfParameters[i]; - } + parameters[i] = copyOfParameters[i]; } } + + return retValue; } [DebuggerHidden] 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 98b7185a9f2ed5..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 @@ -106,10 +106,6 @@ internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span= 0); - ARG_SLOT value = size; memcpyNoGCRefs(indexes + i, ArgSlotEndianessFixup(&value, sizeof(INT32)), sizeof(INT32)); } 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 4ddd0b77c21c8f..cee1b44b232c51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -132,8 +132,7 @@ internal void ThrowNoInvokeException() } else if (argCount > MaxStackAllocArgCount) { - Debug.Assert(parameters != null); - CheckManyArguments(); + CheckManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else { @@ -169,50 +168,60 @@ internal void ThrowNoInvokeException() } return null; + } - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - unsafe void CheckManyArguments() - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + // 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 void CheckManyArguments( + RuntimeConstructorInfo ci, + int argCount, + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) + { + Debug.Assert(parameters != null); - // 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))); + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(objHolder, 0, argCount); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, 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))); - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + bool* boolHolder = stackalloc bool[argCount]; + Span shouldCopyBackParameters = new Span(boolHolder, argCount); - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } + try + { + RegisterForGCReporting(®); + ci.CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + ci.ArgumentTypes, + binder, + culture, + invokeAttr); + + ci.Invoker.InvokeUnsafe(obj, 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++) + // 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]) { - if (shouldCopyBackParameters[i]) - { - parameters[i] = copyOfParameters[i]; - } + parameters[i] = copyOfParameters[i]; } } } @@ -246,8 +255,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] } else if (argCount > MaxStackAllocArgCount) { - Debug.Assert(parameters != null); - CheckManyArguments(); + retValue = CheckManyArguments(this, argCount, invokeAttr, binder, parameters, culture); } else { @@ -284,52 +292,64 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] Debug.Assert(retValue != null); return retValue; + } - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - unsafe void CheckManyArguments() - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + // 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. + private static unsafe object? CheckManyArguments( + RuntimeConstructorInfo ci, + int argCount, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) + { + Debug.Assert(parameters != null); - // 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))); + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(objHolder, 0, argCount); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, 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))); - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + bool* boolHolder = stackalloc bool[argCount]; + Span shouldCopyBackParameters = new Span(boolHolder, argCount); - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - retValue = Invoker.InvokeUnsafe(obj: null, pByRefStorage, copyOfParameters, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } + 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++) + // 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]) { - if (shouldCopyBackParameters[i]) - { - parameters[i] = copyOfParameters[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 7dece61cdbd6a0..a375e11514ee6c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -130,8 +130,7 @@ private void ThrowNoInvokeException() } else if (argCount > MaxStackAllocArgCount) { - Debug.Assert(parameters != null); - CheckManyArguments(); + retValue = CheckManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else { @@ -167,52 +166,65 @@ private void ThrowNoInvokeException() } return retValue; + } - // Slower path that does a heap alloc for copyOfParameters and registers byrefs to those objects. - unsafe void CheckManyArguments() - { - object[] objHolder = new object[argCount]; - Span copyOfParameters = new Span(objHolder, 0, argCount); + // 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? CheckManyArguments( + RuntimeMethodInfo mi, + int argCount, + object? obj, + BindingFlags invokeAttr, + Binder? binder, + object?[]? parameters, + CultureInfo? culture) + { + Debug.Assert(parameters != null); - // 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))); + object[] objHolder = new object[argCount]; + Span copyOfParameters = new Span(objHolder, 0, argCount); - bool* boolHolder = stackalloc bool[argCount]; - Span shouldCopyBackParameters = new Span(boolHolder, 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))); - GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); + bool* boolHolder = stackalloc bool[argCount]; + Span shouldCopyBackParameters = new Span(boolHolder, argCount); - try - { - RegisterForGCReporting(®); - CheckArguments( - copyOfParameters, - pByRefStorage, - shouldCopyBackParameters, - parameters, - ArgumentTypes, - binder, - culture, - invokeAttr); + GCFrameRegistration reg = new(pByRefStorage, (uint)argCount, areByRefs: true); - retValue = Invoker.InvokeUnsafe(obj, pByRefStorage, copyOfParameters, invokeAttr); - } - finally - { - UnregisterForGCReporting(®); - } + object? retValue; + try + { + RegisterForGCReporting(®); + mi.CheckArguments( + copyOfParameters, + pByRefStorage, + shouldCopyBackParameters, + parameters, + mi.ArgumentTypes, + binder, + culture, + invokeAttr); + + retValue = mi.Invoker.InvokeUnsafe(obj, 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++) + // 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]) { - if (shouldCopyBackParameters[i]) - { - parameters[i] = copyOfParameters[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 f67acdae97ad55..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[]"); @@ -108,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); @@ -245,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[][]"); diff --git a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs index 4bc40b459e468d..262fdc2e4de1b1 100644 --- a/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Reflection/tests/ConstructorInfoTests.cs @@ -114,6 +114,7 @@ public void Invoke_OneDimensionalArray() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/67457", TestRuntimes.Mono)] public void Invoke_OneDimensionalArray_NegativeLengths_ThrowsOverflowException() { ConstructorInfo[] constructors = GetConstructors(typeof(object[])); diff --git a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs index 48ea6f4bdfdf8c..a86592b14ff2f9 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/InvokeRefReturn.cs @@ -38,6 +38,7 @@ 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); @@ -60,6 +61,7 @@ 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); 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 b810e4bddedd9e..a9ec8644153680 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1691,7 +1691,7 @@ internal bool CheckValue( if (status == CheckValueStatus.NotSupported_ByRefLike) { - throw new ArgumentException(SR.Format(SR.NotSupported_ByRefLike, value?.GetType(), this)); + throw new NotSupportedException(SR.Format(SR.NotSupported_ByRefLike, value?.GetType(), this)); } if ((invokeAttr & BindingFlags.ExactBinding) == BindingFlags.ExactBinding) @@ -1722,7 +1722,7 @@ private CheckValueStatus TryConvertToType(ref object? value) if (IsByRef) { - Type? elementType = GetElementType(); + Type elementType = GetElementType(); if (elementType.IsByRefLike) { return CheckValueStatus.NotSupported_ByRefLike; From 1f81dc6796e740c610a03333d6db82450eb43d6d Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sat, 2 Apr 2022 17:19:03 -0500 Subject: [PATCH 24/28] Additional validation checks --- .../src/System/RuntimeType.CoreCLR.cs | 35 ++++++++++--------- .../src/System/Reflection/MethodBase.cs | 22 +++++++++--- .../Reflection/RuntimeConstructorInfo.cs | 4 +++ .../System/Reflection/RuntimeMethodInfo.cs | 2 ++ .../tests/System/DelegateTests.cs | 2 +- .../src/System/RuntimeType.Mono.cs | 11 ++++-- 6 files changed, 52 insertions(+), 24 deletions(-) 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 dabaf2a32bf70e..0670e4660a8eb7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3484,6 +3484,19 @@ private enum CheckValueStatus NotSupported_ByRefLike } +#if DEBUG + internal void VerifyValueType(object? value) + { + Debug.Assert(value != null); + Debug.Assert( + value.GetType() == this || + (IsPointer && value.GetType() == typeof(IntPtr)) || + (IsByRef && value.GetType() == RuntimeTypeHandle.GetElementType(this)) || + (value.GetType().IsEnum && GetUnderlyingType((RuntimeType)value.GetType()) == GetUnderlyingType(this)) || + (IsEnum && GetUnderlyingType((RuntimeType)value.GetType()) == GetUnderlyingType(this))); + } +#endif + /// /// Verify and optionally convert the value for special cases. /// @@ -3495,11 +3508,10 @@ internal bool CheckValue( CultureInfo? culture, BindingFlags invokeAttr) { - // These are already fast-pathed by the caller. - Debug.Assert(!(value == null && (RuntimeTypeHandle.IsValueType(this) || RuntimeTypeHandle.IsByRef(this))) || - !ReferenceEquals(value?.GetType(), this)); + // Already fast-pathed by the caller. + Debug.Assert(!ReferenceEquals(value?.GetType(), this)); - // Fast path to whether a value can be assigned to type. + // 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 @@ -3508,19 +3520,10 @@ internal bool CheckValue( if (RuntimeTypeHandle.IsValueType(this)) { - // If a nullable, pass the object even though it's a value type. - if (IsNullableOfT) - { - // Treat as a reference type. - copyBack = false; - return false; - } + // Nullable is the only value type that will get here. + Debug.Assert(IsNullableOfT); - // Must be an equivalent type, re-box to the target type - object newValue = AllocateValueType(this, value, fForceTypeChange: true); - value = newValue; - copyBack = true; - return true; + // Fall through and treat as a reference type. } 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 a16d31276b8073..86d57286ec1d8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -179,7 +179,11 @@ BindingFlags invokeAttr { paramInfos ??= GetParametersNoCopy(); ParameterInfo paramInfo = paramInfos[i]; - if (ReferenceEquals(arg, Type.Missing)) + if (!ReferenceEquals(arg, Type.Missing)) + { + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); + } + else { if (paramInfo.DefaultValue == DBNull.Value) { @@ -187,9 +191,15 @@ BindingFlags invokeAttr } arg = paramInfo.DefaultValue; + if (ReferenceEquals(arg?.GetType(), sigType)) + { + isValueType = RuntimeTypeHandle.IsValueType(sigType); + } + else + { + isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); + } } - - isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } // We need to perform type safety validation against the incoming arguments, but we also need @@ -205,7 +215,11 @@ BindingFlags invokeAttr if (isValueType) { - Debug.Assert(arg != null); +#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; } 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 cee1b44b232c51..d24ef3d9e4287d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -172,6 +172,8 @@ internal void ThrowNoInvokeException() // 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 CheckManyArguments( RuntimeConstructorInfo ci, int argCount, @@ -296,6 +298,8 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] // 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? CheckManyArguments( RuntimeConstructorInfo ci, int argCount, 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 a375e11514ee6c..c933cad24424db 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -170,6 +170,8 @@ private void ThrowNoInvokeException() // 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? CheckManyArguments( RuntimeMethodInfo mi, int argCount, 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/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs index a9ec8644153680..5506db01e32f8c 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1666,6 +1666,12 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) } } + // Once Mono has managed conversion logic, this method can be removed and the Core + // implementation of this method moved to RuntimeMethod.Invoke(). +#if DEBUG + internal void VerifyValueType(object? value) { } +#endif + /// /// Verify and optionally convert the value for special cases. /// @@ -1677,9 +1683,8 @@ internal bool CheckValue( CultureInfo? culture, BindingFlags invokeAttr) { - // These are already fast-pathed by the caller. - Debug.Assert(!(value == null && (RuntimeTypeHandle.IsValueType(this) || RuntimeTypeHandle.IsByRef(this))) || - !ReferenceEquals(value?.GetType(), this)); + // Already fast-pathed by the caller. + Debug.Assert(!ReferenceEquals(value?.GetType(), this)); copyBack = true; From c3a8c14178a771f071a7f872076fcfbf645bd6e7 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sat, 2 Apr 2022 19:35:21 -0500 Subject: [PATCH 25/28] Fix mono error; fix debug-build stack-walk issue --- src/coreclr/vm/appdomain.cpp | 5 ++++- src/coreclr/vm/corelib.h | 3 +++ .../System.Private.CoreLib/src/System/RuntimeType.Mono.cs | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) 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/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs index 5506db01e32f8c..16d19f3bed4c59 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1669,7 +1669,9 @@ internal override FieldInfo GetField(FieldInfo fromNoninstanciated) // 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 /// From 59ef825d93acbe2f2b6fd73e747414d497374932 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sun, 3 Apr 2022 09:06:58 -0500 Subject: [PATCH 26/28] Don't wrap EntryPointNotFoundException; misc naming --- .../src/System/Reflection/Emit/DynamicMethod.cs | 9 +++++++-- .../System/Reflection/RuntimeConstructorInfo.CoreCLR.cs | 5 +++++ .../src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs | 7 ++++++- .../src/System/Reflection/RuntimeConstructorInfo.cs | 8 ++++---- .../src/System/Reflection/RuntimeMethodInfo.cs | 4 ++-- 5 files changed, 24 insertions(+), 9 deletions(-) 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 b716c7cad42c8a..ea71e6919ff677 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 @@ -480,7 +480,7 @@ Signature LazyCreateSignature() } else if (argCount > MaxStackAllocArgCount) { - retValue = CheckManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); + retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else { @@ -521,7 +521,7 @@ Signature LazyCreateSignature() // 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? CheckManyArguments( + private static unsafe object? InvokeWithManyArguments( DynamicMethod mi, int argCount, object? obj, @@ -588,6 +588,11 @@ Signature LazyCreateSignature() { return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } + catch (EntryPointNotFoundException) + { + // Don't wrap since the exception did not originate from within the method. + throw; + } catch (Exception e) { throw new TargetInvocationException(e); 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 35e14b8ebfee40..d868f50fe16d66 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 @@ -106,6 +106,11 @@ internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span copyOfParameters = new(ref argStorage._arg0, 1); - Span parameters = new(ref parameter, 1); + ReadOnlySpan parameters = new(in parameter); Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); StackAllocatedByRefs byrefStorage = default; @@ -378,6 +378,11 @@ public override MethodImplAttributes GetMethodImplementationFlags() { return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } + catch (EntryPointNotFoundException) + { + // Don't wrap since the exception did not originate from within the method. + throw; + } catch (Exception e) { throw new TargetInvocationException(e); 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 d24ef3d9e4287d..492938041d821e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -132,7 +132,7 @@ internal void ThrowNoInvokeException() } else if (argCount > MaxStackAllocArgCount) { - CheckManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); + InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else { @@ -174,7 +174,7 @@ internal void ThrowNoInvokeException() // This is a separate method to support better performance for the faster paths. [DebuggerStepThrough] [DebuggerHidden] - private static unsafe void CheckManyArguments( + private static unsafe void InvokeWithManyArguments( RuntimeConstructorInfo ci, int argCount, object? obj, @@ -257,7 +257,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] } else if (argCount > MaxStackAllocArgCount) { - retValue = CheckManyArguments(this, argCount, invokeAttr, binder, parameters, culture); + retValue = InvokeWithManyArguments(this, argCount, invokeAttr, binder, parameters, culture); } else { @@ -300,7 +300,7 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] // This is a separate method to encourage more efficient IL for the faster paths. [DebuggerStepThrough] [DebuggerHidden] - private static unsafe object? CheckManyArguments( + private static unsafe object? InvokeWithManyArguments( RuntimeConstructorInfo ci, int argCount, BindingFlags invokeAttr, 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 c933cad24424db..adf0955602d492 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -130,7 +130,7 @@ private void ThrowNoInvokeException() } else if (argCount > MaxStackAllocArgCount) { - retValue = CheckManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); + retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else { @@ -172,7 +172,7 @@ private void ThrowNoInvokeException() // This is a separate method to support better performance for the faster paths. [DebuggerStepThrough] [DebuggerHidden] - private static unsafe object? CheckManyArguments( + private static unsafe object? InvokeWithManyArguments( RuntimeMethodInfo mi, int argCount, object? obj, From b667080ba2a8ac6874cea0ba97810e90e2ac9824 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Sun, 3 Apr 2022 11:59:27 -0500 Subject: [PATCH 27/28] Revert previous EntryPointNotFoundException; modify test --- .../src/System/Reflection/Emit/DynamicMethod.cs | 5 ----- .../src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs | 5 ----- .../src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs | 5 ----- src/tests/reflection/DefaultInterfaceMethods/Emit.cs | 4 ++-- .../reflection/DefaultInterfaceMethods/InvokeConsumer.cs | 3 ++- 5 files changed, 4 insertions(+), 18 deletions(-) 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 ea71e6919ff677..6b5e67602f90aa 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 @@ -588,11 +588,6 @@ Signature LazyCreateSignature() { return RuntimeMethodHandle.InvokeMethod(obj, (void**)arguments, Signature, isConstructor: false); } - catch (EntryPointNotFoundException) - { - // Don't wrap since the exception did not originate from within the method. - throw; - } catch (Exception e) { throw new TargetInvocationException(e); 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 d868f50fe16d66..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 @@ -106,11 +106,6 @@ internal unsafe object InvokeNonEmitUnsafe(object? obj, IntPtr* args, Span Date: Mon, 4 Apr 2022 10:13:54 -0500 Subject: [PATCH 28/28] Non-functional nits --- .../src/System/Reflection/Emit/DynamicMethod.cs | 5 ++--- .../src/System/Reflection/MethodBase.cs | 4 ++-- .../src/System/Reflection/RuntimeConstructorInfo.cs | 5 ++--- .../src/System/Reflection/RuntimeMethodInfo.cs | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) 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 6b5e67602f90aa..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 @@ -480,6 +480,7 @@ Signature LazyCreateSignature() } else if (argCount > MaxStackAllocArgCount) { + Debug.Assert(parameters != null); retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else @@ -527,11 +528,9 @@ Signature LazyCreateSignature() object? obj, BindingFlags invokeAttr, Binder? binder, - object?[]? parameters, + object?[] parameters, CultureInfo? culture) { - Debug.Assert(parameters != null); - object[] objHolder = new object[argCount]; Span copyOfParameters = new Span(objHolder, 0, argCount); 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 86d57286ec1d8f..65b371a630191c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -209,7 +209,8 @@ BindingFlags invokeAttr // as we validate them. n.b. This disallows use of ArrayPool, as ArrayPool-rented arrays are // considered user-visible to threads which may still be holding on to returned instances. // This separate array is also used to hold default values when 'null' is specified for value - // types which should not be applied to the incoming array. + // 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; @@ -219,7 +220,6 @@ BindingFlags invokeAttr // 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; } 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 492938041d821e..c5f4d7b1345c7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -132,6 +132,7 @@ internal void ThrowNoInvokeException() } else if (argCount > MaxStackAllocArgCount) { + Debug.Assert(parameters != null); InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else @@ -180,11 +181,9 @@ private static unsafe void InvokeWithManyArguments( object? obj, BindingFlags invokeAttr, Binder? binder, - object?[]? parameters, + object?[] parameters, CultureInfo? culture) { - Debug.Assert(parameters != null); - object[] objHolder = new object[argCount]; Span copyOfParameters = new Span(objHolder, 0, argCount); 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 adf0955602d492..87404f4723de1d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -130,6 +130,7 @@ private void ThrowNoInvokeException() } else if (argCount > MaxStackAllocArgCount) { + Debug.Assert(parameters != null); retValue = InvokeWithManyArguments(this, argCount, obj, invokeAttr, binder, parameters, culture); } else @@ -178,11 +179,9 @@ private void ThrowNoInvokeException() object? obj, BindingFlags invokeAttr, Binder? binder, - object?[]? parameters, + object?[] parameters, CultureInfo? culture) { - Debug.Assert(parameters != null); - object[] objHolder = new object[argCount]; Span copyOfParameters = new Span(objHolder, 0, argCount);