Reconcile ReleaseHolder, ComHolderPreemp, and ComHolderAnyMode#130373
Reconcile ReleaseHolder, ComHolderPreemp, and ComHolderAnyMode#130373AaronRobinsonMSFT wants to merge 17 commits into
Conversation
Converge the three COM-interface RAII holders onto the LifetimeHolder<Traits> pattern: - Retarget ReleaseHolder to LifetimeHolder<ReleaseHolderTraits<T>>, adopting ComHolderPreemp's preemptive-mode release semantics (the VM default). The Free() contract is MODE_PREEMPTIVE, gated on ENABLE_CONTRACTS_IMPL so non-VM consumers (utilcode, md, DAC, createdump) use a static-contract annotation. - Remove ComHolderPreemp entirely; ReleaseHolder replaces all uses. - Rename ComHolderAnyMode to ReleaseHolderAnyMode. - Relax both holders' static_assert from is_base_of<IUnknown, T> to a duck-typed "T has a Release() member" check (holder_detail::HasReleaseMethod), so ReleaseHolder can hold non-IUnknown types that expose Release(). Migrate all call sites off the legacy SpecializedWrapper API onto the LifetimeHolder API (Extract->Detach, Clear->Free, GetValue/IsNull/Assign/SuppressRelease removed). Read-after-suppress sites are restructured to detach into a raw pointer to preserve exception-safety and refcount behavior. BindAssemblySpec is refactored so the CoreLib singleton borrow no longer relies on SuppressRelease. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR refactors CoreCLR’s COM / ref-counted RAII “holder” usage to converge on the LifetimeHolder<Traits> pattern: replacing legacy SpecializedWrapper APIs (Extract, Clear, GetValue, etc.), removing ComHolderPreemp, and renaming ComHolderAnyMode to ReleaseHolderAnyMode.
Changes:
- Retargets
ReleaseHolderto aLifetimeHolder<ReleaseHolderTraits<T>>implementation and introducesHolderDetail::HasReleaseMethodfor duck-typedRelease()detection. - Renames / consolidates COM holders (
ComHolderAnyMode→ReleaseHolderAnyMode; removesComHolderPreemp) and migrates call sites toDetach()/Free()patterns. - Refactors a few lifetime/ownership patterns (notably
AppDomain::BindAssemblySpec) to avoid relying on legacy holder APIs.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/wrappers.h | Renames ComHolderAnyModeTraits/alias to ReleaseHolderAnyMode* and removes ComHolderPreemp traits. |
| src/coreclr/vm/weakreferencenative.cpp | Migrates ComHolderPreemp<> usages to ReleaseHolder<>. |
| src/coreclr/vm/stubmgr.cpp | Migrates ComHolderAnyMode<> usages to ReleaseHolderAnyMode<>. |
| src/coreclr/vm/stubhelpers.cpp | Migrates ComHolderAnyMode<> usages to ReleaseHolderAnyMode<>. |
| src/coreclr/vm/stdinterfaces.cpp | Migrates COM holders to ReleaseHolder<> / ReleaseHolderAnyMode<>. |
| src/coreclr/vm/runtimecallablewrapper.cpp | Migrates COM holders and adjusts a CCWHolder lifetime flow to use Detach(). |
| src/coreclr/vm/rejit.cpp | Removes legacy = NULL initialization pattern for ReleaseHolder. |
| src/coreclr/vm/peimagelayout.cpp | Replaces Extract() with Detach() when returning owned layouts. |
| src/coreclr/vm/peimage.cpp | Migrates metadata COM holder to ReleaseHolderAnyMode<>. |
| src/coreclr/vm/peassembly.cpp | Migrates ComHolderPreemp<> to ReleaseHolder<>. |
| src/coreclr/vm/olevariant.cpp | Migrates COM holders to ReleaseHolder<> / ReleaseHolderAnyMode<>. |
| src/coreclr/vm/olecontexthelpers.cpp | Migrates ComHolderPreemp<> to ReleaseHolder<>. |
| src/coreclr/vm/nativeimage.cpp | Replaces legacy holder APIs (IsNull/Extract) with == NULL and Detach(). |
| src/coreclr/vm/marshalnative.cpp | Updates CCWHolder initialization to brace-init and adjusts holder usage. |
| src/coreclr/vm/interoputil.cpp | Call sites migrated to new holder type names. |
| src/coreclr/vm/interopconverter.cpp | Migrates COM holders and replaces legacy holder APIs in a few places. |
| src/coreclr/vm/encee.cpp | Renames holder types in EnC path comments and variables. |
| src/coreclr/vm/eetoprofinterfaceimpl.cpp | Replaces Extract()+NULL patterns with Detach() for ownership transfer. |
| src/coreclr/vm/dllimport.cpp | Updates holder assignment to use operator= instead of Assign. |
| src/coreclr/vm/dispparammarshaler.cpp | Migrates COM holders to ReleaseHolderAnyMode<>. |
| src/coreclr/vm/crossloaderallocatorhash.inl | Updates holder initialization style and uses Detach() for returns. |
| src/coreclr/vm/coreassemblyspec.cpp | Uses Detach() when returning owned binder assembly. |
| src/coreclr/vm/commodule.cpp | Migrates COM holders to ReleaseHolder<> / ReleaseHolderAnyMode<>. |
| src/coreclr/vm/comconnectionpoints.cpp | Migrates ComHolderAnyMode<> to ReleaseHolderAnyMode<>. |
| src/coreclr/vm/comcallablewrapper.cpp | Migrates holders and refactors template-return ownership to use Detach(). |
| src/coreclr/vm/comcache.cpp | Migrates COM holders and replaces Extract() with Detach(). |
| src/coreclr/vm/clrex.cpp | Migrates error-info holder to ReleaseHolderAnyMode<>. |
| src/coreclr/vm/ceeload.cpp | Migrates symbol reader holders to ReleaseHolder<> and modernizes initialization. |
| src/coreclr/vm/assemblynative.cpp | Passes holder directly to tracing (via implicit pointer conversion). |
| src/coreclr/vm/assembly.cpp | Uses Detach() for ownership transfer and brace-init for holders. |
| src/coreclr/vm/appdomain.hpp | Adjusts FindCachedFile signature (removes unused fThrow). |
| src/coreclr/vm/appdomain.cpp | Refactors BindAssemblySpec lifetime management; simplifies FindCachedFile. |
| src/coreclr/md/enc/mdinternalrw.cpp | Fixes QueryInterface call to pass void**. |
| src/coreclr/inc/holder.h | Removes legacy ReleaseHolder wrapper, adds LifetimeHolder-based ReleaseHolder + HasReleaseMethod. |
| src/coreclr/dlls/mscoree/exports.cpp | Uses Detach() to transfer host handle ownership. |
| src/coreclr/debug/ee/debugger.cpp | Migrates ComHolderPreemp<> to ReleaseHolder<>. |
| src/coreclr/debug/daccess/enummem.cpp | Replaces legacy holder APIs (Clear/GetValue) with Free() and direct holder conversions. |
| src/coreclr/debug/daccess/dacdbiimpl.cpp | Removes = nullptr initialization for ReleaseHolder. |
| src/coreclr/debug/daccess/daccess.cpp | Removes = nullptr initialization for ReleaseHolder. |
| src/coreclr/debug/daccess/cdac.h | Replaces Extract() with Detach() in move operations. |
| src/coreclr/debug/createdump/threadinfo.cpp | Replaces Extract() with Detach() when transferring method ownership into a frame. |
| src/coreclr/debug/createdump/createdumpunix.cpp | Updates holder initialization to brace-init. |
| src/coreclr/debug/createdump/crashinfo.cpp | Removes = nullptr initialization for ReleaseHolder. |
| src/coreclr/binder/defaultassemblybinder.cpp | Replaces Extract() with Detach() for binder assembly ownership transfer. |
| src/coreclr/binder/customassemblybinder.cpp | Replaces Extract() with Detach() for binder assembly ownership transfer. |
| src/coreclr/binder/assemblybindercommon.cpp | Replaces Extract() with Detach() for binder assembly ownership transfer. |
| src/coreclr/binder/assembly.cpp | Replaces Extract() with Detach() for assembly-name ownership transfer. |
Copilot's findings
- Files reviewed: 47/47 changed files
- Comments generated: 4
holder.h now includes contract.h (for the MODE_PREEMPTIVE CONTRACTL in ReleaseHolderTraits::Free), which pulled check.inl -> clrhost.h -> holder.h back into itself before Holder/LifetimeHolder were defined. Remove the clrhost.h include from check.inl to break the cycle, and make the two headers that were relying on that transitive path self-sufficient: - inc/sigbuilder.h: include corhdr.h for CorElementType/mdToken. - jit/jitstd/algorithm.h: include <utility> for std::swap used by jitstd::sort. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Added DBI_COMPILE definition for conditional compilation in clrdefinitions.cmake. - Updated Debugger::GetAndSendTransitionStubInfo to use g_pEEInterface for IP validation. - Refined contract enabling conditions in contract.h to include DBI builds. - Modified ReleaseHolderTraits to use ENABLE_EE_CONTRACTS for contract checks. - Removed unused IsIPInModule function from utilcode.h and implemented it in ExecutionManager. - Updated various files to consistently use ExecutionManager::IsIPInModule for IP validation.
ReleaseHolder's Detach() clears the holder's value (unlike the old Extract(), which retained it via SuppressRelease). BindAssemblyByProbingPaths detached pAssembly into *ppAssembly and then dereferenced pAssembly for the ref-def check, hitting the operator-> null assert (holder.h:974) on the TPA bind path at startup, crashing every coreclr test process. Use a local for the detached pointer and read from it. Also sequence the image reads before Detach() in nativeimage.cpp OpenR2RFromPE, where the member accesses and Detach() were unsequenced call arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6457cb5c-e732-41ad-8cc0-af22e64c1f49
…s across the codebase
…agement and prevent memory leaks
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/coreclr/vm/wrappers.h:61
ReleaseHolderAnyModeTraits::FreecallsSafeRelease(value), butSafeReleaseis declared asULONG SafeRelease(IUnknown* ...). The currentstatic_assertonly checks for aRelease()member, so it will accept types that are not convertible toIUnknown*, and then fail later with a less clear conversion error (and the comment suggests non-IUnknown support). Either constrain the trait toIUnknown-convertible types, or changeFreeto not depend onSafeRelease(IUnknown*).
- Files reviewed: 64/64 changed files
- Comments generated: 1
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/coreclr/vm/wrappers.h:60
- ReleaseHolderAnyModeTraits::Free calls
SafeRelease(value), which requiresvalueto be convertible toIUnknown*. The new static_assert only checks for aRelease()member, so non-IUnknown types will pass the assert but then fail later with a less clear conversion error. Consider asserting convertibility toIUnknown*(or reverting this holder to the IUnknown constraint) so the trait requirements match the implementation.
- Files reviewed: 75/75 changed files
- Comments generated: 5
| #ifdef _DEBUG | ||
| DebugInit(crstType, flags); | ||
| #endif | ||
| return suc; |
| _ep_rt_coreclr_config_lock_handle.lock = &_ep_rt_coreclr_config_lock; | ||
| _ep_rt_coreclr_config_lock_handle.lock->InitNoThrow (CrstEventPipe, (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); | ||
| _ep_rt_coreclr_config_lock_handle.lock->Init (CrstEventPipe, (CrstFlags)(CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); | ||
|
|
| spin_lock->lock = new (nothrow) CrstStatic (); | ||
| spin_lock->lock->InitNoThrow (CrstType::CrstEventPipe); | ||
| spin_lock->lock->Init (CrstType::CrstEventPipe); | ||
| } |
| s_lock.Init(CrstGcStressControl); | ||
|
|
||
| if (g_pRhConfig->GetGcStressSeed()) | ||
| s_lGcStressRNGSeed = (uint32_t)g_pRhConfig->GetGcStressSeed(); |
| void InitializeGCEventLock() | ||
| { | ||
| g_eventStashLock.InitNoThrow(CrstGcEvent); | ||
| g_eventStashLock.Init(CrstGcEvent); | ||
| } |
| // EventPipe library will intialize using thread, EventPipeBufferManager instances and will maintain these on the EventPipe library side | ||
|
|
||
| spin_lock->lock = new (nothrow) CrstStatic (); | ||
| spin_lock->lock->InitNoThrow (CrstType::CrstEventPipe); |
There was a problem hiding this comment.
Did you mean to push these changes that are outside the original scope of this PR that is fairly sizable already, or is it just your local AI agents misbehaving?
There was a problem hiding this comment.
Yes, I changed these... sigh. The issue started out as a misaligned contract. When I determined the issue, it became clear the Init and InitNoThrow were identical - both didn't throw. Realizing this I decided to remove InitNoThrow and make Init return a bool. It is purely cosmetic at this point as the contract should be both no throw and no trigger. I can probably reduce it, but if you look at the last commit by itself you can see the changes are rather mechanical.
There was a problem hiding this comment.
I agree that the InitNoThrow vs. Init implementation is questionable. I am not sure whether unifying it to a single implementation that returns bool is an improvement since it creates a number of places that return bool without checking it.
The initialization of critical sections never fails in practice. I think it would be ok to assert it (or even fail fast) centrally level and avoid propagating the problem to callers.
If possible, I would leave addressing this and other issues for a follow up PR. I suspect that it will need a bunch more iterations and growing set of changes.
There was a problem hiding this comment.
since it creates a number of places that return bool without checking it.
I don't disagree, but it never threw anyways so it is no better/worse than what it was.
The initialization of critical sections never fails in practice. I think it would be ok to assert it (or even fail fast) centrally level and avoid propagating the problem to callers.
That works too. This does mean I can remove some failure cases
There was a problem hiding this comment.
If possible, I would leave addressing this and other issues for a follow up PR. I suspect that it will need a bunch more iterations and growing set of changes.
I'm trying to. The contracts system and use across the repo has degraded very badly so minor tweaks and fixes uncover a slew of issues. I'm going to give this one more shot and see the impact. The vast majority here are updating contracts though and no functional change. I will try and limit it to that for the final attempt.
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "05b5a4a3f05aa075d893d3223e6498a697f47dbb",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "6bd973a541700487cb4a1891943c2a7a55e4c956",
"last_reviewed_commit": "05b5a4a3f05aa075d893d3223e6498a697f47dbb",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "6bd973a541700487cb4a1891943c2a7a55e4c956",
"last_recorded_worker_run_id": "29695952166",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "c8a6b62628ce0721f2ae5c83939a7fcff42633d9",
"review_id": 4730876129
},
{
"commit": "05b5a4a3f05aa075d893d3223e6498a697f47dbb",
"review_id": 4731158829
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: Justified refactor by the interop area owner. The three COM-interface RAII holders (ReleaseHolder, ComHolderPreemp, ComHolderAnyMode) had overlapping, subtly different semantics and a dangerous legacy ReleaseHolder (documented "UNSAFE TO USE IN THE VM"). Converging them onto the LifetimeHolder<Traits> pattern removes a real footgun and reduces duplication.
Approach: Sound and largely mechanical. ReleaseHolder becomes LifetimeHolder<ReleaseHolderTraits<T>> with preemptive-release semantics, ComHolderPreemp is removed, ComHolderAnyMode is renamed to ReleaseHolderAnyMode, and the static_assert is relaxed to a duck-typed HasReleaseMethod check. Call sites migrate from the SpecializedWrapper API (Extract/Clear/GetValue/IsNull/SuppressRelease) to the LifetimeHolder API. The read-after-Detach restructurings (assembly binder, nativeimage.cpp OpenR2RFromPE, RCW aggregation) are correct and address the real semantic difference that Detach() clears the holder whereas the old Extract() retained it — a bug already caught and fixed within this PR (commit 9b81753). Moving IsIPInModule from utilcode into the VM behind g_pEEInterface and the contract-header include-cycle cleanup are reasonable supporting changes; the IsIPInModule body was moved verbatim.
Summary: ComHolderPreemp/ComHolderAnyMode/Extract/GetValue references remain on the converted COM paths). My one substantive concern is a possible behavioral change on the preemptive release path: the removed ComHolderPreemp released through SafeReleasePreemp, which suppresses ThrowsViolation | FaultViolation to tolerate a COM message pump running managed code during Release(); the new ReleaseHolderTraits::Free calls value->Release() directly without that suppression. This is a debug/checked-build contract concern only, not a retail correctness issue, but it warrants an area-owner confirmation that it is intentional. See the inline comment on src/coreclr/inc/holder.h.
Detailed Findings
⚠️ Correctness (debug contracts) — Preemptive release no longer suppresses Throws/Fault violations
See the inline comment on src/coreclr/inc/holder.h (ReleaseHolderTraits::Free). The migrated ComHolderPreemp sites previously released via SafeReleasePreemp with CONTRACT_VIOLATION(ThrowsViolation | FaultViolation); the new trait does not. Advisory, not merge-blocking, but should be confirmed intentional by the interop owner. ReleaseHolderAnyMode still routes through SafeRelease and is unaffected.
✅ Extract→Detach conversions — Read-after-detach correctly restructured
Detach() clears the holder value (unlike the old Extract(), which retained it via an implicit suppress). The PR correctly detaches into a local before subsequent dereferences in assemblybindercommon.cpp (BindByTpaList ref-def check, pFoundAssembly) and sequences the image member reads before Detach() in nativeimage.cpp OpenR2RFromPE. A null-deref regression from this exact conversion was found and fixed earlier in the PR (commit 9b81753), which increases confidence the pattern was audited.
✅ Migration completeness — No stray legacy holder/API references on converted paths
Grep confirms no remaining ComHolderPreemp/ComHolderAnyMode usages, and the surviving .Extract()/.GetValue()/.SuppressRelease()/.IsNull() occurrences are on unrelated SpecializedWrapper/TypeHandle/StackFrame holders that this PR does not touch.
💡 Style — NULL vs nullptr in new/renamed traits
ReleaseHolderTraits::Default() and the renamed ReleaseHolderAnyModeTraits::Default() use return NULL; (the latter changed from nullptr). The native review guidance prefers nullptr; the internal value != NULL check in Free is likewise legacy style. Minor and consistent-enough with surrounding VM code, so non-blocking.
✅ IsIPInModule relocation — Verbatim move, correctly abstracted behind the EE interface
The function body moved from utilcode/util.cpp to vm/util.cpp unchanged, is declared in util.hpp, and the debugger now reaches it via g_pEEInterface->IsIPInModule (new pure-virtual on EEDebugInterface, implemented in EEDbgInterfaceImpl with WRAPPER_NO_CONTRACT and DAC_UNEXPECTED()), plus the DBI_COMPILE/DBI_COMPONENT cmake plumbing. Consistent with how other EE services are exposed to the DBI.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 163.7 AIC · ⌖ 11.7 AIC · ⊞ 10K
| #endif // ENABLE_EE_CONTRACTS | ||
|
|
||
| if (value != NULL) | ||
| value->Release(); |
There was a problem hiding this comment.
Before this PR, the VM sites converted here (e.g. RCW::CreateRCWInternal, RCW::GetMarshalingType, ComWeakRefToObject, Debugger::getBoundariesHelper) used ComHolderPreemp, whose Free called SafeReleasePreemp. That helper deliberately adds CONTRACT_VIOLATION(ThrowsViolation | FaultViolation) with the comment "Message pump could happen, so arbitrary managed code could run" before invoking Release().
The new ReleaseHolderTraits::Free calls value->Release() directly under a bare NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE contract with no ThrowsViolation/FaultViolation suppression. For COM proxies whose Release() pumps messages and can run managed code that throws/faults, this is a stricter debug contract than the code these sites previously ran under, and could trip a contract assert in debug/checked builds that the old SafeReleasePreemp path tolerated.
Was dropping the ThrowsViolation | FaultViolation suppression intentional for the migrated ComHolderPreemp sites? If proxy Release() can pump, consider routing ReleaseHolderTraits::Free through SafeReleasePreemp (or adding the equivalent violation) to preserve prior behavior. Note ReleaseHolderAnyModeTraits::Free still goes through SafeRelease, so it is unaffected; this concern is specific to the ReleaseHolder (preemptive) path.
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (3)
src/coreclr/vm/crst.cpp:49
- InitWorker returns 'suc' but still marks the CRST as initialized before that (SetFlags/SetCrstInitialized), so a minipal_mutex_init failure in retail builds (where _ASSERTE is compiled out) would leave an uninitialized mutex that later gets treated as valid. Consider bailing out early on failure before setting flags / initialized state.
src/coreclr/vm/wrappers.h:60 - ReleaseHolderAnyModeTraits::Free calls SafeRelease(value), which is declared as SafeRelease(IUnknown* ...). With the new duck-typed static_assert, types that merely have Release() but are not convertible to IUnknown* will fail with template/overload errors rather than the intended diagnostic. Since this holder is tied to SafeRelease’s COM semantics, the constraint should still express the IUnknown* convertibility requirement (or SafeRelease would need to be generalized).
src/coreclr/inc/holder.h:1022 - ReleaseHolderTraits::Free hard-codes MODE_PREEMPTIVE for all types with a Release() method when ENABLE_EE_CONTRACTS is enabled. ReleaseHolder is also used for non-COM ref-counted objects in VM code that runs under STANDARD_VM_CONTRACT / MODE_ANY (e.g., ReleaseHolder in ReJitManager::ConfigureILCodeVersion via prestub.cpp). This will trip contract violations (and is semantically misleading) for non-COM releases. Consider splitting the concepts: keep a MODE_ANY ReleaseHolder for general ref-counted runtime types, and introduce a separate preemptive-only COM holder (or a traits parameter) for the former ComHolderPreemp use cases.
NOTHROW;
GC_TRIGGERS;
MODE_PREEMPTIVE;
} CONTRACTL_END;
- Files reviewed: 76/76 changed files
- Comments generated: 0 new
There was a problem hiding this comment.
Holistic Review
Motivation: Unchanged. This PR converges the three COM-interface RAII holders (ReleaseHolder, ComHolderPreemp, ComHolderAnyMode) onto the LifetimeHolder<Traits> pattern, removing a documented "UNSAFE TO USE IN THE VM" footgun and reducing duplication. The supporting contract-annotation cleanups and the IsIPInModule relocation into the VM remain part of the change.
Approach: Unchanged and sound. Since the prior review at commit c8a6b62, the only new commit (05b5a4a, "Add GC_NOTRIGGER to contract definitions") adds GC_NOTRIGGER to three CONTRACTL blocks in src/coreclr/inc/metadata.h: EnumInit, EnumInitNoThrow, and EnumAllInit. These are thin HENUMInternalHolder-style wrappers that forward to IMDInternalImport::EnumInit/EnumAllInit; metadata enumeration is a non-GC-triggering operation, so tightening the contract to GC_NOTRIGGER is consistent with the sibling EnumAssociateInit path and with the PR's broader contract-refinement theme. This is a checked-build contract tightening only, with no retail behavioral effect.
Summary: ReleaseHolderTraits::Free no longer suppressing ThrowsViolation | FaultViolation as the removed ComHolderPreemp's SafeReleasePreemp did) is unaffected by this commit and still warrants interop area-owner confirmation.
Assessment History:
- review 4730876129 reviewed commit c8a6b62 with verdict
⚠️ Needs Human Review. Current verdict:⚠️ Needs Human Review — unchanged. The incremental commit 05b5a4a only addsGC_NOTRIGGERto three metadata enum-init contracts, which does not alter the motivation, approach, or risk profile of the PR.
Detailed Findings
No new actionable findings in the incremental scope. The GC_NOTRIGGER additions in src/coreclr/inc/metadata.h (EnumInit, EnumInitNoThrow, EnumAllInit) are correct and consistent with surrounding contract annotations. Prior-review findings on the cumulative PR (the ReleaseHolderTraits::Free preemptive-release contract-suppression concern and the minor NULL vs nullptr style note) remain applicable but are outside this incremental scope and are unchanged.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 59.6 AIC · ⌖ 16.2 AIC · ⊞ 10K
|
Now failing with |
Converge the three COM-interface RAII holders (
ReleaseHolder,ComHolderPreemp,ComHolderAnyMode) onto theLifetimeHolder<Traits>pattern.Changes
ReleaseHolderis retargeted toLifetimeHolder<ReleaseHolderTraits<T>>, adoptingComHolderPreemp's preemptive-mode release semantics (the VM default).Free()uses aMODE_PREEMPTIVEcontract gated onENABLE_CONTRACTS_IMPL, so non-VM consumers (utilcode, md, DAC, createdump) fall back to a static-contract annotation.ComHolderPreempis removed;ReleaseHolderreplaces all uses.ComHolderAnyModeis renamed toReleaseHolderAnyMode.static_assertis relaxed fromis_base_of<IUnknown, T>to a duck-typed "Thas aRelease()member" check (holder_detail::HasReleaseMethod), soReleaseHoldercan hold non-IUnknowntypes that exposeRelease().