Skip to content

[release/10.0] Fix crash on calling COM interface method on a class where a derived class overriding it was created first#128796

Open
elinor-fung wants to merge 2 commits into
dotnet:release/10.0from
elinor-fung:com-virt-crash-rel
Open

[release/10.0] Fix crash on calling COM interface method on a class where a derived class overriding it was created first#128796
elinor-fung wants to merge 2 commits into
dotnet:release/10.0from
elinor-fung:com-virt-crash-rel

Conversation

@elinor-fung
Copy link
Copy Markdown
Member

@elinor-fung elinor-fung commented May 30, 2026

See #127512

When a COM interface is implemented by a class with a virtual method and a derived class overrides it, they share a ComMethodTable. We then use GetSlotForVirtual to resolve to the correct target for a method. With #101580, the slot for the base class is lazily populated. If the derived class is used first it creates a ComMethodTable and lays it out, which makes sure the slots for that class' method table are restored. When base class is then used, it shares the same ComMethodTable which is already laid out, so it does not explicitly restore its slots, leaving the slot for the base class method null.

This change makes laying out a ComMethodTable also restore parent vtable slots. I did this directly against release/10.0, as how we do this dispatch is quite different in .NET 11. It doesn't crash anymore, but we do still have a correctness issue there.

cc @dotnet/appmodel @AaronRobinsonMSFT

Example setup:

[ComVisible(true)]
public interface IFoo
{
    void DoWork();
}
 
[ComVisible(true), ComDefaultInterface(typeof(IFoo))]
public class Base : IFoo
{
    public virtual void DoWork() { }
}
 
[ComVisible(true), ComDefaultInterface(typeof(IFoo))]
public class Derived : Base
{
    public override void DoWork() { }
}

If Derived is created first, calling Base.DoWork crashes.

elinor-fung and others added 2 commits May 29, 2026 15:57
When a ComMethodTable for a COM-visible interface is shared between a
base class and a derived class (via ImplementsInterfaceWithSameSlotsAsParent),
the virtual dispatch in COMToCLRGetObjectAndTarget_Virtual reads the managed
vtable slot on the actual object's MethodTable via GetSlotForVirtual. Since
temporary entry points are lazily allocated, the parent's vtable slot may
still be NULL if the method was never called from managed code, causing an
access violation.

Fix by eagerly restoring the vtable slot on parent MethodTables during
LayOutInterfaceMethodTable, so that GetSlotForVirtual never returns NULL
during COM dispatch.

Fixes dotnet#127512

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Test that COM-to-CLR dispatch through a shared ComMethodTable correctly
resolves virtual method overrides. Uses separate type sets to independently
validate both orderings (derived-first and base-first) within the same
process, and verifies the correct method implementation is called.

Regression test for dotnet#127512

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Backport to release/10.0 of a fix for a regression introduced by lazy temporary-entrypoint allocation (#101580). When a COM-visible base class and a derived class that overrides one of its virtual methods share a ComMethodTable, creating the derived class first laid out the shared ComMethodTable without ever forcing the base class's vtable slot to be restored. A subsequent COM call on the base instance then dispatched through a NULL slot and crashed. The fix walks parent MethodTables while laying out the interface method table and calls GetRestoredSlot so the base-class slot is materialized.

Changes:

  • In ComMethodTable::LayOutInterfaceMethodTable, for each virtual class method bound to an interface slot, walk up the parent chain and call GetRestoredSlot(slot) for every parent that still defines that virtual.
  • Adds a new COM interop test (VirtualMethodOverrideTest) with both derived-first and base-first call orders to guard against the regression.
Show a summary per file
File Description
src/coreclr/vm/comcallablewrapper.cpp Restore parent vtable slots when laying out a shared ComMethodTable to prevent NULL COM dispatch slots.
src/tests/Interop/COM/VirtualMethodOverride/VirtualMethodOverrideTest.cs New xUnit tests covering both ordering scenarios for COM-visible base/derived classes sharing an interface.
src/tests/Interop/COM/VirtualMethodOverride/VirtualMethodOverrideTest.csproj Test project wiring (process-isolated, NativeAOT-incompatible).

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants