This commit is contained in:
Vladimir Sadov 2025-07-30 06:58:23 -07:00 committed by GitHub
commit c4f2871486
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 276 additions and 84 deletions

View File

@ -2736,20 +2736,30 @@ namespace System
im.InterfaceMethods = new MethodInfo[ifaceVirtualMethodCount];
im.TargetMethods = new MethodInfo[ifaceVirtualMethodCount];
int actualCount = 0;
for (int i = 0; i < ifaceVirtualMethodCount; i++)
{
RuntimeMethodHandleInternal ifaceRtMethodHandle = RuntimeTypeHandle.GetMethodAt(ifaceRtType, i);
// GetMethodAt may return null handle for methods that do not exist or are not supposed
// to be seen in reflection. One example is async variant methods.
// We do not record mapping for interface methods that do not exist.
if (ifaceRtMethodHandle.IsNullHandle())
continue;
// GetMethodBase will convert this to the instantiating/unboxing stub if necessary
MethodBase ifaceMethodBase = GetMethodBase(ifaceRtType, ifaceRtMethodHandle)!;
Debug.Assert(ifaceMethodBase is RuntimeMethodInfo);
im.InterfaceMethods[i] = (MethodInfo)ifaceMethodBase;
im.InterfaceMethods[actualCount] = (MethodInfo)ifaceMethodBase;
// If the impl is null, then virtual stub dispatch is active.
RuntimeMethodHandleInternal classRtMethodHandle = TypeHandle.GetInterfaceMethodImplementation(ifaceRtTypeHandle, ifaceRtMethodHandle);
if (classRtMethodHandle.IsNullHandle())
{
actualCount++;
continue;
}
// If we resolved to an interface method, use the interface type as reflected type. Otherwise use `this`.
RuntimeType reflectedType = RuntimeMethodHandle.GetDeclaringType(classRtMethodHandle);
@ -2764,10 +2774,16 @@ namespace System
// the TargetMethod provided to us by runtime internals may be a generic method instance,
// potentially with invalid arguments. TargetMethods in the InterfaceMap should never be
// instances, only definitions.
im.TargetMethods[i] = (targetMethod is { IsGenericMethod: true, IsGenericMethodDefinition: false })
im.TargetMethods[actualCount++] = (targetMethod is { IsGenericMethod: true, IsGenericMethodDefinition: false })
? targetMethod.GetGenericMethodDefinition() : targetMethod!;
}
if (actualCount != ifaceVirtualMethodCount)
{
Array.Resize(ref im.InterfaceMethods, actualCount);
Array.Resize(ref im.TargetMethods, actualCount);
}
return im;
}
#endregion

View File

@ -709,7 +709,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64
#endif
// Runtime-async
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 0, "Enables runtime async method support")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 1, "Enables runtime async method support")
///
/// Uncategorized

View File

@ -18,7 +18,7 @@ bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_
_ASSERTE(methodILDecoder != NULL);
_ASSERTE(*resolver == NULL && *methodILDecoder == NULL);
_ASSERTE(IsIL());
_ASSERTE(GetRVA() == 0);
_ASSERTE(!HasILHeader());
if (!IsAsyncThunkMethod())
{

View File

@ -913,7 +913,7 @@ PTR_COR_ILMETHOD ILCodeVersion::GetIL() const
{
PTR_Module pModule = GetModule();
PTR_MethodDesc pMethodDesc = dac_cast<PTR_MethodDesc>(pModule->LookupMethodDef(GetMethodDef()));
if (pMethodDesc != NULL)
if (pMethodDesc != NULL && pMethodDesc->MayHaveILHeader())
{
pIL = dac_cast<PTR_COR_ILMETHOD>(pMethodDesc->GetILHeader());
}

View File

@ -326,7 +326,6 @@ protected:
void GetExceptionName(_Out_writes_(maxLength) WCHAR* targetParam, int maxLength);
void GetPackageMoniker(_Out_writes_(maxLength) WCHAR* targetParam, int maxLength);
void GetPRAID(_Out_writes_(maxLength) WCHAR* targetParam, int maxLength);
void GetIlRva(_Out_writes_(maxLength) WCHAR* targetParam, int maxLength);
public:
BaseBucketParamsManager(GenericModeBlock* pGenericModeBlock, TypeOfReportedError typeOfError, PCODE initialFaultingPc, Thread* pFaultingThread, OBJECTREF* pThrownException);
@ -810,31 +809,6 @@ void BaseBucketParamsManager::GetPRAID(_Out_writes_(maxLength) WCHAR* targetPara
_ASSERTE(!"PRAID support NYI for CoreCLR");
}
void BaseBucketParamsManager::GetIlRva(_Out_writes_(maxLength) WCHAR* targetParam, int maxLength)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
DWORD ilOffset = GetILOffset();
if (ilOffset == MAXDWORD)
ilOffset = 0;
if (m_pFaultingMD)
ilOffset += m_pFaultingMD->GetRVA();
_snwprintf_s(targetParam,
maxLength,
_TRUNCATE,
W("%x"),
ilOffset);
}
// helper functions
DWORD BaseBucketParamsManager::GetILOffset()

View File

@ -264,8 +264,6 @@ public:
virtual COR_ILMETHOD* MethodDescGetILHeader(MethodDesc *pFD) = 0;
virtual ULONG MethodDescGetRVA(MethodDesc *pFD) = 0;
virtual void MarkDebuggerAttached(void) = 0;
virtual void MarkDebuggerUnattached(void) = 0;

View File

@ -721,19 +721,6 @@ COR_ILMETHOD* EEDbgInterfaceImpl::MethodDescGetILHeader(MethodDesc *pFD)
RETURN NULL;
}
ULONG EEDbgInterfaceImpl::MethodDescGetRVA(MethodDesc *pFD)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckPointer(pFD));
}
CONTRACTL_END;
return pFD->GetRVA();
}
MethodDesc *EEDbgInterfaceImpl::FindLoadedMethodRefOrDef(Module* pModule,
mdToken memberRef)
{

View File

@ -156,8 +156,6 @@ public:
COR_ILMETHOD* MethodDescGetILHeader(MethodDesc *pFD);
ULONG MethodDescGetRVA(MethodDesc *pFD);
MethodDesc *FindLoadedMethodRefOrDef(Module* pModule,
mdToken memberRef);

View File

@ -7680,20 +7680,16 @@ CEEInfo::getMethodInfo(
JIT_TO_EE_TRANSITION();
MethodDesc* ftn = GetMethod(ftnHnd);
if (ftn->IsDynamicMethod())
{
getMethodInfoWorker(ftn, NULL, methInfo, context);
result = true;
}
else if (!ftn->IsWrapperStub() && ftn->HasILHeader())
if (ftn->HasILHeader())
{
// Get the IL header and set it.
COR_ILMETHOD_DECODER header(ftn->GetILHeader(), ftn->GetMDImport(), NULL);
getMethodInfoWorker(ftn, &header, methInfo, context);
result = true;
}
else if (ftn->IsIL() && ftn->GetRVA() == 0) // IL methods with no RVA indicate there is no implementation defined in metadata.
else if (ftn->IsIL() || ftn->IsDynamicMethod())
{
// IL methods with no IL header indicate there is no implementation defined in metadata.
getMethodInfoWorker(ftn, NULL, methInfo, context);
result = true;
}
@ -7875,7 +7871,7 @@ CorInfoInline CEEInfo::canInline (CORINFO_METHOD_HANDLE hCaller,
goto exit;
}
}
else if (pCallee->IsIL() && pCallee->GetRVA() == 0)
else if (pCallee->IsIL())
{
CORINFO_METHOD_INFO methodInfo;
getMethodInfoWorker(pCallee, NULL, &methodInfo);
@ -12788,7 +12784,7 @@ void CEECodeGenInfo::getEHinfo(
COR_ILMETHOD_DECODER header(pMD->GetILHeader(), pMD->GetMDImport(), NULL);
getEHinfoHelper(ftn, EHnumber, clause, &header);
}
else if (pMD->IsIL() && pMD->GetRVA() == 0)
else if (pMD->IsIL())
{
TransientMethodDetails* details;
if (!FindTransientMethodDetails(pMD, &details))

View File

@ -1123,8 +1123,10 @@ BOOL MethodDesc::HasRetBuffArg()
}
//*******************************************************************************
// This returns the offset of the IL.
// The offset is relative to the base of the IL image.
// This typically returns the offset of the IL.
// Another case when a method may have an RVA is earlybound IJW PInvokes,
// in which case the RVA is referring to native code.
// The offset is relative to the base of the image.
ULONG MethodDesc::GetRVA()
{
CONTRACTL
@ -1133,27 +1135,11 @@ ULONG MethodDesc::GetRVA()
GC_NOTRIGGER;
FORBID_FAULT;
SUPPORTS_DAC;
PRECONDITION((IsIL() && MayHaveILHeader()) ||
(IsNDirect() && ((NDirectMethodDesc*)this)->IsEarlyBound()));
}
CONTRACTL_END
if (IsRuntimeSupplied())
{
return 0;
}
// Methods without metadata don't have an RVA. Examples are IL stubs and LCG methods.
if (IsNoMetadata())
{
return 0;
}
// Between two Async variants of the same method only one represents the actual IL.
// It is the variant that is not a thunk.
if (IsAsyncThunkMethod())
{
return 0;
}
if (GetMemberDef() & 0x00FFFFFF)
{
Module *pModule = GetModule();
@ -1198,8 +1184,7 @@ COR_ILMETHOD* MethodDesc::GetILHeader()
{
THROWS;
GC_NOTRIGGER;
PRECONDITION(IsIL());
PRECONDITION(!IsUnboxingStub());
PRECONDITION(MayHaveILHeader());
}
CONTRACTL_END

View File

@ -901,7 +901,8 @@ public:
MODE_ANY;
}
CONTRACTL_END;
return IsIL() && !IsUnboxingStub() && GetRVA();
return MayHaveILHeader() && GetRVA();
}
COR_ILMETHOD* GetILHeader();
@ -1499,6 +1500,14 @@ public:
BOOL MayHaveNativeCode();
BOOL MayHaveILHeader()
{
LIMITED_METHOD_DAC_CONTRACT;
// methods with transient IL bodies do not have IL headers
return IsIL() && !IsUnboxingStub() && !IsAsyncThunkMethod();
}
ULONG GetRVA();
public:

View File

@ -706,15 +706,20 @@ namespace
_ASSERTE(pConfig != NULL);
_ASSERTE(pDecoderMemory != NULL);
if (!pMD->MayHaveILHeader())
return NULL;
COR_ILMETHOD_DECODER* pHeader = NULL;
COR_ILMETHOD* ilHeader = pConfig->GetILHeader();
// For a Runtime Async method the methoddef maps to a Task-returning thunk with runtime-provided implementation,
// while the default IL belongs to the Async implementation variant.
// By default the config captures the default methoddesc, which would be a thunk, thus no IL header.
// So, if config provides no header and we see an implementation method desc, then just ask the method desc itself.
if (ilHeader == NULL && pMD->IsAsyncVariantMethod() && !pMD->IsAsyncThunkMethod())
if (ilHeader == NULL && pMD->IsAsyncVariantMethod())
{
_ASSERTE(!pMD->IsAsyncThunkMethod());
ilHeader = pMD->GetILHeader();
}

View File

@ -342,6 +342,11 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_InvokeMethod(
COMPlusThrow(kNotSupportedException, W("NotSupported_Type"));
}
if (pMeth->IsAsyncMethod())
{
COMPlusThrow(kNotSupportedException, W("NotSupported_Async"));
}
#ifdef _DEBUG
if (g_pConfig->ShouldInvokeHalt(pMeth))
{

View File

@ -441,6 +441,15 @@ extern "C" MethodDesc* QCALLTYPE RuntimeTypeHandle_GetMethodAt(MethodTable* pMT,
}
}
if (pRetMethod != NULL && pRetMethod->IsAsyncVariantMethod())
{
// do not return methoddescs for async variants.
// NOTE: The only scenario where this is relevant is when caller iterates through all slots.
// If GetMethodAt is used to find a method associated with another one,
// then we would be starting with "real" method and will get a "real" method here.
pRetMethod = NULL;
}
END_QCALL;
return pRetMethod;

View File

@ -1019,7 +1019,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET
_ASSERTE(methodILDecoder != NULL);
_ASSERTE(*resolver == NULL && *methodILDecoder == NULL);
_ASSERTE(IsIL());
_ASSERTE(GetRVA() == 0);
_ASSERTE(!HasILHeader());
// The UnsafeAccessorAttribute is applied to methods with an
// RVA of 0 (for example, C#'s extern keyword).

View File

@ -2987,6 +2987,9 @@
<data name="NotSupported_ByRefToVoidReturn" xml:space="preserve">
<value>ByRef to void return values are not supported in reflection invocation.</value>
</data>
<data name="NotSupported_Async" xml:space="preserve">
<value>Async infrastructure methods are not supported in reflection invocation.</value>
</data>
<data name="NotSupported_CallToVarArg" xml:space="preserve">
<value>Vararg calling convention not supported.</value>
</data>

View File

@ -6,7 +6,7 @@
<PropertyGroup>
<!-- runtime async testing in main repo NYI -->
<DisableProjectBuild>true</DisableProjectBuild>
<!-- <DisableProjectBuild>true</DisableProjectBuild> -->
</PropertyGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets, $(MSBuildThisFileDirectory)..))" />

View File

@ -2,14 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading.Tasks;
using Xunit;
public class Async2Reflection
{
[Fact]
public static void TestEntryPoint()
public static void MethodInfo_Invoke_TaskReturning()
{
var mi = typeof(Async2Reflection).GetMethod("Foo", BindingFlags.Static | BindingFlags.NonPublic)!;
Task<int> r = (Task<int>)mi.Invoke(null, null)!;
@ -19,15 +22,217 @@ public class Async2Reflection
Assert.Equal(100, (int)(r.Result + d.Bar().Result));
}
#pragma warning disable SYSLIB5007 // 'System.Runtime.CompilerServices.AsyncHelpers' is for evaluation purposes only
[Fact]
public static void MethodInfo_Invoke_AsyncHelper()
{
var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!;
Assert.NotNull(mi);
Assert.Throws<TargetInvocationException>(() => mi.Invoke(null, new object[] { FooTask() }));
// Sadly the following does not throw and results in UB
// We cannot completely prevent putting a token of an Async method into IL stream.
// CONSIDER: perhaps JIT could throw?
//
// dynamic d = FooTask();
// System.Runtime.CompilerServices.AsyncHelpers.Await(d);
}
#pragma warning restore SYSLIB5007
private static async Task<int> Foo()
{
await Task.Yield();
return 90;
}
private static async Task FooTask()
{
await Task.Yield();
}
private async Task<int> Bar()
{
await Task.Yield();
return 10;
}
[Fact]
public static void AwaitTaskReturningExpressionLambda()
{
var expr1 = (Expression<Func<Task<int>>>)(() => Task.FromResult(42));
var del = expr1.Compile();
Assert.Equal(42, del().Result);
AwaitF(42, del).GetAwaiter().GetResult();
}
static async Task AwaitF<T>(T expected, Func<Task<T>> f)
{
var res = await f.Invoke();
Assert.Equal(expected, res);
}
public interface IExample<T>
{
Task TaskReturning();
T TReturning();
}
public class ExampleClass : IExample<Task>
{
public Task TaskReturning()
{
return null;
}
public Task TReturning()
{
return null;
}
}
public struct ExampleStruct : IExample<Task>
{
public Task TaskReturning()
{
return null;
}
public Task TReturning()
{
return null;
}
}
[Fact]
public static void GetInterfaceMap()
{
Type interfaceType = typeof(IExample<Task>);
Type classType = typeof(ExampleClass);
InterfaceMapping map = classType.GetInterfaceMap(interfaceType);
Assert.Equal(2, map.InterfaceMethods.Length);
Assert.Equal("System.Threading.Tasks.Task TaskReturning() --> System.Threading.Tasks.Task TaskReturning()",
$"{map.InterfaceMethods[0]?.ToString()} --> {map.TargetMethods[0]?.ToString()}");
Assert.Equal("System.Threading.Tasks.Task TReturning() --> System.Threading.Tasks.Task TReturning()",
$"{map.InterfaceMethods[1]?.ToString()} --> {map.TargetMethods[1]?.ToString()}");
Type structType = typeof(ExampleStruct);
map = structType.GetInterfaceMap(interfaceType);
Assert.Equal(2, map.InterfaceMethods.Length);
Assert.Equal("System.Threading.Tasks.Task TaskReturning() --> System.Threading.Tasks.Task TaskReturning()",
$"{map.InterfaceMethods[0]?.ToString()} --> {map.TargetMethods[0]?.ToString()}");
Assert.Equal("System.Threading.Tasks.Task TReturning() --> System.Threading.Tasks.Task TReturning()",
$"{map.InterfaceMethods[1]?.ToString()} --> {map.TargetMethods[1]?.ToString()}");
}
[Fact]
public static void TypeBuilder_DefineMethod()
{
// we will be compiling a dynamic vesion of this method
//
// public async static Task StaticMethod(Task arg)
// {
// await arg;
// }
// Define a dynamic assembly and module
AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
// Define a type
TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public);
// Define a method
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
"DynamicMethod",
MethodAttributes.Public | MethodAttributes.Static,
typeof(Task),
new Type[] { typeof(Task) });
// Set `MethodImpl.Async` flag
methodBuilder.SetImplementationFlags(MethodImplAttributes.Async);
// {
// Await(arg_0);
// ret;
// }
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
#pragma warning disable SYSLIB5007 // 'System.Runtime.CompilerServices.AsyncHelpers' is for evaluation purposes only
var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!;
#pragma warning restore SYSLIB5007
ilGenerator.EmitCall(OpCodes.Call, mi, new Type[] { typeof(Task) });
ilGenerator.Emit(OpCodes.Ret);
// Create the type and invoke the method
Type dynamicType = typeBuilder.CreateType();
MethodInfo dynamicMethod = dynamicType.GetMethod("DynamicMethod");
var del = dynamicMethod.CreateDelegate<Func<Task, Task>>();
// the following should not crash
del(Task.CompletedTask);
del(FooTask());
}
public class PrivateAsync1<T>
{
public static int s;
private static async Task<T> a_task1(int i)
{
s++;
if (i == 0)
{
await Task.Yield();
return default;
}
return await Accessors2.accessor<T>(null, i - 1);
}
}
public class PrivateAsync2
{
public static int s;
private static async Task<T> a_task2<T>(int i)
{
s++;
if (i == 0)
{
await Task.Yield();
return default;
}
return await Accessors1<T>.accessor(null, i - 1);
}
}
public class Accessors1<T>
{
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "a_task1")]
public extern static Task<T> accessor(PrivateAsync1<T> o, int i);
}
public class Accessors2
{
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "a_task2")]
public extern static Task<T> accessor<T>(PrivateAsync2 o, int i);
}
[Fact]
public static void UnsafeAccessors()
{
Accessors2.accessor<int>(null, 7).GetAwaiter().GetResult();
Assert.Equal(4, PrivateAsync1<int>.s);
Assert.Equal(4, PrivateAsync2.s);
Accessors1<int>.accessor(null, 7).GetAwaiter().GetResult();
Assert.Equal(8, PrivateAsync1<int>.s);
Assert.Equal(8, PrivateAsync2.s);
}
}

View File

@ -1,5 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<CLRTestPriority>0</CLRTestPriority>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>