private protected unsafe void CheckArguments( Span <object?> copyOfParameters, IntPtr *byrefParameters, Span <ParameterCopyBackAction> shouldCopyBack, ReadOnlySpan <object?> parameters, RuntimeType[] sigTypes, Binder?binder, CultureInfo?culture, BindingFlags invokeAttr ) { Debug.Assert(parameters.Length > 0); ParameterInfo[]? paramInfos = null; for (int i = 0; i < parameters.Length; i++) { ParameterCopyBackAction copyBackArg = default; bool isValueType = false; object? arg = parameters[i]; RuntimeType sigType = sigTypes[i]; // Convert a Type.Missing to the default value. if (ReferenceEquals(arg, Type.Missing)) { paramInfos ??= GetParametersNoCopy(); ParameterInfo paramInfo = paramInfos[i]; if (paramInfo.DefaultValue == DBNull.Value) { throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); } arg = paramInfo.DefaultValue; if (sigType.IsNullableOfT) { copyBackArg = ParameterCopyBackAction.CopyNullable; if (arg is not null) { // For nullable Enum types, the ParameterInfo.DefaultValue returns a raw value which // needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924 Type argumentType = sigType.GetGenericArguments()[0]; if (argumentType.IsEnum) { arg = Enum.ToObject(argumentType, arg); } } } else { copyBackArg = ParameterCopyBackAction.Copy; } } if (arg is null) { // Fast path for null reference types. isValueType = RuntimeTypeHandle.IsValueType(sigType); if (isValueType || RuntimeTypeHandle.IsByRef(sigType)) { isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } } else { RuntimeType argType = (RuntimeType)arg.GetType(); if (ReferenceEquals(argType, sigType)) { // Fast path when the value's type matches the signature type. isValueType = RuntimeTypeHandle.IsValueType(argType); } else if (sigType.TryByRefFastPath(ref arg, ref isValueType)) { // Fast path when the value's type matches the signature type of a byref parameter. copyBackArg = ParameterCopyBackAction.Copy; } else { // Slow path that supports type conversions. 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 // to be resilient against the possibility that some other thread (or even the binder itself!) // may mutate the array after we've validated the arguments but before we've properly invoked // the method. The solution is to copy the arguments to a different, not-user-visible buffer // as we validate them. n.b. This disallows use of ArrayPool, as ArrayPool-rented arrays are // considered user-visible to threads which may still be holding on to returned instances. // This separate array is also used to hold default values when 'null' is specified for value // types, and also used to hold the results from conversions such as from Int16 to Int32. For // compat, these default values and conversions are not be applied to the incoming arguments. shouldCopyBack[i] = copyBackArg; copyOfParameters[i] = arg; if (isValueType) { #if !MONO // Temporary until Mono is updated. Debug.Assert(arg != null); Debug.Assert( arg.GetType() == sigType || (sigType.IsPointer && (arg.GetType() == typeof(IntPtr) || arg.GetType() == typeof(UIntPtr))) || (sigType.IsByRef && arg.GetType() == RuntimeTypeHandle.GetElementType(sigType)) || ((sigType.IsEnum || arg.GetType().IsEnum) && RuntimeType.GetUnderlyingType((RuntimeType)arg.GetType()) == RuntimeType.GetUnderlyingType(sigType))); #endif ByReference valueTypeRef = ByReference.Create(ref copyOfParameters[i] !.GetRawData()); *(ByReference *)(byrefParameters + i) = valueTypeRef; } else { ByReference objRef = ByReference.Create(ref copyOfParameters[i]); *(ByReference *)(byrefParameters + i) = objRef; } } }