Skip to content

ILLink: Marking a type forwarder should also mark its target type #126583

@sbomer

Description

@sbomer

Note

This issue was filed with AI (Copilot) assistance.

Currently, when ILLink marks an ExportedType (type forwarder) row — whether through rooting an assembly with -a, or through copy/save action — it preserves the forwarder metadata but does not mark the target TypeDefinition the forwarder resolves to. This means the forwarder can become dangling, pointing to a type or assembly that was trimmed away.

This is tested and documented as expected behavior:

// From RootAllLibraryBehavior.cs:
// Type forwarders are kept (but not necessarily the type definition that the forwarder points to)
[KeptTypeInAssembly("library.dll", typeof(RootAllLibrary_ExportedType))]
[RemovedAssembly("exportedtype.dll")]

The same behavior applies for copy action assemblies (RootAllLibraryCopyBehavior.cs, UnusedForwarderWithAssemblyCopyIsKept.cs).

Why the current behavior may be correct

Shim assemblies like mscorlib.dll and netstandard.dll are composed almost entirely of type forwarders pointing to dozens of implementation assemblies. If marking a forwarder also marked its target, rooting one of these assemblies would pull in a large portion of the .NET surface area — defeating the purpose of trimming.

Some forwarder targets may not even be resolvable. The mscorlib shim forwards to assemblies like System.Drawing.Common, System.Data.SqlClient, and System.Security.Permissions that may not be present in the app. This is an established pattern that --skip-unresolved true was designed to accommodate (see #103831).

Why it might be a problem

A dangling forwarder is broken at runtime — if any code attempts to load a type through the forwarder, it will fail. Issue #99592 reported a real-world case where rooting assemblies with forwarded types caused runtime failures because the forwarder targets were trimmed. @filipnavara also noted a production scenario with System.Windows.Forms serialized resources depending on forwarders being functional after trimming.

Questions for discussion

  1. Is the current behavior (dangling forwarders are acceptable) the right default?
  2. Should there be an opt-in mechanism to mark forwarder targets? For example, a per-assembly or per-forwarder option to say "keep this forwarder and its target."
  3. Should ILLink warn when a marked forwarder's target is not marked, so users can take action?

Related

Metadata

Metadata

Assignees

Labels

area-Tools-ILLink.NET linker development as well as trimming analyzers

Type

No type
No fields configured for issues without a type.

Projects

Status

No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions