Skip to content

Fix IEnumerator adapter initialization for COM RCWs - #136392

Merged
AaronRobinsonMSFT merged 2 commits into
dotnet:mainfrom
AaronRobinsonMSFT:arobins/fix-com-enumerator
Oct 24, 2026
Merged

AaronRobinsonMSFT merged 2 commits into
dotnet:mainfrom
AaronRobinsonMSFT:arobins/fix-com-enumerator

Conversation

@AaronRobinsonMSFT

@AaronRobinsonMSFT AaronRobinsonMSFT commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Initialize COM enumerator adapters lazily per RCW instead of during interface resolution, which cached dispatch can bypass. Reuse the existing adapter cache.

Add regression coverage for repeated fresh RCWs, all three enumerator entry points, empty/nonempty collections, and reset after exhaustion. Confirmed the test fails with NullReferenceException before the fix.

Fixes #136118

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ee70b0a5-4f16-4838-9d10-fe9924d6864c
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member Author

@jkoritzinsky I will also be taking this into .NET 11 and .NET 10 servicing.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused change removes the initialization dependency on interface resolution, covers all three entry points, and has no unresolved review findings.

No open issues 🎉

What changed in this PR

Fixes COM runtime-callable wrapper (RCW) enumeration by initializing adapters lazily at each enumerator entry point, rather than relying on interface resolution that cached dispatch can bypass.

Changes:

  • Reuses the existing per-RCW adapter cache for MoveNext, Reset, and Current.
  • Adds regression coverage for fresh RCWs, each initial entry point, empty/nonempty collections, and reset after exhaustion.
File Description
src/​tests/​Interop/​COM/​Dynamic/​CollectionTest.cs Adds coverage for adapter initialization and enumeration state.
src/​coreclr/​System.Private.CoreLib/​src/​System/​__ComObject.cs Moves adapter initialization from interface resolution to enumerator calls.

Review effort: Balanced

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member Author

/ba-g HTTP failures

@AaronRobinsonMSFT
AaronRobinsonMSFT merged commit 1df7015 into dotnet:main Oct 24, 2026
107 of 109 checks passed
@github-project-automation github-project-automation Bot moved this to Done in AppModel Oct 24, 2026
@AaronRobinsonMSFT
AaronRobinsonMSFT deleted the arobins/fix-com-enumerator branch October 24, 2026 16:05
@AaronRobinsonMSFT

Copy link
Copy Markdown
Member Author

/backport to release/10.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/10.0 (link to workflow run)

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member Author

/backport to release/11.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0 (link to workflow run)

@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 26, 2026
JulieLeeMSFT pushed a commit that referenced this pull request Oct 29, 2026
…136429)

Backport of #136392 to release/11.0

/cc @AaronRobinsonMSFT

## Customer Impact

- [x] Customer reported
- [ ] Found internally

Customer-reported in #136118 on .NET 10.0.9 / Windows 11
Arm64 and reproduced locally on Windows x64. Every fresh native
IEnumVARIANT RCW cast to IEnumerator should enumerate successfully.
Instead, cached interface dispatch can bypass per-RCW adapter
initialization after the first instance, causing NullReferenceException
when using subsequent enumerators. This breaks COM collection
enumeration, including Windows Firewall rules, for applications ported
from .NET Framework 4.8.

## Regression

- [x] Yes
- [ ] No

Introduced by #105965 (commit 0d8e565, merged August
17, 2024), which moved RCW enumeration to IDynamicInterfaceCastable and
initialized per-instance adapter state during interface resolution. That
resolution can be cached without initializing later RCWs. The customer
reports this worked on .NET Framework 4.8.

## Testing

The new regression test reproduced the NullReferenceException without
the fix and passed with it. It covers three fresh native RCWs per case,
MoveNext/Reset/Current as the first member called, empty and nonempty
collections, and reset after exhaustion. The existing COM Dynamic
collection test did not repeatedly cast fresh native IEnumVARIANT RCWs
at the same call sites.

Original main-branch validation on Windows x64 with Checked CoreCLR:
- `.\build.cmd clr.corelib -arch x64 -c Release -rc Checked` - passed.
- `.\src\tests\build.cmd x64 Checked -Test
Interop\COM\Dynamic\Dynamic.csproj -SkipNative -SkipGenerateLayout` -
passed using existing native test assets.
- With `CORE_ROOT` set to
`artifacts\tests\coreclr\windows.x64.Checked\Tests\Core_Root`,
`.\artifacts\tests\coreclr\windows.x64.Checked\Interop\COM\Dynamic\Dynamic\Dynamic.cmd`
- passed normally and with `DOTNET_TieredCompilation=0`.

No additional local builds or tests were run for this backport; the
local validation above was performed for the original main-branch fix.

## Risk

Low. The change is limited to the Windows built-in COM IEnumerator
adapter path. It lazily obtains each RCW's adapter through the existing
marshaler cache instead of depending on interface-resolution side
effects. There are no public API or native COM server changes.
Regression coverage exercises fresh RCWs and all three enumerator
members, and the existing COM Dynamic test program passed.

**IMPORTANT**: If this backport is for a servicing release, please
verify that:

- For .NET 8 and .NET 9: The PR target branch is `release/X.0-staging`,
not `release/X.0`.
- For .NET 10+: The PR target branch is `release/X.0` (no `-staging`
suffix).

## Package authoring no longer needed in .NET 9

**IMPORTANT**: Starting with .NET 9, you no longer need to edit a NuGet
package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older
versions.

> [!NOTE]
> The servicing details were prepared with GitHub Copilot.

Co-authored-by: Aaron R Robinson <arobins@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ee70b0a5-4f16-4838-9d10-fe9924d6864c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Casting a second COM RCW to IEnumerator throws NullReferenceException in __ComObject.IEnumeratorOverEnumVARIANTImpl.MoveNext

3 participants