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.IsEmpty); 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]; 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 if (!ReferenceEquals(arg, Type.Missing)) { // Slow path that supports type conversions. isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } else { // Convert Type.Missing to the default value. paramInfos ??= GetParametersNoCopy(); ParameterInfo paramInfo = paramInfos[i]; if (paramInfo.DefaultValue == DBNull.Value) { throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); } arg = paramInfo.DefaultValue; if (ReferenceEquals(arg?.GetType(), sigType)) { // Fast path when the default value's type matches the signature type. isValueType = RuntimeTypeHandle.IsValueType(sigType); } else { if (arg != null && sigType.IsNullableOfT) { // In case if the parameter is nullable Enum type 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); } } 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)) || (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; } } }
private protected unsafe void CheckArguments( Span <object?> copyOfParameters, IntPtr *byrefParameters, Span <bool> shouldCopyBack, ReadOnlySpan <object?> parameters, RuntimeType[] sigTypes, Binder?binder, CultureInfo?culture, BindingFlags invokeAttr ) { Debug.Assert(!parameters.IsEmpty); ParameterInfo[]? paramInfos = null; for (int i = 0; i < parameters.Length; i++) { bool copyBackArg = false; bool isValueType; object? arg = parameters[i]; RuntimeType sigType = sigTypes[i]; if (arg is null) { // Fast path that avoids calling CheckValue() for reference types. isValueType = RuntimeTypeHandle.IsValueType(sigType); if (isValueType || RuntimeTypeHandle.IsByRef(sigType)) { isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } } else if (ReferenceEquals(arg.GetType(), sigType)) { // Fast path that avoids calling CheckValue() when argument value matches the signature type. isValueType = RuntimeTypeHandle.IsValueType(sigType); } else { paramInfos ??= GetParametersNoCopy(); ParameterInfo paramInfo = paramInfos[i]; if (!ReferenceEquals(arg, Type.Missing)) { isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } else { if (paramInfo.DefaultValue == DBNull.Value) { throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); } arg = paramInfo.DefaultValue; if (ReferenceEquals(arg?.GetType(), sigType)) { isValueType = RuntimeTypeHandle.IsValueType(sigType); } else { isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } } } // We need to perform type safety validation against the incoming arguments, but we also need // to be resilient against the possibility that some other thread (or even the binder itself!) // may mutate the array after we've validated the arguments but before we've properly invoked // the method. The solution is to copy the arguments to a different, not-user-visible buffer // as we validate them. n.b. This disallows use of ArrayPool, as ArrayPool-rented arrays are // considered user-visible to threads which may still be holding on to returned instances. // This separate array is also used to hold default values when 'null' is specified for value // types, and also used to hold the results from conversions such as from Int16 to Int32; these // default values and conversions should not be applied to the incoming arguments. shouldCopyBack[i] = copyBackArg; copyOfParameters[i] = arg; if (isValueType) { #if DEBUG // Once Mono has managed conversion logic, VerifyValueType() can be lifted here as Asserts. sigType.VerifyValueType(arg); #endif ByReference <byte> valueTypeRef = new(ref copyOfParameters[i] !.GetRawData()); *(ByReference <byte> *)(byrefParameters + i) = valueTypeRef; } else { ByReference <object?> objRef = new(ref copyOfParameters[i]); *(ByReference <object?> *)(byrefParameters + i) = objRef; } } }