diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 69750286092dcf..b4c4933b6d02e7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -606,6 +606,11 @@ public static object CheckArgument(object srcObject, RuntimeTypeHandle dstType, return InvokeUtils.CheckArgument(srcObject, dstType.ToMethodTable(), InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle); } + public static object CheckArgument(object srcObject, RuntimeTypeHandle dstType, BinderBundle? binderBundle, out bool copyBack) + { + return InvokeUtils.CheckArgument(srcObject, dstType.ToMethodTable(), InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle, out copyBack); + } + // FieldInfo.SetValueDirect() has a completely different set of rules on how to coerce the argument from // the other Reflection api. public static object CheckArgumentForDirectFieldAccess(object srcObject, RuntimeTypeHandle dstType) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs index 0c61f0bb55156d..2cb5fb66b860b2 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/InvokeUtils.cs @@ -42,6 +42,13 @@ internal enum CheckArgumentSemantics internal static object? CheckArgument(object? srcObject, MethodTable* dstEEType, CheckArgumentSemantics semantics, BinderBundle? binderBundle) { + return CheckArgument(srcObject, dstEEType, semantics, binderBundle, out _); + } + + internal static object? CheckArgument(object? srcObject, MethodTable* dstEEType, CheckArgumentSemantics semantics, BinderBundle? binderBundle, out bool copyBack) + { + copyBack = false; + // Methods with ByRefLike types in signatures should be filtered out earlier Debug.Assert(!dstEEType->IsByRefLike); @@ -65,23 +72,24 @@ internal enum CheckArgumentSemantics } else { - MethodTable* srcEEType = srcObject.GetMethodTable(); - - if (srcEEType == dstEEType || - RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) || - (dstEEType->IsInterface && srcObject is Runtime.InteropServices.IDynamicInterfaceCastable castable - && castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false))) + if (IsArgumentAssignable(srcObject, dstEEType)) { return srcObject; } - return CheckArgumentConversions(srcObject, dstEEType, semantics, binderBundle); + return CheckArgumentConversions(srcObject, dstEEType, semantics, binderBundle, out copyBack); } } internal static object? CheckArgumentConversions(object srcObject, MethodTable* dstEEType, CheckArgumentSemantics semantics, BinderBundle? binderBundle) + { + return CheckArgumentConversions(srcObject, dstEEType, semantics, binderBundle, out _); + } + + internal static object? CheckArgumentConversions(object srcObject, MethodTable* dstEEType, CheckArgumentSemantics semantics, BinderBundle? binderBundle, out bool copyBack) { object? dstObject; + copyBack = false; Exception exception = ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(srcObject, dstEEType, semantics, out dstObject); if (exception == null) return dstObject; @@ -93,11 +101,21 @@ internal enum CheckArgumentSemantics Type exactDstType = Type.GetTypeFromHandle(new RuntimeTypeHandle(dstEEType))!; srcObject = binderBundle.ChangeType(srcObject, exactDstType); + copyBack = srcObject is not null && IsArgumentAssignable(srcObject, dstEEType); // For compat with desktop, the result of the binder call gets processed through the default rules again. return CheckArgument(srcObject, dstEEType, semantics, binderBundle: null); } + private static bool IsArgumentAssignable(object srcObject, MethodTable* dstEEType) + { + MethodTable* srcEEType = srcObject.GetMethodTable(); + return srcEEType == dstEEType || + RuntimeImports.AreTypesAssignable(srcEEType, dstEEType) || + (dstEEType->IsInterface && srcObject is Runtime.InteropServices.IDynamicInterfaceCastable castable + && castable.IsInterfaceImplemented(new RuntimeTypeHandle(dstEEType), throwIfNotImplemented: false)); + } + // Special coersion rules for primitives, enums and pointer. private static Exception ConvertOrWidenPrimitivesEnumsAndPointersIfPossible(object srcObject, MethodTable* dstEEType, CheckArgumentSemantics semantics, out object? dstObject) { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs index 00fed3ec6c6f3c..e6f2f76ae0faef 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs @@ -22,7 +22,6 @@ public unsafe class DynamicInvokeInfo private readonly int _argumentCount; private readonly bool _isStatic; // private readonly bool _isValueTypeInstanceMethod; - private readonly bool _needsCopyBack; private readonly Transform _returnTransform; private readonly MethodTable* _returnType; private readonly ArgumentInfo[] _arguments; @@ -77,7 +76,6 @@ public unsafe DynamicInvokeInfo(MethodBase method, IntPtr invokeThunk) Type argumentType = parameters[i].ParameterType; if (argumentType.IsByRef) { - _needsCopyBack = true; transform |= Transform.ByRef; argumentType = argumentType.GetElementType()!; } @@ -419,7 +417,9 @@ private unsafe ref byte InvokeWithManyArguments( RuntimeImports.RhRegisterForGCReporting(®ByRefStorage); Span copyOfParameters = new(ref Unsafe.As(ref *pStorage), argCount); - CheckArguments(copyOfParameters, pByRefStorage, parameters, binderBundle); + Span shouldCopyBack = stackalloc bool[argCount]; + shouldCopyBack.Clear(); + bool needsCopyBack = CheckArguments(copyOfParameters, pByRefStorage, parameters, binderBundle, shouldCopyBack); try { @@ -430,11 +430,9 @@ private unsafe ref byte InvokeWithManyArguments( { throw new TargetInvocationException(e); } - finally - { - if (_needsCopyBack) - CopyBackToArray(ref Unsafe.As(ref *pStorage), parameters); - } + + if (needsCopyBack) + CopyBackToArray(ref Unsafe.As(ref *pStorage), parameters, shouldCopyBack); } finally { @@ -468,13 +466,15 @@ private unsafe ref byte InvokeWithManyArguments( RuntimeImports.RhRegisterForGCReporting(®ByRefStorage); Span copyOfParameters = new(ref Unsafe.As(ref *pStorage), argCount); - CheckArguments(copyOfParameters, pByRefStorage, parameters); + Span shouldCopyBack = stackalloc bool[argCount]; + shouldCopyBack.Clear(); + bool needsCopyBack = CheckArguments(copyOfParameters, pByRefStorage, parameters, shouldCopyBack); ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); - if (_needsCopyBack) - CopyBackToSpan(copyOfParameters, parameters); + if (needsCopyBack) + CopyBackToSpan(copyOfParameters, parameters, shouldCopyBack); } finally { @@ -496,8 +496,10 @@ private unsafe ref byte InvokeWithFewArguments( Span copyOfParameters = ((Span)argStorage._args).Slice(0, _argumentCount); StackAllocatedByRefs byrefStorage = default; void* pByRefStorage = (ByReference*)&byrefStorage; + ArgumentData copyBackStorage = default; + Span shouldCopyBack = ((Span)copyBackStorage).Slice(0, _argumentCount); - CheckArguments(copyOfParameters, pByRefStorage, parameters, binderBundle); + bool needsCopyBack = CheckArguments(copyOfParameters, pByRefStorage, parameters, binderBundle, shouldCopyBack); try { @@ -508,11 +510,9 @@ private unsafe ref byte InvokeWithFewArguments( { throw new TargetInvocationException(e); } - finally - { - if (_needsCopyBack) - CopyBackToArray(ref copyOfParameters[0], parameters); - } + + if (needsCopyBack) + CopyBackToArray(ref copyOfParameters[0], parameters, shouldCopyBack); return ref ret; } @@ -528,19 +528,16 @@ private unsafe ref byte InvokeWithFewArguments( Span copyOfParameters = ((Span)argStorage._args).Slice(0, _argumentCount); StackAllocatedByRefs byrefStorage = default; void* pByRefStorage = (ByReference*)&byrefStorage; + ArgumentData copyBackStorage = default; + Span shouldCopyBack = ((Span)copyBackStorage).Slice(0, _argumentCount); - CheckArguments(copyOfParameters, pByRefStorage, parameters); + bool needsCopyBack = CheckArguments(copyOfParameters, pByRefStorage, parameters, shouldCopyBack); - try - { - ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); - DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); - } - finally - { - if (_needsCopyBack) - CopyBackToSpan(copyOfParameters, parameters); - } + ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); + DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); + + if (needsCopyBack) + CopyBackToSpan(copyOfParameters, parameters, shouldCopyBack); return ref ret; } @@ -560,7 +557,7 @@ private unsafe ref byte InvokeDirectWithFewArguments( ret = ref RawCalliHelper.Call(InvokeThunk, (void*)methodToCall, ref thisArg, ref ret, pByRefStorage); DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); - // No need to call CopyBack here since there are no ref values. + // No need to call CopyBack here since no copy of the arguments was made. return ref ret; } @@ -585,17 +582,25 @@ private unsafe ref byte InvokeDirectWithFewArguments( return defaultValue; } - private unsafe void CheckArguments( + private unsafe bool CheckArguments( Span copyOfParameters, void* byrefParameters, object?[] parameters, - BinderBundle? binderBundle) + BinderBundle? binderBundle, + Span shouldCopyBack) { + bool needsCopyBack = false; + for (int i = 0; i < parameters.Length; i++) { object? arg = parameters[i]; ref readonly ArgumentInfo argumentInfo = ref _arguments[i]; + if ((argumentInfo.Transform & Transform.ByRef) != 0) + { + shouldCopyBack[i] = true; + needsCopyBack = true; + } Again: if (arg is null) @@ -615,8 +620,9 @@ private unsafe void CheckArguments( // Missing is substited by metadata default value arg = GetCoercedDefaultValue(i, in argumentInfo); - // The metadata default value is written back into the parameters array - parameters[i] = arg; + // The metadata default value is written back into the parameters array after invocation. + shouldCopyBack[i] = true; + needsCopyBack = true; if (arg is null) goto Again; // Redo the argument handling to deal with null } @@ -633,7 +639,13 @@ private unsafe void CheckArguments( if ((argumentInfo.Transform & Transform.ByRef) != 0) throw InvokeUtils.CreateChangeTypeArgumentException(srcEEType, argumentInfo.Type, destinationIsByRef: true); - arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle); + bool copyBack; + arg = InvokeUtils.CheckArgumentConversions(arg, argumentInfo.Type, InvokeUtils.CheckArgumentSemantics.DynamicInvoke, binderBundle, out copyBack); + if (copyBack) + { + shouldCopyBack[i] = true; + needsCopyBack = true; + } } if ((argumentInfo.Transform & Transform.Reference) == 0) @@ -664,6 +676,8 @@ private unsafe void CheckArguments( ref Unsafe.As(ref copyOfParameters[i]) : ref arg.GetRawData()); #pragma warning restore 9094 } + + return needsCopyBack; } // This method is equivalent to the one above except that it takes 'Span' instead of 'object[]' @@ -673,11 +687,31 @@ private unsafe void CheckArguments( void* byrefParameters, Span parameters) { + Debug.Assert(parameters.Length <= MaxStackAllocArgCount); + + ArgumentData copyBackStorage = default; + Span shouldCopyBack = ((Span)copyBackStorage).Slice(0, parameters.Length); + CheckArguments(copyOfParameters, byrefParameters, parameters, shouldCopyBack); + } + + private unsafe bool CheckArguments( + Span copyOfParameters, + void* byrefParameters, + Span parameters, + Span shouldCopyBack) + { + bool needsCopyBack = false; + for (int i = 0; i < parameters.Length; i++) { object? arg = parameters[i]; ref readonly ArgumentInfo argumentInfo = ref _arguments[i]; + if ((argumentInfo.Transform & Transform.ByRef) != 0) + { + shouldCopyBack[i] = true; + needsCopyBack = true; + } if (arg is null) { @@ -732,44 +766,45 @@ private unsafe void CheckArguments( ref Unsafe.As(ref copyOfParameters[i]) : ref arg.GetRawData()); #pragma warning restore 9094 } + + return needsCopyBack; } - private unsafe void CopyBackToArray(ref object? src, object?[] dest) + private unsafe void CopyBackToArray(ref object? src, object?[] dest, Span shouldCopyBack) { ArgumentInfo[] arguments = _arguments; - for (int i = 0; i < arguments.Length; i++) + for (int i = 0; i < dest.Length; i++) { - ref readonly ArgumentInfo argumentInfo = ref arguments[i]; - - Transform transform = argumentInfo.Transform; - - if ((transform & Transform.ByRef) == 0) - continue; + if (shouldCopyBack[i]) + { + ref readonly ArgumentInfo argumentInfo = ref arguments[i]; - object? obj = Unsafe.Add(ref src, i); + object? obj = Unsafe.Add(ref src, i); - if ((transform & (Transform.Pointer | Transform.FunctionPointer | Transform.Nullable)) != 0) - { - if ((transform & Transform.Pointer) != 0) - { - Type type = Type.GetTypeFromMethodTable(argumentInfo.Type); - Debug.Assert(type.IsPointer); - obj = Pointer.Box((void*)Unsafe.As(ref obj.GetRawData()), type); - } - else + Transform transform = argumentInfo.Transform; + if ((transform & (Transform.Pointer | Transform.FunctionPointer | Transform.Nullable)) != 0) { - obj = RuntimeExports.RhBox( - (transform & Transform.FunctionPointer) != 0 ? MethodTable.Of() : argumentInfo.Type, - ref obj.GetRawData()); + if ((transform & Transform.Pointer) != 0) + { + Type type = Type.GetTypeFromMethodTable(argumentInfo.Type); + Debug.Assert(type.IsPointer); + obj = Pointer.Box((void*)Unsafe.As(ref obj.GetRawData()), type); + } + else + { + obj = RuntimeExports.RhBox( + (transform & Transform.FunctionPointer) != 0 ? MethodTable.Of() : argumentInfo.Type, + ref obj.GetRawData()); + } } - } - dest[i] = obj; + dest[i] = obj; + } } } - private unsafe void CopyBackToSpan(Span src, Span dest) + private unsafe void CopyBackToSpan(Span src, Span dest, Span shouldCopyBack) { ArgumentInfo[] arguments = _arguments; @@ -777,30 +812,29 @@ private unsafe void CopyBackToSpan(Span src, Span dest) { ref readonly ArgumentInfo argumentInfo = ref arguments[i]; - Transform transform = argumentInfo.Transform; - - if ((transform & Transform.ByRef) == 0) - continue; - - object? obj = src[i]; - - if ((transform & (Transform.Pointer | Transform.FunctionPointer | Transform.Nullable)) != 0) + if (shouldCopyBack[i]) { - if ((transform & Transform.Pointer) != 0) - { - Type type = Type.GetTypeFromMethodTable(argumentInfo.Type); - Debug.Assert(type.IsPointer); - obj = Pointer.Box((void*)Unsafe.As(ref obj.GetRawData()), type); - } - else + object? obj = src[i]; + + Transform transform = argumentInfo.Transform; + if ((transform & (Transform.Pointer | Transform.FunctionPointer | Transform.Nullable)) != 0) { - obj = RuntimeExports.RhBox( - (transform & Transform.FunctionPointer) != 0 ? MethodTable.Of() : argumentInfo.Type, - ref obj.GetRawData()); + if ((transform & Transform.Pointer) != 0) + { + Type type = Type.GetTypeFromMethodTable(argumentInfo.Type); + Debug.Assert(type.IsPointer); + obj = Pointer.Box((void*)Unsafe.As(ref obj.GetRawData()), type); + } + else + { + obj = RuntimeExports.RhBox( + (transform & Transform.FunctionPointer) != 0 ? MethodTable.Of() : argumentInfo.Type, + ref obj.GetRawData()); + } } - } - dest[i] = obj; + dest[i] = obj; + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs index f2c15681c288fe..7fc20e1efd98b3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/CustomMethodInvoker.cs @@ -22,15 +22,20 @@ public CustomMethodInvoker(Type thisType, Type[] parameterTypes, InvokerOptions } protected sealed override object? Invoke(object? thisObject, object?[]? arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) => - InvokeSpecial(thisObject, arguments, binderBundle, wrapInTargetInvocationException); + InvokeSpecial(thisObject, arguments, arguments, binderBundle, wrapInTargetInvocationException); protected internal sealed override object? Invoke(object? thisObject, Span arguments) => - InvokeSpecial(thisObject, arguments, binderBundle: null, wrapInTargetInvocationException: false); + InvokeSpecial(thisObject, arguments, copyBackArguments: null, binderBundle: null, wrapInTargetInvocationException: false); protected internal sealed override object? InvokeDirectWithFewArgs(object? thisObject, Span arguments) => - InvokeSpecial(thisObject, arguments, binderBundle: null, wrapInTargetInvocationException: false); - - private object? InvokeSpecial(object? thisObject, ReadOnlySpan arguments, BinderBundle binderBundle, bool wrapInTargetInvocationException) + InvokeSpecial(thisObject, arguments, copyBackArguments: null, binderBundle: null, wrapInTargetInvocationException: false); + + private object? InvokeSpecial( + object? thisObject, + ReadOnlySpan arguments, + object?[]? copyBackArguments, + BinderBundle binderBundle, + bool wrapInTargetInvocationException) { // This does not handle optional parameters. None of the methods we use custom invocation for have them. if (!(thisObject == null && 0 != (_options & InvokerOptions.AllowNullThis))) @@ -41,10 +46,18 @@ public CustomMethodInvoker(Type thisType, Type[] parameterTypes, InvokerOptions throw new TargetParameterCountException(); object[] convertedArguments = new object[argCount]; + bool[]? shouldCopyBack = null; for (int i = 0; i < convertedArguments.Length; i++) { - convertedArguments[i] = RuntimeAugments.CheckArgument(arguments[i], _parameterTypes[i].TypeHandle, binderBundle); + bool copyBack; + convertedArguments[i] = RuntimeAugments.CheckArgument(arguments[i], _parameterTypes[i].TypeHandle, binderBundle, out copyBack); + if (copyBack) + { + shouldCopyBack ??= new bool[argCount]; + shouldCopyBack[i] = true; + } } + object result; try { @@ -54,6 +67,18 @@ public CustomMethodInvoker(Type thisType, Type[] parameterTypes, InvokerOptions { throw new TargetInvocationException(e); } + + if (shouldCopyBack is not null) + { + Debug.Assert(copyBackArguments is not null); + + for (int i = 0; i < shouldCopyBack.Length; i++) + { + if (shouldCopyBack[i]) + copyBackArguments[i] = convertedArguments[i]; + } + } + return result; } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/Common.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/Common.cs index e70336e7e330c7..d71c15dfeb158e 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/Common.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/Common.cs @@ -128,4 +128,10 @@ public override void ReorderArgumentArray(ref object?[] args, object state) public override PropertyInfo? SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type? returnType, Type[]? indexes, ParameterModifier[]? modifiers) => throw new NotImplementedException(); } + + public class ConvertStringToInt16Binder : ConvertStringToIntBinder + { + public override object ChangeType(object value, Type type, CultureInfo? culture) + => short.Parse((string)value); + } } diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs index e8d791d2f8a19f..7823d7733e2fdf 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/ConstructorInfoTests.cs @@ -94,7 +94,6 @@ public void Invoke_StaticConstructorMultipleTimes() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67531", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_TwoDimensionalArray_CustomBinder_IncorrectTypeArguments() { var ctor = typeof(int[,]).GetConstructor(new[] { typeof(int), typeof(int) }); @@ -106,7 +105,6 @@ public void Invoke_TwoDimensionalArray_CustomBinder_IncorrectTypeArguments() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67531", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_TwoParameters_CustomBinder_IncorrectTypeArgument() { ConstructorInfo[] constructors = GetConstructors(typeof(ClassWith3Constructors)); diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs index bcd709802d43a7..20585f722e1c94 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/MethodInfoTests.cs @@ -467,7 +467,6 @@ public static void Invoke_OptionalParameterUnassingableFromMissing_WithMissingVa } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/67531", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot))] public void Invoke_TwoParameters_CustomBinder_IncorrectTypeArguments() { MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt)); @@ -477,6 +476,16 @@ public void Invoke_TwoParameters_CustomBinder_IncorrectTypeArguments() Assert.True(args[1] is int); } + [Fact] + public void Invoke_CustomBinder_ResultRequiringPrimitiveWidening_DoesNotCopyBackWidenedArgument() + { + MethodInfo method = GetMethod(typeof(MI_SubClass), nameof(MI_SubClass.StaticIntIntMethodReturningInt)); + object[] args = new object[] { "10", 100 }; + + Assert.Equal(110, method.Invoke(null, BindingFlags.Default, new ConvertStringToInt16Binder(), args, null)); + Assert.Equal("10", Assert.IsType(args[0])); + } + [Theory] [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod1), new Type[] { typeof(int) })] [InlineData(typeof(MI_SubClass), nameof(MI_SubClass.GenericMethod2), new Type[] { typeof(string), typeof(int) })] diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Delegates.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Delegates.cs index 2617cb57320dbe..3764ae1c16bf7e 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Delegates.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Delegates.cs @@ -51,7 +51,6 @@ public static int Run() result = Fail; } - TestLinqExpressions.Run(); TestDefaultInterfaceMethods.Run(); return result; @@ -437,39 +436,6 @@ public static void Mutate(ref string x) } } -class TestLinqExpressions -{ - public static void ModifyByRefAndThrow(ref int i) - { - i = 123; - throw new Exception(); - } - - delegate void RefIntDelegate(ref int i); - - public static void Run() - { - Console.WriteLine("Testing LINQ Expressions..."); - - { - ParameterExpression pX = Expression.Parameter(typeof(int).MakeByRefType()); - RefIntDelegate del = - Expression.Lambda( - Expression.Call(null, typeof(TestLinqExpressions).GetMethod(nameof(ModifyByRefAndThrow)), pX), pX).Compile(); - - int i = 0; - try - { - del(ref i); - } - catch (Exception) { } - - if (i != 123) - throw new Exception(); - } - } -} - class TestDefaultInterfaceMethods { interface IFoo