Avoid object[1] allocation in PropertyInfo.SetValue (#54918)
authorStephen Toub <stoub@microsoft.com>
Thu, 1 Jul 2021 00:15:52 +0000 (20:15 -0400)
committerGitHub <noreply@github.com>
Thu, 1 Jul 2021 00:15:52 +0000 (20:15 -0400)
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.cs
src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBase.CoreCLR.cs
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs

index 336c1f0..8b6a412 100644 (file)
@@ -460,7 +460,12 @@ namespace System.Reflection.Emit
             bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
 
             StackAllocedArguments stackArgs = default;
-            Span<object?> arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
+            Span<object?> arguments = default;
+            if (actualCount != 0)
+            {
+                arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
+            }
+
             object? retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, false, wrapExceptions);
 
             // copy out. This should be made only if ByRef are present.
index 532d684..7cd1966 100644 (file)
@@ -65,42 +65,38 @@ namespace System.Reflection
             return parameterTypes;
         }
 
-        private protected Span<object?> CheckArguments(ref StackAllocedArguments stackArgs, object?[]? parameters, Binder? binder,
+        private protected Span<object?> CheckArguments(ref StackAllocedArguments stackArgs, ReadOnlySpan<object?> parameters, Binder? binder,
             BindingFlags invokeAttr, CultureInfo? culture, Signature sig)
         {
             Debug.Assert(Unsafe.SizeOf<StackAllocedArguments>() == StackAllocedArguments.MaxStackAllocArgCount * Unsafe.SizeOf<object>(),
                 "MaxStackAllocArgCount not properly defined.");
+            Debug.Assert(!parameters.IsEmpty);
 
-            Span<object?> copyOfParameters = default;
+            // 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.
 
-            if (parameters is not null)
+            Span<object?> copyOfParameters = (parameters.Length <= StackAllocedArguments.MaxStackAllocArgCount)
+                    ? MemoryMarshal.CreateSpan(ref stackArgs._arg0, parameters.Length)
+                    : new Span<object?>(new object?[parameters.Length]);
+
+            ParameterInfo[]? p = null;
+            for (int i = 0; i < parameters.Length; i++)
             {
-                // 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.
-
-                copyOfParameters = (parameters.Length <= StackAllocedArguments.MaxStackAllocArgCount)
-                        ? MemoryMarshal.CreateSpan(ref stackArgs._arg0, parameters.Length)
-                        : new Span<object?>(new object?[parameters.Length]);
-
-                ParameterInfo[]? p = null;
-                for (int i = 0; i < parameters.Length; i++)
+                object? arg = parameters[i];
+                RuntimeType argRT = sig.Arguments[i];
+
+                if (arg == Type.Missing)
                 {
-                    object? arg = parameters[i];
-                    RuntimeType argRT = sig.Arguments[i];
-
-                    if (arg == Type.Missing)
-                    {
-                        p ??= GetParametersNoCopy();
-                        if (p[i].DefaultValue == System.DBNull.Value)
-                            throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters));
-                        arg = p[i].DefaultValue!;
-                    }
-                    copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr);
+                    p ??= GetParametersNoCopy();
+                    if (p[i].DefaultValue == System.DBNull.Value)
+                        throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters));
+                    arg = p[i].DefaultValue!;
                 }
+                copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr);
             }
 
             return copyOfParameters;
index c991b64..21bcf10 100644 (file)
@@ -340,7 +340,12 @@ namespace System.Reflection
             bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
 
             StackAllocedArguments stackArgs = default;
-            Span<object?> arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
+            Span<object?> arguments = default;
+            if (actualCount != 0)
+            {
+                arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
+            }
+
             object? retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, sig, false, wrapExceptions);
 
             // copy out. This should be made only if ByRef are present.
@@ -394,7 +399,12 @@ namespace System.Reflection
             bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
 
             StackAllocedArguments stackArgs = default;
-            Span<object?> arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
+            Span<object?> arguments = default;
+            if (actualCount != 0)
+            {
+                arguments = CheckArguments(ref stackArgs, parameters, binder, invokeAttr, culture, sig);
+            }
+
             object retValue = RuntimeMethodHandle.InvokeMethod(null, arguments, sig, true, wrapExceptions)!; // ctor must return non-null
 
             // copy out. This should be made only if ByRef are present.
index 9d1023b..f66ab6d 100644 (file)
@@ -410,8 +410,26 @@ namespace System.Reflection
         [Diagnostics.DebuggerHidden]
         public override object? Invoke(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture)
         {
+            // INVOCATION_FLAGS_CONTAINS_STACK_POINTERS 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
+            // be boxed and thus cannot be invoked through reflection which only deals with boxed value type objects.
+            if ((InvocationFlags & (INVOCATION_FLAGS.INVOCATION_FLAGS_NO_INVOKE | INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS)) != 0)
+                ThrowNoInvokeException();
+
+            // check basic method consistency. This call will throw if there are problems in the target/method relationship
+            CheckConsistency(obj);
+
+            Signature sig = Signature;
+            int actualCount = (parameters != null) ? parameters.Length : 0;
+            if (sig.Arguments.Length != actualCount)
+                throw new TargetParameterCountException(SR.Arg_ParmCnt);
+
             StackAllocedArguments stackArgs = default; // try to avoid intermediate array allocation if possible
-            Span<object?> arguments = InvokeArgumentsCheck(ref stackArgs, obj, invokeAttr, binder, parameters, culture);
+            Span<object?> arguments = default;
+            if (actualCount != 0)
+            {
+                arguments = CheckArguments(ref stackArgs, parameters!, binder, invokeAttr, culture, sig);
+            }
 
             bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
             object? retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, false, wrapExceptions);
@@ -426,34 +444,30 @@ namespace System.Reflection
 
         [DebuggerStepThroughAttribute]
         [Diagnostics.DebuggerHidden]
-        private Span<object?> InvokeArgumentsCheck(ref StackAllocedArguments stackArgs, object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? parameters, CultureInfo? culture)
+        internal object? InvokeOneParameter(object? obj, BindingFlags invokeAttr, Binder? binder, object? parameter, CultureInfo? culture)
         {
-            Signature sig = Signature;
-
-            // get the signature
-            int formalCount = sig.Arguments.Length;
-            int actualCount = (parameters != null) ? parameters.Length : 0;
-
-            INVOCATION_FLAGS invocationFlags = InvocationFlags;
-
             // INVOCATION_FLAGS_CONTAINS_STACK_POINTERS 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
             // be boxed and thus cannot be invoked through reflection which only deals with boxed value type objects.
-            if ((invocationFlags & (INVOCATION_FLAGS.INVOCATION_FLAGS_NO_INVOKE | INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS)) != 0)
+            if ((InvocationFlags & (INVOCATION_FLAGS.INVOCATION_FLAGS_NO_INVOKE | INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS)) != 0)
+            {
                 ThrowNoInvokeException();
+            }
 
             // check basic method consistency. This call will throw if there are problems in the target/method relationship
             CheckConsistency(obj);
 
-            if (formalCount != actualCount)
-                throw new TargetParameterCountException(SR.Arg_ParmCnt);
-
-            Span<object?> retVal = default;
-            if (actualCount != 0)
+            Signature sig = Signature;
+            if (sig.Arguments.Length != 1)
             {
-                retVal = CheckArguments(ref stackArgs, parameters!, binder, invokeAttr, culture, sig);
+                throw new TargetParameterCountException(SR.Arg_ParmCnt);
             }
-            return retVal;
+
+            StackAllocedArguments stackArgs = default;
+            Span<object?> arguments = CheckArguments(ref stackArgs, new ReadOnlySpan<object?>(ref parameter, 1), binder, invokeAttr, culture, sig);
+
+            bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
+            return RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, constructor: false, wrapExceptions);
         }
 
         #endregion
index f01ae39..2eb46bf 100644 (file)
@@ -239,7 +239,7 @@ namespace System.Reflection
 
         public override Type PropertyType => Signature.ReturnType;
 
-        public override MethodInfo? GetGetMethod(bool nonPublic)
+        public override RuntimeMethodInfo? GetGetMethod(bool nonPublic)
         {
             if (!Associates.IncludeAccessor(m_getterMethod, nonPublic))
                 return null;
@@ -247,7 +247,7 @@ namespace System.Reflection
             return m_getterMethod;
         }
 
-        public override MethodInfo? GetSetMethod(bool nonPublic)
+        public override RuntimeMethodInfo? GetSetMethod(bool nonPublic)
         {
             if (!Associates.IncludeAccessor(m_setterMethod, nonPublic))
                 return null;
@@ -282,7 +282,7 @@ namespace System.Reflection
                 ParameterInfo[]? methParams = null;
 
                 // First try to get the Get method.
-                MethodInfo? m = GetGetMethod(true);
+                RuntimeMethodInfo? m = GetGetMethod(true);
                 if (m != null)
                 {
                     // There is a Get method so use it.
@@ -337,7 +337,7 @@ namespace System.Reflection
         [Diagnostics.DebuggerHidden]
         public override object? GetValue(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? index, CultureInfo? culture)
         {
-            MethodInfo? m = GetGetMethod(true);
+            RuntimeMethodInfo? m = GetGetMethod(true);
             if (m == null)
                 throw new ArgumentException(System.SR.Arg_GetMethNotFnd);
             return m.Invoke(obj, invokeAttr, binder, index, null);
@@ -359,28 +359,26 @@ namespace System.Reflection
         [Diagnostics.DebuggerHidden]
         public override void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, object?[]? index, CultureInfo? culture)
         {
-            MethodInfo? m = GetSetMethod(true);
+            RuntimeMethodInfo? m = GetSetMethod(true);
 
             if (m == null)
                 throw new ArgumentException(System.SR.Arg_SetMethNotFnd);
 
-            object?[] args;
-            if (index != null)
+            if (index is null)
             {
-                args = new object[index.Length + 1];
+                m.InvokeOneParameter(obj, invokeAttr, binder, value, culture);
+            }
+            else
+            {
+                var args = new object?[index.Length + 1];
 
                 for (int i = 0; i < index.Length; i++)
                     args[i] = index[i];
 
                 args[index.Length] = value;
-            }
-            else
-            {
-                args = new object[1];
-                args[0] = value;
-            }
 
-            m.Invoke(obj, invokeAttr, binder, args, culture);
+                m.Invoke(obj, invokeAttr, binder, args, culture);
+            }
         }
         #endregion