Skip to content

Add support for async thunk methods in GetNativeCodeInfo#124354

Open
tommcdon wants to merge 1 commit intodotnet:mainfrom
tommcdon:dev/tommcdon/fix_dac_getnativecodeinfo
Open

Add support for async thunk methods in GetNativeCodeInfo#124354
tommcdon wants to merge 1 commit intodotnet:mainfrom
tommcdon:dev/tommcdon/fix_dac_getnativecodeinfo

Conversation

@tommcdon
Copy link
Member

@tommcdon tommcdon commented Feb 12, 2026

The runtime by default caches the async thunk when looking up runtime async methods in the methoddef map:

// For {Task-returning, Async} variants of the same definition
// we associate the methoddef with the Task-returning variant since it
// matches the methadata signature.
if (!pMD->IsUnboxingStub() && !pMD->IsAsyncVariantMethod())
{
pModule->EnsuredStoreMethodDef(pMD->GetMemberDef(), pMD);
}

This causes DacDbiInterfaceImpl::GetNativeCodeInfo to incorrectly return data for the async thunk and not the actual method.

@tommcdon tommcdon added this to the 11.0.0 milestone Feb 12, 2026
@tommcdon tommcdon self-assigned this Feb 12, 2026
Copilot AI review requested due to automatic review settings February 12, 2026 21:01
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR attempts to fix an issue where DacDbiInterfaceImpl::GetNativeCodeInfo incorrectly returns data for async thunk methods instead of the actual async implementation method. The runtime caches async thunks in the methoddef map (see clsload.cpp:2804-2810), causing FindLoadedMethodRefOrDef to return the thunk. This PR adds logic to detect async thunk methods and attempts to find the corresponding async implementation method by iterating through all methods in the type.

Changes:

  • Added logic to detect async thunk methods using IsAsyncThunkMethod()
  • Implemented a loop to iterate through all introduced methods in the method table
  • Filters methods using IsDiagnosticsHidden() to find the actual async implementation method

Comment on lines +1192 to +1202
MethodTable * pMT = pMethodDesc->GetMethodTable();
MethodTable::IntroducedMethodIterator it(pMT);
for (; it.IsValid(); it.Next())
{
MethodDesc * pMD = it.GetMethodDesc();
CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT);
if (!pMD->IsDiagnosticsHidden())
{
pMethodDesc = pMD;
break;
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop that searches for a non-hidden method does not handle the case where no non-hidden method is found. If all methods in the type are diagnostics-hidden, the loop will exit without finding a match, and pMethodDesc will remain pointing to the async thunk method. This defeats the purpose of the fix.

Consider using GetAsyncOtherVariant() or GetAsyncVariantNoCreate() methods (defined in method.hpp) which directly return the async variant method associated with a thunk, rather than iterating through all methods. These methods are specifically designed for this purpose and are used elsewhere in the codebase (e.g., stubmgr.cpp:2293, method.cpp:3305, reflectioninvocation.cpp:1298).

Suggested change
MethodTable * pMT = pMethodDesc->GetMethodTable();
MethodTable::IntroducedMethodIterator it(pMT);
for (; it.IsValid(); it.Next())
{
MethodDesc * pMD = it.GetMethodDesc();
CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT);
if (!pMD->IsDiagnosticsHidden())
{
pMethodDesc = pMD;
break;
}
MethodDesc * pAsyncVariant = pMethodDesc->GetAsyncOtherVariant();
if (pAsyncVariant != NULL && !pAsyncVariant->IsDiagnosticsHidden())
{
pMethodDesc = pAsyncVariant;

Copilot uses AI. Check for mistakes.
{
MethodDesc * pMD = it.GetMethodDesc();
CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT);
if (!pMD->IsDiagnosticsHidden())
Copy link
Member

@max-charlamb max-charlamb Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want to make sure the mdToken is the same as functionToken?

As I understand this, it would get the first !IsDiagnosticsHidden() method on the methodtable, not necessarily the non thunk variant.

MethodDesc* pMethodDesc = FindLoadedMethodRefOrDef(pModule, functionToken);
if (pMethodDesc->IsAsyncThunkMethod())
{
MethodTable * pMT = pMethodDesc->GetMethodTable();
Copy link
Member

@noahfalk noahfalk Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work? All the code below seems like it is trying to emulate the behavior of GetAsyncVariant.

if (PMethodDesc != NULL && pMethodDesc->IsAsyncThunkMethod())
{
    pMethodDesc = pMethodDesc->GetAsyncVariantNoCreate()
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MethodDesc::GetAsyncOtherVariant calls down to the static MethodDesc::FindOrCreateAssociatedMethodDesc which is defined in genmeth.cpp which is not included in DAC builds.

Not sure if this would be safe to DAC-ize.

Copy link
Member

@noahfalk noahfalk Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should try to DAC-ize the portion of the code path that is needed for GetAsyncVariantNoCreate(). If that is problematic for some reason we could do a separate standalone implementation but for that impl to be correct it has to replicate the logic in FindOrCreateAssociatedMethodDesc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a draft which does this: #124529

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants