From 5436b3e8b6cb49e3ebcbe7b078ae4908d2f5c916 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:45:14 +0000 Subject: [PATCH 1/7] Initial plan From afa8061e9268cb802c121fd2fda86c9cd1cdf44c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 06:25:27 +0000 Subject: [PATCH 2/7] Allow IMallocSpy callbacks during LPSTR cleanup Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/68c1bb22-8f29-41a2-86b8-b039f3aa63bc Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com> --- src/coreclr/vm/olevariant.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 26fe57e76d8be7..25bff96dc5b151 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1610,7 +1610,7 @@ void OleVariant::ClearLPWSTRArray(void *oleArray, SIZE_T cElements, MethodTable { CONTRACTL { - NOTHROW; + THROWS; // CoTaskMemFree can invoke a managed IMallocSpy implementation. GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(oleArray)); @@ -1745,7 +1745,7 @@ void OleVariant::ClearLPSTRArray(void *oleArray, SIZE_T cElements, MethodTable * { CONTRACTL { - NOTHROW; + THROWS; // CoTaskMemFree can invoke a managed IMallocSpy implementation. GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(oleArray)); @@ -4176,4 +4176,3 @@ extern "C" void QCALLTYPE Variant_ConvertValueTypeToRecord(QCall::ObjectHandleOn END_QCALL; } #endif // FEATURE_COMINTEROP - From 9333674b4744c2daefaa676ad1ba78198553b3e7 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 6 Apr 2026 16:46:29 -0700 Subject: [PATCH 3/7] Refactor CheckParentComVisibility to remove unnecessary BOOL parameter and update related calls --- src/coreclr/vm/comcallablewrapper.cpp | 26 +++++++++++--------------- src/coreclr/vm/comcallablewrapper.h | 12 ++++++------ src/coreclr/vm/olevariant.cpp | 6 ++++-- src/coreclr/vm/stdinterfaces.cpp | 4 ++-- src/coreclr/vm/stubhelpers.cpp | 2 +- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index b0f1341a4632eb..c94dfe774853d3 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -310,6 +310,8 @@ extern "C" PCODE ComPreStubWorker(UMEntryThunkData* pEntryThunk) return pMarshalInfo->GetReturnStubForHResult(E_OUTOFMEMORY); } + BEGIN_CONTRACT_VIOLATION(ThrowsViolation); + INSTALL_MANAGED_EXCEPTION_DISPATCHER; INSTALL_UNWIND_AND_CONTINUE_HANDLER; @@ -335,6 +337,8 @@ extern "C" PCODE ComPreStubWorker(UMEntryThunkData* pEntryThunk) UNINSTALL_UNWIND_AND_CONTINUE_HANDLER; UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; + END_CONTRACT_VIOLATION; + return pStub; } @@ -2272,7 +2276,7 @@ static IUnknown * GetComIPFromCCW_ForIID_Worker( { // Make sure the all the base classes of the class this IClassX corresponds to // are visible to COM. - pIntfComMT->CheckParentComVisibility(FALSE); + pIntfComMT->CheckParentComVisibility(); // Giveout IClassX of this class because the IID matches one of the IClassX in the hierarchy // This assumes any IClassX implementation must be derived from base class IClassX's implementation @@ -2320,7 +2324,7 @@ static IUnknown *GetComIPFromCCW_ForIntfMT_Worker(ComCallWrapper *pWrap, MethodT { // Make sure the all the base classes of the class this IClassX corresponds to // are visible to COM. - pIntfComMT->CheckParentComVisibility(FALSE); + pIntfComMT->CheckParentComVisibility(); // Giveout IClassX IUnknown * pIntf = pWrap->GetIClassXIP(); @@ -2606,7 +2610,7 @@ IDispatch* ComCallWrapper::GetIDispatchIP() // Make sure we release the BasicIP we're about to get. SafeComHolder pBasic = GetBasicIP(); ComMethodTable* pCMT = ComMethodTable::ComMethodTableFromIP(pBasic); - pCMT->CheckParentComVisibility(TRUE); + pCMT->CheckParentComVisibility(); } // If the class implements IReflect then use the IDispatchEx implementation. @@ -3811,7 +3815,7 @@ BOOL ComCallWrapperTemplate::IsSafeTypeForMarshalling() // Checks to see if the parent of the current class interface is visible to COM. // Throws an InvalidOperationException if not. //-------------------------------------------------------------------------- -void ComCallWrapperTemplate::CheckParentComVisibility(BOOL fForIDispatch) +void ComCallWrapperTemplate::CheckParentComVisibility() { CONTRACTL { @@ -3821,9 +3825,8 @@ void ComCallWrapperTemplate::CheckParentComVisibility(BOOL fForIDispatch) } CONTRACTL_END; - // Throw an exception to report the error. - if (!CheckParentComVisibilityNoThrow(fForIDispatch)) + if (!CheckParentComVisibilityNoThrow()) { ComCallWrapperTemplate *invisParent = FindInvisibleParent(); _ASSERTE(invisParent != NULL); @@ -3836,16 +3839,9 @@ void ComCallWrapperTemplate::CheckParentComVisibility(BOOL fForIDispatch) } } -BOOL ComCallWrapperTemplate::CheckParentComVisibilityNoThrow(BOOL fForIDispatch) +BOOL ComCallWrapperTemplate::CheckParentComVisibilityNoThrow() { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - + WRAPPER_NO_CONTRACT; // If the parent is visible to COM then everything is ok. if (!HasInvisibleParent()) diff --git a/src/coreclr/vm/comcallablewrapper.h b/src/coreclr/vm/comcallablewrapper.h index 163ad53f53b279..ed00a8f70468f9 100644 --- a/src/coreclr/vm/comcallablewrapper.h +++ b/src/coreclr/vm/comcallablewrapper.h @@ -226,8 +226,8 @@ class ComCallWrapperTemplate ComMethodTable* GetBasicComMT(); ULONG GetNumInterfaces(); SLOT* GetVTableSlot(ULONG index); - void CheckParentComVisibility(BOOL fForIDispatch); - BOOL CheckParentComVisibilityNoThrow(BOOL fForIDispatch); + void CheckParentComVisibility(); + BOOL CheckParentComVisibilityNoThrow(); // Calls GetDefaultInterfaceForClassInternal and caches the result. DefaultInterfaceType GetDefaultInterface(MethodTable **ppDefaultItf); @@ -672,18 +672,18 @@ struct ComMethodTable } #endif // DACCESS_COMPILE - void CheckParentComVisibility(BOOL fForIDispatch) + void CheckParentComVisibility() { WRAPPER_NO_CONTRACT; - ((ComCallWrapperTemplate*)m_pMT->GetComCallWrapperTemplate())->CheckParentComVisibility(fForIDispatch); + ((ComCallWrapperTemplate*)m_pMT->GetComCallWrapperTemplate())->CheckParentComVisibility(); } - BOOL CheckParentComVisibilityNoThrow(BOOL fForIDispatch) + BOOL CheckParentComVisibilityNoThrow() { WRAPPER_NO_CONTRACT; - return ((ComCallWrapperTemplate*)m_pMT->GetComCallWrapperTemplate())->CheckParentComVisibilityNoThrow(fForIDispatch); + return ((ComCallWrapperTemplate*)m_pMT->GetComCallWrapperTemplate())->CheckParentComVisibilityNoThrow(); } private: diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 25bff96dc5b151..03b7fd67a2c23a 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1610,7 +1610,7 @@ void OleVariant::ClearLPWSTRArray(void *oleArray, SIZE_T cElements, MethodTable { CONTRACTL { - THROWS; // CoTaskMemFree can invoke a managed IMallocSpy implementation. + NOTHROW; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(oleArray)); @@ -1621,6 +1621,7 @@ void OleVariant::ClearLPWSTRArray(void *oleArray, SIZE_T cElements, MethodTable LPWSTR *pOle = (LPWSTR *) oleArray; LPWSTR *pOleEnd = pOle + cElements; + PERMANENT_CONTRACT_VIOLATION(ThrowsViolation, ReasonRuntimeReentrancy); // IMallocSpy in managed while (pOle < pOleEnd) { LPWSTR lpwstr = *pOle++; @@ -1745,7 +1746,7 @@ void OleVariant::ClearLPSTRArray(void *oleArray, SIZE_T cElements, MethodTable * { CONTRACTL { - THROWS; // CoTaskMemFree can invoke a managed IMallocSpy implementation. + NOTHROW; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(oleArray)); @@ -1756,6 +1757,7 @@ void OleVariant::ClearLPSTRArray(void *oleArray, SIZE_T cElements, MethodTable * LPSTR *pOle = (LPSTR *) oleArray; LPSTR *pOleEnd = pOle + cElements; + PERMANENT_CONTRACT_VIOLATION(ThrowsViolation, ReasonRuntimeReentrancy); // IMallocSpy in managed while (pOle < pOleEnd) { LPSTR lpstr = *pOle++; diff --git a/src/coreclr/vm/stdinterfaces.cpp b/src/coreclr/vm/stdinterfaces.cpp index 58728593fd1ea9..eacbf9235312d2 100644 --- a/src/coreclr/vm/stdinterfaces.cpp +++ b/src/coreclr/vm/stdinterfaces.cpp @@ -1348,7 +1348,7 @@ InternalDispatchImpl_GetIDsOfNames ( ComMethodTable* pCMT = ComMethodTable::ComMethodTableFromIP(pDisp); if (pCMT->IsIClassXOrBasicItf() && pCMT->GetClassInterfaceType() != clsIfNone) - pCMT->CheckParentComVisibility(FALSE); + pCMT->CheckParentComVisibility(); pSimpleWrap = pCCW->GetSimpleWrapper(); pDispInfo = ComMethodTable::ComMethodTableFromIP(pDisp)->GetDispatchInfo(); @@ -1420,7 +1420,7 @@ InternalDispatchImpl_Invoke ComMethodTable* pCMT = ComMethodTable::ComMethodTableFromIP(pDisp); if (pCMT->IsIClassXOrBasicItf() && pCMT->GetClassInterfaceType() != clsIfNone) - pCMT->CheckParentComVisibility(FALSE); + pCMT->CheckParentComVisibility(); pSimpleWrap = pCCW->GetSimpleWrapper(); diff --git a/src/coreclr/vm/stubhelpers.cpp b/src/coreclr/vm/stubhelpers.cpp index 36dd859b5c7a84..0b24871017db0f 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -471,7 +471,7 @@ extern "C" void QCALLTYPE InterfaceMarshaler_ValidateComVisibilityForIUnknown(IU if (pComMT->IsIClassX()) { - pComMT->CheckParentComVisibility(FALSE); + pComMT->CheckParentComVisibility(); } END_QCALL; From e4b74edd33e3c46c6f65480afe4da857cfd18513 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:05:11 +0000 Subject: [PATCH 4/7] Make ComPreStubWorker outer contract no-throw Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/dda7a23f-910f-4f7f-b3f8-4b21b06d6a14 Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- src/coreclr/vm/comcallablewrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index c94dfe774853d3..24fd5eb4f994d2 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -296,7 +296,7 @@ extern PLATFORM_THREAD_LOCAL UMEntryThunkData * t_MostRecentUMEntryThunkData; //-------------------------------------------------------------------------- extern "C" PCODE ComPreStubWorker(UMEntryThunkData* pEntryThunk) { - STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_TRIGGERS; STATIC_CONTRACT_MODE_ANY; From 44c13dba21d543e3561f386fef24a4dd9d368c7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:26:47 +0000 Subject: [PATCH 5/7] Remove redundant CheckParentComVisibilityNoThrow helper Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/76569a90-7ab4-4a60-ab3d-c1795c141dd9 Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com> --- src/coreclr/vm/comcallablewrapper.cpp | 13 +------------ src/coreclr/vm/comcallablewrapper.h | 8 -------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index 24fd5eb4f994d2..f2a3e1d2bf68ca 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -3826,7 +3826,7 @@ void ComCallWrapperTemplate::CheckParentComVisibility() CONTRACTL_END; // Throw an exception to report the error. - if (!CheckParentComVisibilityNoThrow()) + if (HasInvisibleParent()) { ComCallWrapperTemplate *invisParent = FindInvisibleParent(); _ASSERTE(invisParent != NULL); @@ -3839,17 +3839,6 @@ void ComCallWrapperTemplate::CheckParentComVisibility() } } -BOOL ComCallWrapperTemplate::CheckParentComVisibilityNoThrow() -{ - WRAPPER_NO_CONTRACT; - - // If the parent is visible to COM then everything is ok. - if (!HasInvisibleParent()) - return TRUE; - - return FALSE; -} - DefaultInterfaceType ComCallWrapperTemplate::GetDefaultInterface(MethodTable **ppDefaultItf) { CONTRACTL diff --git a/src/coreclr/vm/comcallablewrapper.h b/src/coreclr/vm/comcallablewrapper.h index ed00a8f70468f9..d32492086bb837 100644 --- a/src/coreclr/vm/comcallablewrapper.h +++ b/src/coreclr/vm/comcallablewrapper.h @@ -227,7 +227,6 @@ class ComCallWrapperTemplate ULONG GetNumInterfaces(); SLOT* GetVTableSlot(ULONG index); void CheckParentComVisibility(); - BOOL CheckParentComVisibilityNoThrow(); // Calls GetDefaultInterfaceForClassInternal and caches the result. DefaultInterfaceType GetDefaultInterface(MethodTable **ppDefaultItf); @@ -679,13 +678,6 @@ struct ComMethodTable ((ComCallWrapperTemplate*)m_pMT->GetComCallWrapperTemplate())->CheckParentComVisibility(); } - BOOL CheckParentComVisibilityNoThrow() - { - WRAPPER_NO_CONTRACT; - - return ((ComCallWrapperTemplate*)m_pMT->GetComCallWrapperTemplate())->CheckParentComVisibilityNoThrow(); - } - private: SLOT m_ptReserved; //= (SLOT) 0xDEADC0FF; reserved PTR_MethodTable m_pMT; // pointer to the VMs method table From 8fb499e10b92f95b17d849cbbed33b59c0931428 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 6 Apr 2026 21:52:45 -0700 Subject: [PATCH 6/7] Apply suggestion from @AaronRobinsonMSFT --- src/coreclr/vm/comcallablewrapper.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index f2a3e1d2bf68ca..ec1a5909ccd24b 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -310,6 +310,9 @@ extern "C" PCODE ComPreStubWorker(UMEntryThunkData* pEntryThunk) return pMarshalInfo->GetReturnStubForHResult(E_OUTOFMEMORY); } + // The below "INSTALL_" macros ensure exceptions don't escape, + // but the macros do not update the contract state for the thread, so + // we manually indicate that here. BEGIN_CONTRACT_VIOLATION(ThrowsViolation); INSTALL_MANAGED_EXCEPTION_DISPATCHER; From e782db037101731b401900fa52245a94e5afb36d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:42:16 +0000 Subject: [PATCH 7/7] Re-enable the ExtensionPoints IMallocSpy test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6e3f0d63-765e-4f6a-bdda-7ebfdd27e4ec Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com> --- src/tests/Interop/COM/ExtensionPoints/ExtensionPoints.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/Interop/COM/ExtensionPoints/ExtensionPoints.cs b/src/tests/Interop/COM/ExtensionPoints/ExtensionPoints.cs index 0aa4edc382b4e1..33f27f520d342a 100644 --- a/src/tests/Interop/COM/ExtensionPoints/ExtensionPoints.cs +++ b/src/tests/Interop/COM/ExtensionPoints/ExtensionPoints.cs @@ -42,7 +42,6 @@ public virtual void PostHeapMinimize() { } [Fact] [Xunit.SkipOnCoreClrAttribute("Depends on marshalled calli", RuntimeTestModes.InterpreterActive)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/126567")] public static unsafe void Validate_Managed_IMallocSpy() { Console.WriteLine($"Running {nameof(Validate_Managed_IMallocSpy)}...");