Skip to content

Commit 55728d4

Browse files
authored
Exclude runtime-async methods from built-in COM vtables and GUID calculations (#122195)
1 parent faff920 commit 55728d4

19 files changed

+473
-60
lines changed

src/coreclr/vm/asyncthunks.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void MethodDesc::EmitTaskReturningThunk(MethodDesc* pAsyncCallVariant, MetaSig&
6565
_ASSERTE(!pAsyncCallVariant->IsAsyncThunkMethod());
6666

6767
// Emits roughly the following code:
68-
//
68+
//
6969
// ExecutionAndSyncBlockStore store = default;
7070
// store.Push();
7171
// try
@@ -569,7 +569,15 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pTaskReturningVariant, MetaSig
569569
}
570570

571571
// other(arg)
572-
pCode->EmitCALL(userFuncToken, localArg, 1);
572+
if (pTaskReturningVariant->IsAbstract())
573+
{
574+
_ASSERTE(pTaskReturningVariant->IsCLRToCOMCall());
575+
pCode->EmitCALLVIRT(userFuncToken, localArg, 1);
576+
}
577+
else
578+
{
579+
pCode->EmitCALL(userFuncToken, localArg, 1);
580+
}
573581

574582
TypeHandle thLogicalRetType = msig.GetRetTypeHandleThrowing();
575583
if (IsValueTaskAsyncThunk())

src/coreclr/vm/class.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2828,13 +2828,15 @@ void SparseVTableMap::AllocOrExpand()
28282828
}
28292829

28302830
//*******************************************************************************
2831-
// While building mapping list, record a gap in VTable slot numbers.
2831+
// While building mapping list, record a gap in VTable slot numbers or MT slots.
2832+
// A positive number indicates a gap in the VTable slot numbers.
2833+
// A negative number indicates a gap in the MT slots.
28322834
void SparseVTableMap::RecordGap(WORD StartMTSlot, WORD NumSkipSlots)
28332835
{
28342836
STANDARD_VM_CONTRACT;
28352837

28362838
_ASSERTE((StartMTSlot == 0) || (StartMTSlot > m_MTSlot));
2837-
_ASSERTE(NumSkipSlots > 0);
2839+
_ASSERTE(NumSkipSlots != 0);
28382840

28392841
// We use the information about the current gap to complete a map entry for
28402842
// the last non-gap. There is a special case where the vtable begins with a
@@ -2860,6 +2862,14 @@ void SparseVTableMap::RecordGap(WORD StartMTSlot, WORD NumSkipSlots)
28602862
m_MapEntries++;
28612863
}
28622864

2865+
//*******************************************************************************
2866+
// While building mapping list, record an excluded MT slot.
2867+
void SparseVTableMap::RecordExcludedMethod(WORD MTSlot)
2868+
{
2869+
WRAPPER_NO_CONTRACT;
2870+
return RecordGap(MTSlot, -1);
2871+
}
2872+
28632873
//*******************************************************************************
28642874
// Finish creation of mapping list.
28652875
void SparseVTableMap::FinalizeMapping(WORD TotalMTSlots)

src/coreclr/vm/class.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ class SparseVTableMap
289289
// occurs.
290290
void RecordGap(WORD StartMTSlot, WORD NumSkipSlots);
291291

292+
// Record that the method table slot at MTSlot is excluded from the VT slots.
293+
void RecordExcludedMethod(WORD MTSlot);
294+
292295
// Then call FinalizeMapping to create the actual mapping list.
293296
void FinalizeMapping(WORD TotalMTSlots);
294297

src/coreclr/vm/clrtocomcall.cpp

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ I4ARRAYREF SetUpWrapperInfo(MethodDesc *pMD)
212212
MODE_COOPERATIVE;
213213
INJECT_FAULT(COMPlusThrowOM());
214214
PRECONDITION(CheckPointer(pMD));
215+
PRECONDITION(!pMD->IsAsyncMethod());
215216
}
216217
CONTRACTL_END;
217218

@@ -229,13 +230,6 @@ I4ARRAYREF SetUpWrapperInfo(MethodDesc *pMD)
229230
WrapperTypeArr = (I4ARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_I4, numArgs);
230231

231232
GCX_PREEMP();
232-
233-
234-
// TODO: (async) revisit and examine if this needs to be supported somehow
235-
if (pMD->IsAsyncMethod())
236-
{
237-
ThrowHR(COR_E_NOTSUPPORTED);
238-
}
239233

240234
// Collects ParamDef information in an indexed array where element 0 represents
241235
// the return type.
@@ -511,11 +505,9 @@ UINT32 CLRToCOMLateBoundWorker(
511505
LPCUTF8 strMemberName;
512506
ULONG uSemantic;
513507

514-
// TODO: (async) revisit and examine if this needs to be supported somehow
515-
if (pItfMD->IsAsyncMethod())
516-
{
517-
ThrowHR(COR_E_NOTSUPPORTED);
518-
}
508+
// We should never see an async method here, as the async variant should go down
509+
// the async stub path and call the non-async variant (which ends up here).
510+
_ASSERTE(!pItfMD->IsAsyncMethod());
519511

520512
// See if there is property information for this member.
521513
hr = pItfMT->GetMDImport()->GetPropertyInfoForMethodDef(pItfMD->GetMemberDef(), &propToken, &strMemberName, &uSemantic);

src/coreclr/vm/comcallablewrapper.cpp

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3313,9 +3313,10 @@ void ComMethodTable::LayOutClassMethodTable()
33133313
pCurrParentInteropMD = &pCurrParentInteropMT->pVTable[i];
33143314
pParentMD = pCurrParentInteropMD->pMD;
33153315

3316-
if (pMD &&
3317-
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
3318-
IsOverloadedComVisibleMember(pMD, pParentMD))
3316+
if (pMD
3317+
&& !(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i))
3318+
&& IsOverloadedComVisibleMember(pMD, pParentMD)
3319+
&& !pMD->IsAsyncMethod())
33193320
{
33203321
// some bytes are reserved for CALL xxx before the method desc
33213322
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
@@ -3346,9 +3347,10 @@ void ComMethodTable::LayOutClassMethodTable()
33463347
pCurrInteropMD = &pCurrInteropMT->pVTable[i];
33473348
pMD = pCurrInteropMD->pMD;
33483349

3349-
if (pMD &&
3350-
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
3351-
IsNewComVisibleMember(pMD))
3350+
if (pMD
3351+
&& !(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i))
3352+
&& IsNewComVisibleMember(pMD)
3353+
&& !pMD->IsAsyncMethod())
33523354
{
33533355
// some bytes are reserved for CALL xxx before the method desc
33543356
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
@@ -3376,8 +3378,12 @@ void ComMethodTable::LayOutClassMethodTable()
33763378
if (!it.IsVirtual()) {
33773379
MethodDesc* pMD = it.GetMethodDesc();
33783380

3379-
if (pMD != NULL && !IsDuplicateClassItfMD(pMD, it.GetSlotNumber()) &&
3380-
IsNewComVisibleMember(pMD) && !pMD->IsStatic() && !pMD->IsCtor()
3381+
if (pMD != NULL
3382+
&& !IsDuplicateClassItfMD(pMD, it.GetSlotNumber())
3383+
&& IsNewComVisibleMember(pMD)
3384+
&& !pMD->IsStatic()
3385+
&& !pMD->IsCtor()
3386+
&& !pMD->IsAsyncMethod()
33813387
&& (!pCurrMT->IsValueType() || (GetClassInterfaceType() != clsIfAutoDual && IsStrictlyUnboxed(pMD))))
33823388
{
33833389
// some bytes are reserved for CALL xxx before the method desc
@@ -3558,6 +3564,8 @@ BOOL ComMethodTable::LayOutInterfaceMethodTable(MethodTable* pClsMT)
35583564
ArrayList NewCOMMethodDescs;
35593565
ComCallMethodDescArrayHolder NewCOMMethodDescsHolder(&NewCOMMethodDescs);
35603566

3567+
unsigned numVtableSlots = 0;
3568+
35613569
for (i = 0; i < cbSlots; i++)
35623570
{
35633571
// Some space for a CALL xx xx xx xx stub is reserved before the beginning of the MethodDesc
@@ -3566,6 +3574,15 @@ BOOL ComMethodTable::LayOutInterfaceMethodTable(MethodTable* pClsMT)
35663574

35673575
MethodDesc* pIntfMD = m_pMT->GetMethodDescForSlot(i);
35683576

3577+
if (pIntfMD->IsAsyncMethod())
3578+
{
3579+
// Async methods are not supported on COM interfaces
3580+
// And we don't include them in the calculation of COM vtable slots.
3581+
continue;
3582+
}
3583+
3584+
numVtableSlots++;
3585+
35693586
if (m_pMT->HasInstantiation())
35703587
{
35713588
pIntfMD = MethodDesc::FindOrCreateAssociatedMethodDesc(
@@ -3616,23 +3633,36 @@ BOOL ComMethodTable::LayOutInterfaceMethodTable(MethodTable* pClsMT)
36163633
SLOT *pComVtableRW = (SLOT*)((BYTE*)pComVtable + writeableOffset);
36173634

36183635
// Method descs are at the end of the vtable
3619-
// m_cbSlots interfaces methods + IUnk methods
3636+
// numVtableSlots interfaces methods + IUnk methods
3637+
unsigned cbEmittedSlots = 0;
36203638
pMethodDescMemory = (BYTE *)&pComVtable[m_cbSlots];
3639+
_ASSERTE(numVtableSlots <= m_cbSlots);
36213640
for (i = 0; i < cbSlots; i++)
36223641
{
36233642
ComCallMethodDesc* pNewMD = (ComCallMethodDesc *) (pMethodDescMemory + COMMETHOD_PREPAD);
36243643
ComCallMethodDesc* pNewMDRW = (ComCallMethodDesc *) (pMethodDescMemory + writeableOffset + COMMETHOD_PREPAD);
36253644

36263645
MethodDesc* pIntfMD = m_pMT->GetMethodDescForSlot(i);
36273646

3647+
if (pIntfMD->IsAsyncMethod())
3648+
{
3649+
// Async methods are not supported on COM interfaces
3650+
// We skip them above in the vtable calculation
3651+
// so don't fill in the COM vtable slot here.
3652+
continue;
3653+
}
3654+
36283655
emitCOMStubCall(pNewMD, pNewMDRW, GetEEFuncEntryPoint(ComCallPreStub));
36293656

36303657
UINT slotIndex = (pIntfMD->GetComSlot() - cbExtraSlots);
36313658
FillInComVtableSlot(pComVtableRW, slotIndex, pNewMD);
36323659

36333660
pMethodDescMemory += (COMMETHOD_PREPAD + sizeof(ComCallMethodDesc));
3661+
cbEmittedSlots++;
36343662
}
36353663

3664+
_ASSERTE(numVtableSlots == cbEmittedSlots);
3665+
36363666
// Set the layout complete flag and release the lock.
36373667
comMTWriterHolder.GetRW()->m_Flags |= enum_LayoutComplete;
36383668
NewCOMMethodDescsHolder.SuppressRelease();
@@ -4248,9 +4278,10 @@ ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForClass(MethodTable
42484278
pCurrParentInteropMD = &pCurrParentInteropMT->pVTable[i];
42494279
pParentMD = pCurrParentInteropMD->pMD;
42504280

4251-
if (pMD &&
4252-
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
4253-
IsOverloadedComVisibleMember(pMD, pParentMD))
4281+
if (pMD
4282+
&& !(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i))
4283+
&& IsOverloadedComVisibleMember(pMD, pParentMD)
4284+
&& !pMD->IsAsyncMethod())
42544285
{
42554286
cbNewPublicMethods++;
42564287
}
@@ -4267,9 +4298,10 @@ ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForClass(MethodTable
42674298
pCurrInteropMD = &pCurrInteropMT->pVTable[i];
42684299
pMD = pCurrInteropMD->pMD;
42694300

4270-
if (pMD &&
4271-
!(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i)) &&
4272-
IsNewComVisibleMember(pMD))
4301+
if (pMD
4302+
&& !(pCurrInteropMD ? IsDuplicateClassItfMD(pCurrInteropMD, i) : IsDuplicateClassItfMD(pMD, i))
4303+
&& IsNewComVisibleMember(pMD)
4304+
&& !pMD->IsAsyncMethod())
42734305
{
42744306
cbNewPublicMethods++;
42754307
}
@@ -4282,9 +4314,13 @@ ComMethodTable* ComCallWrapperTemplate::CreateComMethodTableForClass(MethodTable
42824314
if (!it.IsVirtual())
42834315
{
42844316
MethodDesc* pMD = it.GetMethodDesc();
4285-
if (pMD && !IsDuplicateClassItfMD(pMD, it.GetSlotNumber()) && IsNewComVisibleMember(pMD) &&
4286-
!pMD->IsStatic() && !pMD->IsCtor() &&
4287-
(!pCurrMT->IsValueType() || (ClassItfType != clsIfAutoDual && IsStrictlyUnboxed(pMD))))
4317+
if (pMD
4318+
&& !IsDuplicateClassItfMD(pMD, it.GetSlotNumber())
4319+
&& IsNewComVisibleMember(pMD)
4320+
&& !pMD->IsStatic()
4321+
&& !pMD->IsCtor()
4322+
&& !pMD->IsAsyncMethod()
4323+
&& (!pCurrMT->IsValueType() || (ClassItfType != clsIfAutoDual && IsStrictlyUnboxed(pMD))))
42884324
{
42894325
cbNewPublicMethods++;
42904326
}

src/coreclr/vm/commtmemberinfomap.cpp

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ void ComMTMemberInfoMap::SetupPropsForIClassX(size_t sizeOfPtr)
361361
// Retrieve the method desc on the current class. This involves looking up the method
362362
// desc in the vtable if it is a virtual method.
363363
pMeth = pCMT->GetMethodDescForSlot(i);
364+
_ASSERTE(!pMeth->IsAsyncMethod());
364365
if (pMeth->IsVirtual())
365366
{
366367
WORD wSlot = InteropMethodTableData::GetSlotForMethodDesc(m_pMT, pMeth);
@@ -541,6 +542,15 @@ void ComMTMemberInfoMap::SetupPropsForInterface(size_t sizeOfPtr)
541542
{
542543
MethodDesc* pMD = m_pMT->GetMethodDescForSlot(iMD);
543544
_ASSERTE(pMD != NULL);
545+
546+
if (pMD->IsAsyncMethod())
547+
{
548+
// Async methods introduce mismatches in the .NET and COM vtables.
549+
// We will need to remap slots.
550+
bSlotRemap = true;
551+
continue;
552+
}
553+
544554
ULONG tmp = pMD->GetComSlot();
545555

546556
if (tmp < ulComSlotMin)
@@ -552,10 +562,13 @@ void ComMTMemberInfoMap::SetupPropsForInterface(size_t sizeOfPtr)
552562
// Used a couple of times.
553563
MethodTable::MethodIterator it(m_pMT);
554564

555-
if (ulComSlotMax-ulComSlotMin >= nSlots)
565+
if (ulComSlotMax - ulComSlotMin >= nSlots)
556566
{
557567
bSlotRemap = true;
568+
}
558569

570+
if (bSlotRemap)
571+
{
559572
// Resize the array.
560573
rSlotMap.ReSizeThrows(ulComSlotMax+1);
561574

@@ -566,7 +579,7 @@ void ComMTMemberInfoMap::SetupPropsForInterface(size_t sizeOfPtr)
566579
it.MoveToBegin();
567580
for (; it.IsValid(); it.Next())
568581
{
569-
if (it.IsVirtual())
582+
if (it.IsVirtual() && !it.GetMethodDesc()->IsAsyncMethod())
570583
{
571584
MethodDesc* pMD = it.GetMethodDesc();
572585
_ASSERTE(pMD != NULL);
@@ -590,7 +603,7 @@ void ComMTMemberInfoMap::SetupPropsForInterface(size_t sizeOfPtr)
590603
if (it.IsVirtual())
591604
{
592605
pMeth = it.GetMethodDesc();
593-
if (pMeth != NULL)
606+
if (pMeth != NULL && !pMeth->IsAsyncMethod())
594607
{
595608
ULONG ixSlot = pMeth->GetComSlot();
596609
if (bSlotRemap)
@@ -607,6 +620,22 @@ void ComMTMemberInfoMap::SetupPropsForInterface(size_t sizeOfPtr)
607620
for (iMD=0; iMD < nSlots; ++iMD)
608621
{
609622
pMeth = m_MethodProps[iMD].pMeth;
623+
if (pMeth == nullptr)
624+
{
625+
// For async methods, we skip .NET methods when building the COM vtable.
626+
// So at some point we hit the end of the .NET methods before we run
627+
// through all possible vtable slots.
628+
// Record when we ran out here.
629+
#ifdef _DEBUG
630+
// In debug, validate that all remaining slots are null.
631+
for (unsigned j = iMD; j < nSlots; ++j)
632+
{
633+
_ASSERTE(m_MethodProps[j].pMeth == nullptr);
634+
}
635+
#endif
636+
nSlots = iMD;
637+
break;
638+
}
610639
GetMethodPropsForMeth(pMeth, iMD, m_MethodProps, m_sNames);
611640
}
612641

@@ -688,9 +717,7 @@ void ComMTMemberInfoMap::GetMethodPropsForMeth(
688717
// Generally don't munge function into a getter.
689718
rProps[ix].bFunction2Getter = FALSE;
690719

691-
// TODO: (async) revisit and examine if this needs to be supported somehow
692-
if (pMeth->IsAsyncMethod())
693-
ThrowHR(COR_E_NOTSUPPORTED);
720+
_ASSERTE(!pMeth->IsAsyncMethod());
694721

695722
// See if there is property information for this member.
696723
hr = pMeth->GetMDImport()->GetPropertyInfoForMethodDef(pMeth->GetMemberDef(), &pd, &pPropName, &uSemantic);
@@ -1608,11 +1635,7 @@ void ComMTMemberInfoMap::PopulateMemberHashtable()
16081635

16091636
// We are dealing with a method.
16101637
MethodDesc *pMD = pProps->pMeth;
1611-
// TODO: (async) revisit and examine if this needs to be supported somehow
1612-
if (pMD->IsAsyncMethod())
1613-
{
1614-
ThrowHR(COR_E_NOTSUPPORTED); // Probably this isn't right, and instead should be a skip, but a throw makes it easier to find if this is wrong
1615-
}
1638+
_ASSERTE(!pMD->IsAsyncMethod());
16161639
EEModuleTokenPair Key(pMD->GetMemberDef(), pMD->GetModule());
16171640
m_TokenToComMTMethodPropsMap.InsertValue(&Key, (HashDatum)pProps);
16181641
}

src/coreclr/vm/comtoclrcall.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ OBJECTREF COMToCLRGetObjectAndTarget_NonVirtual(ComCallWrapper * pWrap, MethodDe
263263
}
264264
CONTRACTL_END;
265265

266+
CONTRACT_VIOLATION(ThrowsViolation);
267+
266268
//NOTE: No need to optimize for stub dispatch since non-virtuals are retrieved quickly.
267269
*ppManagedTargetOut = pRealMD->GetSingleCallableAddrOfCode();
268270

@@ -850,6 +852,7 @@ void ComCallMethodDesc::InitMethod(MethodDesc *pMD, MethodDesc *pInterfaceMD)
850852
GC_TRIGGERS;
851853
MODE_ANY;
852854
PRECONDITION(CheckPointer(pMD));
855+
PRECONDITION(!pMD->IsAsyncMethod());
853856
}
854857
CONTRACTL_END;
855858

@@ -974,6 +977,7 @@ void ComCallMethodDesc::InitNativeInfo()
974977
else
975978
{
976979
MethodDesc *pMD = GetCallMethodDesc();
980+
_ASSERTE(!pMD->IsAsyncMethod()); // Async methods should never have a ComCallMethodDesc.
977981

978982
#ifdef _DEBUG
979983
LPCUTF8 szDebugName = pMD->m_pszDebugMethodName;
@@ -985,9 +989,6 @@ void ComCallMethodDesc::InitNativeInfo()
985989

986990
MethodTable * pMT = pMD->GetMethodTable();
987991
IMDInternalImport * pInternalImport = pMT->GetMDImport();
988-
// TODO: (async) revisit and examine if this needs to be supported somehow
989-
if (pMD->IsAsyncMethod())
990-
ThrowHR(COR_E_NOTSUPPORTED);
991992

992993
mdMethodDef md = pMD->GetMemberDef();
993994

0 commit comments

Comments
 (0)