Repository navigation
Fix native memory leak in AsAnyMarshaler.ConvertLayoutToNative on exception - #126909
jkoritzinsky with Copilot wants to merge 6 commits into
Conversation
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33ae7353-2afd-4be1-9081-898caba946da Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes an exception-path leak in CoreCLR’s AsAnyMarshaler.ConvertLayoutToNative (in System.Private.CoreLib) by ensuring unmanaged memory allocated via Marshal.AllocCoTaskMem is freed if LayoutTypeConvertToUnmanaged throws during layout marshaling.
Changes:
- Wrap
LayoutTypeConvertToUnmanagedintry/catchwithinConvertLayoutToNative. - Free the
CoTaskMembuffer on exception before rethrowing.
There was a problem hiding this comment.
Pull request overview
Fixes an exception-path native memory leak in AsAnyMarshaler.ConvertLayoutToNative within CoreLib interop marshaling by ensuring native allocations (and any accumulated cleanup work) are released when LayoutTypeConvertToUnmanaged throws.
Changes:
- Wraps the
LayoutTypeConvertToUnmanagedcall inConvertLayoutToNativewith atry/catch. - On exception, destroys the
cleanupWorkListand frees theMarshal.AllocCoTaskMembuffer before rethrowing.
|
Tagging subscribers to this area: @dotnet/interop-contrib |
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "e439821dede5d567aa78b379fa8e9bdc2764d0b9",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "cead7e78dc1bb6bd8f3c1f2e918df5e2c97156d6",
"last_reviewed_commit": "e439821dede5d567aa78b379fa8e9bdc2764d0b9",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "cead7e78dc1bb6bd8f3c1f2e918df5e2c97156d6",
"last_recorded_worker_run_id": "29766969872",
"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": "7e6b02951dede8088a2b29a2780b358bb04d2d56",
"review_id": 4730179668
},
{
"commit": "e439821dede5d567aa78b379fa8e9bdc2764d0b9",
"review_id": 4737835721
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: AsAnyMarshaler.ConvertLayoutToNative allocates a native buffer with Marshal.AllocCoTaskMem and then calls StubHelpers.LayoutTypeConvertToUnmanaged. If that conversion throws, the stack unwinds past return pNativeHome and the buffer is never freed, leaking native memory. Because ClearNative/ConvertToManaged only run for a marshaler whose ConvertToNative returned successfully, nothing else reclaims the buffer on this path. The motivation is sound and the leak is real.
Approach: The change wraps the IsIn-guarded LayoutTypeConvertToUnmanaged call in a try/catch. On failure it calls StubHelpers.DestroyCleanupList(ref cleanupWorkList), frees the CoTaskMem buffer, and rethrows. This mirrors the defensive cleanup already performed in ClearNative and matches the sibling error-handling pattern in this file. The approach is minimal and targeted, with no change to the happy path.
Summary: The fix correctly plugs the leak and, beyond the PR description, also releases any cleanupWorkList entries (e.g. SafeHandle references) accumulated during the partial conversion, which is the right thing to do. DestroyCleanupList sets cleanupWorkList to null and pNativeHome is a local, so there is no double-free risk with the normal ClearNative path (which never runs when ConvertToNative throws). One residual, non-blocking observation: fields that LayoutTypeConvertToUnmanaged had already marshaled into the native buffer before throwing are not unwound via DestroyStructure/LayoutTypeConvertToManaged (unlike the success-time ClearNative, which uses layoutType). That is inherent to a partial-conversion failure and consistent with existing behavior, so it is acceptable here. Verdict: LGTM.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 74 AIC · ⌖ 10.3 AIC · ⊞ 10K
There was a problem hiding this comment.
Holistic Review
Motivation: Unchanged from the prior review. AsAnyMarshaler's layout marshaling allocates a native buffer with Marshal.AllocCoTaskMem and then calls StubHelpers.LayoutTypeConvertToUnmanaged. If that conversion throws, the stack unwinds past the return and the buffer (plus any accumulated cleanup-work-list entries) is never reclaimed, since ClearNative/ConvertToManaged only run for a marshaler whose ConvertToNative returned successfully. The leak is real and worth fixing.
Approach: Unchanged in intent — wrap the IsIn-guarded conversion in a try/catch that, on failure, destroys the cleanup work list, frees the CoTaskMem buffer, and rethrows. Since the previous review, the PR was rebased onto a base where AsAnyMarshaler was refactored: the old flat ConvertLayoutToNative(pManagedHome, ...) method is now LayoutImplementation.ConvertToNative(object managed, ...) with a pNative local and an _cleanupWorkList field. The merge-conflict resolution updated the cleanup-list field reference to _cleanupWorkList but did not rename the argument/buffer references, so the reapplied patch still uses the removed identifiers pManagedHome and pNativeHome.
Summary: The incremental change since commit 7e6b029 is the rebase and its conflict resolution. That resolution is incorrect: the try-block calls LayoutTypeConvertToUnmanaged(pManagedHome, (byte*)pNativeHome, ...) and the catch frees pNativeHome, but neither identifier exists in the new LayoutImplementation.ConvertToNative scope (the parameter is managed, the buffer local is pNative). This is a hard CS0103 compile break for System.Private.CoreLib — see the inline finding for the exact lines and corrected code. The underlying fix remains correct once the identifiers are updated to managed/pNative. Verdict: the fix is sound in principle but currently does not compile and must be corrected before merge.
Detailed Findings
See the inline comment on src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs (lines 978–987): the rebased patch references the removed pManagedHome/pNativeHome identifiers instead of the new managed/pNative, causing a compile failure.
Assessment History
- review 4730179668 reviewed commit 7e6b029 with an LGTM verdict. Current verdict: not mergeable as-is (compile break). Assessment changed: the motivation and the fundamental approach are unchanged, but the rebase's merge-conflict resolution left stale identifiers (
pManagedHome/pNativeHome) in the refactoredLayoutImplementation.ConvertToNative, turning a previously-compiling, correct fix into non-compiling code.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 56.2 AIC · ⌖ 10.9 AIC · ⊞ 10K
…leanup Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Description
AsAnyMarshaler.ConvertLayoutToNativeleaks theMarshal.AllocCoTaskMem-allocated buffer whenLayoutTypeConvertToUnmanagedthrows — the exception unwinds past thereturn pNativeHomewith no cleanup.Fix
Wrap the conversion call in a
try/catchthat frees on failure, matching the defensive pattern already used in sibling code in the same file:Changes
src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs: Addedtry/catcharoundLayoutTypeConvertToUnmanagedcall inConvertLayoutToNativeto free native memory on exception.Testing
Risk
Low — strictly adds a cleanup path that was previously missing; no behavioral change on the happy path.