Skip to content

Commit 217fd74

Browse files
authored
Fix check for invocation of async infrastructure methods via reflection (#121609)
MethodInfo.Invoke has multiple implementation strategies. The check for async infrastructure methods needs to be placed in the code that is shared by all implementation strategies. Before this change, this check would not kick in under managed debugger or when the test is run with `Switch.System.Reflection.ForceEmitInvoke=true` appcontext switch. Also, this check should fail with NotSupportedException, without wrapping it in TargetInvocationException() to match existing behaviors.
1 parent 04c4fe6 commit 217fd74

File tree

9 files changed

+36
-15
lines changed

9 files changed

+36
-15
lines changed

src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ private ParameterInfo[] FetchNonReturnParameters() =>
7878

7979
private ParameterInfo FetchReturnParameter() =>
8080
m_returnParameter ??= RuntimeParameterInfo.GetReturnParameter(this, this, Signature);
81+
82+
private bool IsDisallowedAsyncHelper =>
83+
RuntimeMethodHandle.IsAsyncMethod(new RuntimeMethodHandleInternal(m_handle));
8184
#endregion
8285

8386
#region Internal Members

src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,9 @@ internal static IRuntimeMethodInfo StripMethodInstantiation(IRuntimeMethodInfo m
13371337
[MethodImpl(MethodImplOptions.InternalCall)]
13381338
internal static extern bool IsConstructor(RuntimeMethodHandleInternal method);
13391339

1340+
[MethodImpl(MethodImplOptions.InternalCall)]
1341+
internal static extern bool IsAsyncMethod(RuntimeMethodHandleInternal method);
1342+
13401343
[MethodImpl(MethodImplOptions.InternalCall)]
13411344
private static extern LoaderAllocator GetLoaderAllocatorInternal(RuntimeMethodHandleInternal method);
13421345

src/coreclr/vm/ecalllist.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ FCFuncStart(gRuntimeMethodHandle)
148148
FCFuncElement("GetMethodFromCanonical", RuntimeMethodHandle::GetMethodFromCanonical)
149149
FCFuncElement("IsDynamicMethod", RuntimeMethodHandle::IsDynamicMethod)
150150
FCFuncElement("IsConstructor", RuntimeMethodHandle::IsConstructor)
151+
FCFuncElement("IsAsyncMethod", RuntimeMethodHandle::IsAsyncMethod)
151152
FCFuncElement("GetResolver", RuntimeMethodHandle::GetResolver)
152153
FCFuncElement("GetLoaderAllocatorInternal", RuntimeMethodHandle::GetLoaderAllocatorInternal)
153154
FCFuncEnd()

src/coreclr/vm/reflectioninvocation.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,11 +342,6 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_InvokeMethod(
342342
COMPlusThrow(kNotSupportedException, W("NotSupported_Type"));
343343
}
344344

345-
if (pMeth->IsAsyncMethod())
346-
{
347-
COMPlusThrow(kNotSupportedException, W("NotSupported_Async"));
348-
}
349-
350345
#ifdef _DEBUG
351346
if (g_pConfig->ShouldInvokeHalt(pMeth))
352347
{

src/coreclr/vm/runtimehandles.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,8 +2120,19 @@ FCIMPL1(FC_BOOL_RET, RuntimeMethodHandle::IsConstructor, MethodDesc *pMethod)
21202120
}
21212121
CONTRACTL_END;
21222122

2123-
BOOL ret = (BOOL)pMethod->IsClassConstructorOrCtor();
2124-
FC_RETURN_BOOL(ret);
2123+
FC_RETURN_BOOL(pMethod->IsClassConstructorOrCtor());
2124+
}
2125+
FCIMPLEND
2126+
2127+
FCIMPL1(FC_BOOL_RET, RuntimeMethodHandle::IsAsyncMethod, MethodDesc *pMethod)
2128+
{
2129+
CONTRACTL {
2130+
FCALL_CHECK;
2131+
PRECONDITION(CheckPointer(pMethod));
2132+
}
2133+
CONTRACTL_END;
2134+
2135+
FC_RETURN_BOOL(pMethod->IsAsyncMethod());
21252136
}
21262137
FCIMPLEND
21272138

src/coreclr/vm/runtimehandles.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ class RuntimeMethodHandle
215215

216216
static FCDECL1(FC_BOOL_RET, IsConstructor, MethodDesc *pMethod);
217217

218+
static FCDECL1(FC_BOOL_RET, IsAsyncMethod, MethodDesc *pMethod);
219+
218220
static FCDECL1(Object*, GetLoaderAllocatorInternal, MethodDesc *pMethod);
219221
};
220222

src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ internal InvocationFlags ComputeAndUpdateInvocationFlags()
1616
InvocationFlags invocationFlags = InvocationFlags.Unknown;
1717

1818
Type? declaringType = DeclaringType;
19+
Type returnType = ReturnType;
1920

2021
if (ContainsGenericParameters // Method has unbound generics
21-
|| IsDisallowedByRefType(ReturnType) // Return type is an invalid by-ref (i.e., by-ref-like or void*)
22+
|| IsDisallowedByRefType(returnType) // Return type is an invalid by-ref (i.e., by-ref-like or void*)
2223
|| (CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs // Managed varargs
24+
|| IsDisallowedAsyncHelper
2325
)
2426
{
2527
invocationFlags = InvocationFlags.NoInvoke;
@@ -38,7 +40,7 @@ internal InvocationFlags ComputeAndUpdateInvocationFlags()
3840
}
3941
}
4042

41-
if (ReturnType.IsByRefLike) // Check for byref-like types for return
43+
if (returnType.IsByRefLike) // Check for byref-like types for return
4244
{
4345
invocationFlags |= InvocationFlags.ContainsStackPointers;
4446
}
@@ -60,6 +62,8 @@ static bool IsDisallowedByRefType(Type type)
6062
[DoesNotReturn]
6163
internal void ThrowNoInvokeException()
6264
{
65+
Type? declaringType = DeclaringType;
66+
6367
// method is on a class that contains stack pointers
6468
if ((InvocationFlags & InvocationFlags.ContainsStackPointers) != 0)
6569
{
@@ -71,7 +75,7 @@ internal void ThrowNoInvokeException()
7175
throw new NotSupportedException();
7276
}
7377
// method is generic or on a generic class
74-
else if (DeclaringType!.ContainsGenericParameters || ContainsGenericParameters)
78+
else if ((declaringType != null && declaringType.ContainsGenericParameters) || ContainsGenericParameters)
7579
{
7680
throw new InvalidOperationException(SR.Arg_UnboundGenParam);
7781
}
@@ -88,6 +92,10 @@ internal void ThrowNoInvokeException()
8892
if (elementType == typeof(void))
8993
throw new NotSupportedException(SR.NotSupported_ByRefToVoidReturn);
9094
}
95+
else if (IsDisallowedAsyncHelper)
96+
{
97+
throw new NotSupportedException(SR.NotSupported_Async);
98+
}
9199

92100
throw new TargetException();
93101
}

src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.Mono.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,8 @@ public override IList<CustomAttributeData> GetCustomAttributesData()
705705
}
706706

707707
public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeMethodInfo>(other);
708+
709+
private static bool IsDisallowedAsyncHelper => false;
708710
}
709711
#region Sync with _MonoReflectionMethod in object-internals.h
710712
[StructLayout(LayoutKind.Sequential)]

src/tests/async/reflection/reflection-simple.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ public static void MethodInfo_Invoke_AsyncHelper()
2727
{
2828
var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!;
2929
Assert.NotNull(mi);
30-
31-
if (TestLibrary.Utilities.IsNativeAot)
32-
Assert.Throws<NotSupportedException>(() => mi.Invoke(null, new object[] { FooTask() }));
33-
else
34-
Assert.Throws<TargetInvocationException>(() => mi.Invoke(null, new object[] { FooTask() }));
30+
Assert.Throws<NotSupportedException>(() => mi.Invoke(null, new object[] { FooTask() }));
3531

3632
// Sadly the following does not throw and results in UB
3733
// We cannot completely prevent putting a token of an Async method into IL stream.

0 commit comments

Comments
 (0)