From 40da3cc80df9658d6fc8940ad7aba1746095d541 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 23:36:39 +0000 Subject: [PATCH 1/2] Convert GetRcwCachedInterfacePointers to callback-based enumerator Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 33 +++++----- src/coreclr/debug/daccess/dacdbiimpl.h | 2 +- src/coreclr/debug/di/divalue.cpp | 42 ++++++++---- src/coreclr/debug/di/process.cpp | 28 ++------ src/coreclr/debug/di/rspriv.h | 39 +++++++++++ src/coreclr/debug/inc/dacdbiinterface.h | 11 ++-- src/coreclr/inc/dacdbi.idl | 3 +- src/coreclr/vm/runtimecallablewrapper.h | 21 ------ .../Dbi/DacDbiImpl.cs | 64 ++++++++++++++++++- .../Dbi/IDacDbiInterface.cs | 2 +- 10 files changed, 159 insertions(+), 86 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index eee1d1586f5de8..e1d25b6c95f01e 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3616,42 +3616,39 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::IsRcw(VMPTR_Object vmObject, OUT #endif // FEATURE_COMINTEROP } -HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetRcwCachedInterfacePointers(VMPTR_Object vmObject, BOOL bIInspectableOnly, OUT DacDbiArrayList * pDacItfPtrs) +HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::EnumerateRcwCachedInterfacePointers(VMPTR_Object vmObject, FP_RCW_INTERFACE_CALLBACK fpCallback, CALLBACK_DATA pUserData) { #ifdef FEATURE_COMINTEROP DD_ENTER_MAY_THROW; + if (fpCallback == NULL) + return E_POINTER; + HRESULT hr = S_OK; EX_TRY { - - Object* objPtr = vmObject.GetDacPtr(); - - InlineSArray rgUnks; - PTR_RCW pRCW = GetRcwFromVmptrObject(vmObject); if (pRCW != NULL) { - pRCW->GetCachedInterfacePointers(bIInspectableOnly, &rgUnks); - - pDacItfPtrs->Alloc(rgUnks.GetCount()); - - for (COUNT_T i = 0; i < rgUnks.GetCount(); ++i) + RCW::CachedInterfaceEntryIterator it = pRCW->IterateCachedInterfacePointers(); + while (it.Next()) { - (*pDacItfPtrs)[i] = (CORDB_ADDRESS)(rgUnks[i]); + PTR_MethodTable pMT = dac_cast((TADDR)(it.GetEntry()->m_pMT.Load())); + if (pMT != NULL) + { + TADDR taUnk = (TADDR)(it.GetEntry()->m_pUnknown.Load()); + if (taUnk != NULL) + { + fpCallback((CORDB_ADDRESS)taUnk, pUserData); + } + } } - - } - else - { - pDacItfPtrs->Alloc(0); } } EX_CATCH_HRESULT(hr); return hr; #else - pDacItfPtrs->Alloc(0); return S_OK; #endif // FEATURE_COMINTEROP } diff --git a/src/coreclr/debug/daccess/dacdbiimpl.h b/src/coreclr/debug/daccess/dacdbiimpl.h index da74f528abd8fc..56a7cde8d8756d 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.h +++ b/src/coreclr/debug/daccess/dacdbiimpl.h @@ -317,7 +317,7 @@ class DacDbiInterfaceImpl : // retrieves the list of interfaces pointers implemented by vmObject, as it is known at // the time of the call (the list may change as new interface types become available // in the runtime) - HRESULT STDMETHODCALLTYPE GetRcwCachedInterfacePointers(VMPTR_Object vmObject, BOOL bIInspectableOnly, OUT DacDbiArrayList * pDacItfPtrs); + HRESULT STDMETHODCALLTYPE EnumerateRcwCachedInterfacePointers(VMPTR_Object vmObject, FP_RCW_INTERFACE_CALLBACK fpCallback, CALLBACK_DATA pUserData); private: // Given a pointer to a managed function, obtain the method desc for it. diff --git a/src/coreclr/debug/di/divalue.cpp b/src/coreclr/debug/di/divalue.cpp index 709b33281d8736..44b644024ce9f3 100644 --- a/src/coreclr/debug/di/divalue.cpp +++ b/src/coreclr/debug/di/divalue.cpp @@ -2802,6 +2802,16 @@ HRESULT CordbObjectValue::GetCachedInterfaceTypes( #endif } +#if defined(FEATURE_COMINTEROP) +namespace +{ + void RcwInterfacePointerCallback(CORDB_ADDRESS itfPtr, CALLBACK_DATA pUserData) + { + CallbackAccumulator::From(pUserData)->Push(itfPtr); + } +} +#endif // FEATURE_COMINTEROP + HRESULT CordbObjectValue::GetCachedInterfacePointers( BOOL bIInspectableOnly, ULONG32 celt, @@ -2825,25 +2835,29 @@ HRESULT CordbObjectValue::GetCachedInterfacePointers( HRESULT hr = S_OK; ULONG32 cItfs = 0; - // retrieve interface types - - CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); + CallbackAccumulator acc; - DacDbiArrayList dacItfPtrs; - EX_TRY + // The RCW interface pointer cache only holds non-IInspectable interfaces, so when the caller + // asks for IInspectable-only pointers the result is always empty. + if (!bIInspectableOnly) { - IDacDbiInterface* pDAC = GetProcess()->GetDAC(); - VMPTR_Object vmObj; - IfFailThrow(pDAC->GetObject(objAddr, &vmObj)); + CORDB_ADDRESS objAddr = m_valueHome.GetAddress(); - // retrieve type info from LS - IfFailThrow(pDAC->GetRcwCachedInterfacePointers(vmObj, bIInspectableOnly, &dacItfPtrs)); + EX_TRY + { + IDacDbiInterface* pDAC = GetProcess()->GetDAC(); + VMPTR_Object vmObj; + IfFailThrow(pDAC->GetObject(objAddr, &vmObj)); + + IfFailThrow(pDAC->EnumerateRcwCachedInterfacePointers(vmObj, &RcwInterfacePointerCallback, &acc)); + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + IfFailRet(acc.hrError); } - EX_CATCH_HRESULT(hr); - IfFailRet(hr); // synthesize CordbType instances - cItfs = (ULONG32)dacItfPtrs.Count(); + cItfs = (ULONG32)acc.items.Size(); if (pcEltFetched != NULL && ptrs == NULL) { @@ -2859,7 +2873,7 @@ HRESULT CordbObjectValue::GetCachedInterfacePointers( if (ptrs != NULL && *pcEltFetched > 0) { for (ULONG32 i = 0; i < *pcEltFetched; ++i) - ptrs[i] = dacItfPtrs[i]; + ptrs[i] = acc.items[i]; } return (*pcEltFetched == celt ? S_OK : S_FALSE); diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index 5f6dfdeb36b11f..82a21bbd75a891 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -2190,12 +2190,6 @@ HRESULT CordbProcess::GetGCHeapInformation(COR_HEAPINFO *pHeapInfo) namespace { - struct HeapSegmentAccumulator - { - CQuickArrayList segments; - HRESULT hrError; - }; - void HeapSegmentCallback( CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, @@ -2203,25 +2197,12 @@ namespace ULONG heap, CALLBACK_DATA pUserData) { - HeapSegmentAccumulator *acc = - reinterpret_cast(pUserData); - - if (FAILED(acc->hrError)) - return; - COR_SEGMENT s; s.start = rangeStart; s.end = rangeEnd; s.type = (CorDebugGenerationTypes)generation; s.heap = heap; - HRESULT hr = S_OK; - EX_TRY - { - acc->segments.Push(s); - } - EX_CATCH_HRESULT(hr); - if (FAILED(hr)) - acc->hrError = hr; + CallbackAccumulator::From(pUserData)->Push(s); } } @@ -2236,8 +2217,7 @@ HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions) EX_TRY { - HeapSegmentAccumulator acc; - acc.hrError = S_OK; + CallbackAccumulator acc; hr = GetDAC()->EnumerateHeapSegments(&HeapSegmentCallback, &acc); if (SUCCEEDED(hr) && FAILED(acc.hrError)) @@ -2245,9 +2225,9 @@ HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions) if (SUCCEEDED(hr)) { - if (acc.segments.Size() != 0) + if (acc.items.Size() != 0) { - CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, acc.segments.Ptr(), (DWORD)acc.segments.Size()); + CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, acc.items.Ptr(), (DWORD)acc.items.Size()); GetContinueNeuterList()->Add(this, segEnum); hr = segEnum->QueryInterface(__uuidof(ICorDebugHeapSegmentEnum), (void**)ppRegions); } diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index f408785787e217..e570444935ff90 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -69,6 +69,45 @@ struct MachineInfo; #define WriteProcessMemory DONT_USE_WRITEPROCESS_MEMORY +//----------------------------------------------------------------------------- +// CallbackAccumulator +// +// Helper for FP_*_CALLBACK consumers on the DI side that need to collect a +// list of values produced by the DAC and surface a single HRESULT. The +// callback can call Push() without worrying about exceptions - the first +// failure is captured in hrError and subsequent pushes are short-circuited. +// After the enumeration call returns, the caller should check both the DAC's +// returned HRESULT and acc.hrError, then consume acc.items. +//----------------------------------------------------------------------------- +template +struct CallbackAccumulator +{ + CQuickArrayList items; + HRESULT hrError; + + CallbackAccumulator() : hrError(S_OK) { } + + void Push(const T& item) + { + if (FAILED(hrError)) + return; + HRESULT hr = S_OK; + EX_TRY + { + items.Push(item); + } + EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + hrError = hr; + } + + static CallbackAccumulator* From(CALLBACK_DATA pUserData) + { + return reinterpret_cast(pUserData); + } +}; + + /* ------------------------------------------------------------------------- * * Forward class declarations * ------------------------------------------------------------------------- */ diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index d0f3b75d63a72b..3c375295b2d09b 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1712,10 +1712,13 @@ IDacDbiInterface : public IUnknown // Check whether the argument is a runtime callable wrapper. virtual HRESULT STDMETHODCALLTYPE IsRcw(VMPTR_Object vmObject, OUT BOOL * pResult) = 0; - // retrieves the list of interfaces pointers implemented by vmObject, as it is known at - // the time of the call (the list may change as new interface types become available - // in the runtime) - virtual HRESULT STDMETHODCALLTYPE GetRcwCachedInterfacePointers(VMPTR_Object vmObject, BOOL bIInspectableOnly, OUT DacDbiArrayList * pDacItfPtrs) = 0; + // Callback invoked for each cached interface pointer held by an RCW. + typedef void (*FP_RCW_INTERFACE_CALLBACK)(CORDB_ADDRESS itfPtr, CALLBACK_DATA pUserData); + + // Enumerates the interface pointers cached by the RCW associated with vmObject, as known at + // the time of the call (the list may change as new interface types become available in the + // runtime). If vmObject is not an RCW the enumeration is empty. The callback must not throw. + virtual HRESULT STDMETHODCALLTYPE EnumerateRcwCachedInterfacePointers(VMPTR_Object vmObject, FP_RCW_INTERFACE_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0; // ---------------------------------------------------------------------------- // functions to get information about reference/handle referents for ICDValue diff --git a/src/coreclr/inc/dacdbi.idl b/src/coreclr/inc/dacdbi.idl index b19a1059318177..30fc20131bc344 100644 --- a/src/coreclr/inc/dacdbi.idl +++ b/src/coreclr/inc/dacdbi.idl @@ -144,6 +144,7 @@ typedef void (*FP_MODULE_ENUMERATION_CALLBACK)(VMPTR_Assembly vmAssembly, CALLBA typedef void (*FP_THREAD_ENUMERATION_CALLBACK)(VMPTR_Thread vmThread, CALLBACK_DATA pUserData); typedef BOOL (*FP_INTERNAL_FRAME_ENUMERATION_CALLBACK)(FramePointer fpFrame, CALLBACK_DATA pUserData); typedef void (*FP_HEAPSEGMENT_CALLBACK)(CORDB_ADDRESS rangeStart, CORDB_ADDRESS rangeEnd, int generation, ULONG heap, CALLBACK_DATA pUserData); +typedef void (*FP_RCW_INTERFACE_CALLBACK)(CORDB_ADDRESS itfPtr, CALLBACK_DATA pUserData); // @@ -336,7 +337,7 @@ interface IDacDbiInterface : IUnknown HRESULT IsExceptionObject([in] VMPTR_Object vmObject, [out] BOOL * pResult); HRESULT GetStackFramesFromException([in] VMPTR_Object vmObject, [out] DacDbiArrayList_DacExceptionCallStackData * pDacStackFrames); HRESULT IsRcw([in] VMPTR_Object vmObject, [out] BOOL * pResult); - HRESULT GetRcwCachedInterfacePointers([in] VMPTR_Object vmObject, [in] BOOL bIInspectableOnly, [out] DacDbiArrayList_CORDB_ADDRESS * pDacItfPtrs); + HRESULT EnumerateRcwCachedInterfacePointers([in] VMPTR_Object vmObject, [in] FP_RCW_INTERFACE_CALLBACK fpCallback, [in] CALLBACK_DATA pUserData); // Object Data HRESULT GetTypedByRefInfo([in] CORDB_ADDRESS pTypedByRef, [out] struct DebuggerIPCE_ObjectData * pObjectData); diff --git a/src/coreclr/vm/runtimecallablewrapper.h b/src/coreclr/vm/runtimecallablewrapper.h index fec2211911b14e..e8e16a7074b932 100644 --- a/src/coreclr/vm/runtimecallablewrapper.h +++ b/src/coreclr/vm/runtimecallablewrapper.h @@ -272,27 +272,6 @@ struct RCW return m_cbRefCount; } - void GetCachedInterfacePointers(BOOL bIInspectableOnly, - SArray * rgItfPtrs) - { - LIMITED_METHOD_DAC_CONTRACT; - - CachedInterfaceEntryIterator it = IterateCachedInterfacePointers(); - while (it.Next()) - { - PTR_MethodTable pMT = dac_cast((TADDR)(it.GetEntry()->m_pMT.Load())); - if (pMT != NULL && - (!bIInspectableOnly)) - { - TADDR taUnk = (TADDR)(it.GetEntry()->m_pUnknown.Load()); - if (taUnk != NULL) - { - rgItfPtrs->Append(taUnk); - } - } - } - } - LPVOID GetVTablePtr() { LIMITED_METHOD_CONTRACT; return m_vtablePtr; } // Remoting aware QI that will attempt to re-unmarshal on object disconnect. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 0b9bfe91dd9785..09799589fd2d31 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1599,8 +1599,68 @@ public int IsRcw(ulong vmObject, Interop.BOOL* pResult) return hr; } - public int GetRcwCachedInterfacePointers(ulong vmObject, Interop.BOOL bIInspectableOnly, nint pDacItfPtrs) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetRcwCachedInterfacePointers(vmObject, bIInspectableOnly, pDacItfPtrs) : HResults.E_NOTIMPL; +#if DEBUG + [ThreadStatic] + private static List? _debugEnumerateRcwCachedInterfacePointers; + + private static List DebugEnumerateRcwCachedInterfacePointers + => _debugEnumerateRcwCachedInterfacePointers ??= new(); + + [UnmanagedCallersOnly] + private static void EnumerateRcwCachedInterfacePointersDebugCallback(ulong itfPtr, nint _) + { + DebugEnumerateRcwCachedInterfacePointers.Add(itfPtr); + } +#endif + + public int EnumerateRcwCachedInterfacePointers(ulong vmObject, delegate* unmanaged fpCallback, nint pUserData) + { + int hr = HResults.S_OK; + List itfPtrs = new(); + try + { + if (fpCallback is null) + throw new ArgumentNullException(nameof(fpCallback)); + + IObject obj = _target.Contracts.Object; + _ = obj.GetBuiltInComData(new TargetPointer(vmObject), out TargetPointer rcw, out _, out _); + if (rcw != TargetPointer.Null) + { + IBuiltInCOM builtInCom = _target.Contracts.BuiltInCOM; + foreach ((TargetPointer methodTable, TargetPointer unknown) in builtInCom.GetRCWInterfaces(rcw)) + { + if (methodTable != TargetPointer.Null && unknown != TargetPointer.Null) + itfPtrs.Add(unknown.Value); + } + } + + foreach (ulong itfPtr in itfPtrs) + fpCallback(itfPtr, pUserData); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + DebugEnumerateRcwCachedInterfacePointers.Clear(); + delegate* unmanaged debugCallbackPtr = &EnumerateRcwCachedInterfacePointersDebugCallback; + int hrLocal = _legacy.EnumerateRcwCachedInterfacePointers(vmObject, debugCallbackPtr, 0); + Debug.ValidateHResult(hr, hrLocal); + + if (hr == HResults.S_OK && hrLocal == HResults.S_OK) + { + List legacyItfPtrs = DebugEnumerateRcwCachedInterfacePointers; + Debug.Assert(itfPtrs.SequenceEqual(legacyItfPtrs), + $"cDAC: [{string.Join(",", itfPtrs.Select(p => $"0x{p:x}"))}], DAC: [{string.Join(",", legacyItfPtrs.Select(p => $"0x{p:x}"))}]"); + } + DebugEnumerateRcwCachedInterfacePointers.Clear(); + } +#endif + return hr; + } public int GetTypedByRefInfo(ulong pTypedByRef, nint pObjectData) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypedByRefInfo(pTypedByRef, pObjectData) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 79c0e36953db9f..d86a7c433068c8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -450,7 +450,7 @@ public unsafe partial interface IDacDbiInterface int IsRcw(ulong vmObject, Interop.BOOL* pResult); [PreserveSig] - int GetRcwCachedInterfacePointers(ulong vmObject, Interop.BOOL bIInspectableOnly, nint pDacItfPtrs); + int EnumerateRcwCachedInterfacePointers(ulong vmObject, /*FP_RCW_INTERFACE_CALLBACK*/ delegate* unmanaged fpCallback, nint pUserData); [PreserveSig] int GetTypedByRefInfo(ulong pTypedByRef, nint pObjectData); From 2043af1f3718d8e54a4efc604bcd895cb961b76c Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 18 May 2026 15:47:59 -0700 Subject: [PATCH 2/2] Update DacDbiImpl.cs --- .../Dbi/DacDbiImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 09799589fd2d31..b632ac39ca7321 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -1650,7 +1650,7 @@ public int EnumerateRcwCachedInterfacePointers(ulong vmObject, delegate* unmanag int hrLocal = _legacy.EnumerateRcwCachedInterfacePointers(vmObject, debugCallbackPtr, 0); Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK && hrLocal == HResults.S_OK) + if (hr == HResults.S_OK) { List legacyItfPtrs = DebugEnumerateRcwCachedInterfacePointers; Debug.Assert(itfPtrs.SequenceEqual(legacyItfPtrs),