microsoft / microsoft/CsWinRT

AOT optimizer emits unqualified generic type arguments in GlobalVtableLookup, so the entries never match Type.ToString()

Open
#2,536 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
665
Forks
134
Avg merge
1d 3h
Merged PRs (30d)
32

Description

Summary

The AOT optimizer's generated GlobalVtableLookup compares type.ToString() against string literals, but it emits those literals using a display name for generic type arguments rather than the metadata name. When the argument's namespace is imported with a using at the instantiation site, the emitted key is unqualified and can therefore never match — the lookup silently misses, the object gets no vtable entries, and the call fails at runtime.

Configuration

  • Microsoft.Windows.CsWinRT 2.3.1 (generated header says cswinrt.exe version 2.3.1.260716.1)
  • .NET 10, UseUwp=true, NativeAOT, CsWinRTAotWarningLevel=3

What is generated

private static ComWrappers.ComInterfaceEntry[] LookupVtableEntries(Type type)
{
    string typeName = type.ToString();
    if (typeName == "..."

Type.ToString() for a constructed generic always yields fully-qualified arguments, e.g.
Telegram.Collections.SortedObservableCollection1[Telegram.Td.Api.ConnectedWebsite]`.

But the emitted literals are not consistently qualified. From one build of our app:

"Telegram.Collections.SortedObservableCollection`1[ChatMember]"
"Telegram.Collections.SortedObservableCollection`1[ConnectedWebsite]"
"Telegram.Collections.SortedObservableCollection`1[GroupCallMessage]"
"Telegram.Collections.SortedObservableCollection`1[Telegram.Td.Api.GroupCallMessage]"
"Telegram.Collections.SortedObservableCollection`1[Telegram.Td.Api.User]"

The GroupCallMessage pair is the whole bug in two lines: the same constructed type registered twice,
unqualified by the optimizer's own discovery and qualified by an explicit
[assembly: GeneratedWinRTExposedExternalType(typeof(...))]. Only the second one ever matches.

A single entry can even mix the two, which shows the string is a display name rather than a metadata name:

"System.Collections.Generic.Dictionary`2[System.Type,Animation]"

The instantiation site is new SortedObservableCollection<ConnectedWebsite>(...) in a file that has
using Telegram.Td.Api;. Writing the argument fully qualified at that site, or declaring the type via
GeneratedWinRTExposedExternalType, produces a matching entry.

Impact

The failure is silent at build time and only shows up when the object crosses the ABI — for us, assigning
such a collection to ItemsSource, which surfaces as E_INVALIDARG (and on a DispatcherQueue, as a
fail-fast rather than a catchable exception). Nothing in the build output indicates the entry is dead.

In our app 38 of 804 generic registrations are in this state; after discarding open generics and tuple
artefacts, 10 are real instantiations that are reachable at runtime.

Suggested fix

Emit the type-argument names the same way Type.ToString() renders them — the metadata name, unaffected
by using directives at the instantiation site — so that the generated comparison can match. Comparing on
something less brittle than a formatted string (a RuntimeTypeHandle, or Type equality) would remove the
class of bug entirely.

Detecting it in an existing app

Scanning the generated WinRTGlobalVtableLookup.g.cs for registrations that exist only with an
unqualified type argument — i.e. no fully-qualified sibling for the same constructed type — finds them
mechanically, and every hit is a real one.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the generated WinRTGlobalVtableLookup.g.cs and the GlobalVtableLookup generation path; compare generic-argument rendering for optimizer discovery versus GeneratedWinRTExposedExternalType registrations. Done means generated keys use the qualified form expected by Type.ToString() and affected constructed types resolve vtable entries at runtime; no regression test is named.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
build-system, desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.