Skip to content

[ILLink/ILCompiler] Fix crash when array types are used as TypeMap trim targets#126380

Merged
jkoritzinsky merged 16 commits intomainfrom
copilot/fix-array-type-map-build-failure
Apr 20, 2026
Merged

[ILLink/ILCompiler] Fix crash when array types are used as TypeMap trim targets#126380
jkoritzinsky merged 16 commits intomainfrom
copilot/fix-array-type-map-build-failure

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 31, 2026

PublishTrimmed crashes with NotSupportedException: TypeDefinition cannot be resolved from 'Mono.Cecil.ArrayType' when a TypeMap attribute specifies an array type as its trim target (e.g., typeof(Foo[])).

Root cause (ILLink): LinkContext.Resolve throws for any TypeSpecification that isn't a GenericInstanceType. Array types, pointer types, and byref types all fall into this category.

Root cause (ILCompiler): ExternalTypeMapNode.GetConditionalStaticDependencies used context.NecessaryTypeSymbol(trimmingTargetType) as the condition node. When the trim target is Foo[], the TypeMap entry should be present whenever Foo (the element type) is reachable — but NecessaryTypeSymbol(Foo[]) is a different node from NecessaryTypeSymbol(Foo) and is only transitively marked when Foo[] itself is used. If code only uses Foo (e.g., new Foo()), the condition never fires and the entry is silently dropped.

Description

ILLink (TypeMapHandler.cs):

  • TypeMapHandler.AddExternalTypeMapEntry: Strip non-resolvable TypeSpecification wrappers (array, pointer, byref, etc.) from the trim target before passing to RecordTypeMapEntry, using the element type instead.
  • TypeMapHandler.AddProxyTypeMapEntry: Same fix applied symmetrically to the source type parameter. Corrected the comment to accurately state that TypeMapAssociationAttribute has two constructor type arguments (source and proxy).
  • TypeMapHandler.MarkTypeMapAttribute: Same TypeSpecification-stripping fix applied before calling _context.Resolve(targetType).
  • TypeMapHandler.UnwrapToResolvableType: The three previously duplicate stripping loops have been extracted into a single private static TypeReference UnwrapToResolvableType(TypeReference) helper, so future fixes only need to be made in one place. All call sites use idiomatic C# property pattern matching (TypeSpecification { ElementType: var elementType }) to avoid explicit casts.

ILCompiler (ExternalTypeMapNode.cs):

  • ExternalTypeMapNode.GetEffectiveTrimTargetType: New private static helper that strips ParameterizedType wrappers (arrays, pointers, byrefs) to reach the element type. InstantiatedType (generics like List<T>) does not extend ParameterizedType and is correctly left alone.
  • ExternalTypeMapNode.GetConditionalStaticDependencies and GetMarkedEntries: Updated to strip the trim target type via GetEffectiveTrimTargetType and use NecessaryTypeSymbol on the resulting element type. This matches ILLink's stripping behavior: the TypeMap entry is included whenever the element type is reachable (via new Foo(), is Foo casts, array construction new Foo[n], etc.), regardless of whether Foo[] itself is explicitly used.
// Before fix: crashes (ILLink) or silently drops entry (ILCompiler)
[assembly: TypeMap<TestTypeMap>("Key", typeof(SomeTarget), typeof(Foo[]))]

// After fix (ILLink): strips Foo[] → Foo, uses Foo as the trim dependency
// After fix (ILCompiler): strips Foo[] → Foo, uses NecessaryTypeSymbol(Foo) as the condition
// The TypeMap entry is included iff Foo (the element type) is trimmer-reachable

Both fixes handle nested arrays (Foo[][]Foo), pointer types (Foo*[]Foo), multi-dimensional arrays (Foo[,]Foo), and mixed cases like List<Foo>[] (strips to List<Foo>, which is not a ParameterizedType).

Customer Impact

PublishTrimmed builds fail with an unhandled NotSupportedException / fatal ILLink error when any assembly attribute uses an array type as a TypeMap trim target. In ILCompiler (PublishAot), the TypeMap entry for an array trim target whose element type is reachable is silently dropped. No workaround short of removing the array type.

Regression

No — this is a new feature (TypeMap) that never handled array trim targets correctly in either ILLink or ILCompiler.

Testing

Added test cases to TypeMap.cs (ILLink / ILCompiler trimming tests):

  • TrimTargetIsUsedArrayType: array trim target whose element type is reachable (via new ArrayTypeTrimTargetClass()) — attribute must be preserved; verified with KeptAttributeAttribute.
  • TrimTargetIsUnusedArrayType: array trim target whose element type is unreachable — attribute and target types are removed; verified with explicit RemovedTypeInAssembly("test", ...) assertions on both ArrayTypeTrimTargetUnusedClass and ArrayTypeTrimTargetUnusedTarget.

Added TestInteropMapArrayTrimming to src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs (NativeAOT smoke test):

  • 7 TypeMap entries (A–G) with array trim targets covering T[], T[,], and T*[] variants.
  • MakeGenerics<T>() is a generic method that constructs TrimTarget1 [1], TrimTarget2 [1,1], TrimTarget3 *[1], and TrimTarget6[1], invoked via typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)).MakeGenericMethod([GetAtom()]).Invoke(null, []). Entry G is rooted via an is TrimTarget7[] cast.
  • Atom class and GetAtom() method are present to provide the generic type argument for the reflection-based call.
  • Struct declarations TrimTarget1 through TrimTarget4 use trailing spaces before the semicolon (e.g., TrimTarget1 ;) to visually distinguish the array-targeted trim types from the non-array ones.
  • Verifies entries A, B, C, F, G are present in the map using TryGetValue; verifies entries D, E are absent (element type unreachable) also using TryGetValueContainsKey is not used because TypeMapLazyDictionary throws NotSupportedException for that method.

Both existing Reflection.TypeMap and Reflection.TypeMapEntryAssembly tests pass.

Risk

Low. The ILLink change is localized to TypeMapHandler — three call sites and one new private static helper. The ILCompiler change is localized to ExternalTypeMapNode — two methods updated and one new private static helper. The NativeAOT smoke test addition is additive only. No behavior change for existing valid (non-array) trim targets.

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.

Strip parameterized types (array, pointer, etc.) from TypeMap trim targets
and source types, using the element type instead. This fixes the crash
when typeof(SomeType[]) is used as a trim target in TypeMap attributes
during PublishTrimmed.

Add test cases for array type trim targets (used and unused) to TypeMap.cs.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/82e75422-6094-47d8-be6f-7a4527457908

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
@dotnet-policy-service dotnet-policy-service Bot added the linkable-framework Issues associated with delivering a linker friendly framework label Mar 31, 2026
Copilot AI changed the title [WIP] Fix build failure for array TypeMap entries when PublishTrimmed [ILLink] Fix crash when array types are used as TypeMap trim targets with PublishTrimmed Mar 31, 2026
Copilot AI requested a review from jkoritzinsky March 31, 2026 23:01
@jkoritzinsky jkoritzinsky marked this pull request as ready for review March 31, 2026 23:34
@jkoritzinsky jkoritzinsky requested a review from sbomer as a code owner March 31, 2026 23:34
Copilot AI review requested due to automatic review settings March 31, 2026 23:34
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

Fixes an ILLink crash in PublishTrimmed when TypeMap attributes specify non-resolvable TypeSpecification types (notably arrays) as trim targets by stripping wrappers down to a resolvable element type before calling LinkContext.Resolve.

Changes:

  • Normalize TypeMap trim targets and association source types by unwrapping TypeSpecification (array/pointer/byref/…) to an element type until reaching a TypeDefinition or GenericInstanceType.
  • Add new linker test coverage for an array-type trim target scenario (kept case), and introduce a second unused-case scenario that should assert removal.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/tools/illink/src/linker/Linker/TypeMapHandler.cs Unwraps TypeSpecification wrappers before resolving trim targets / proxy source types to avoid NotSupportedException crashes.
src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs Adds test inputs for array-type trim targets and ensures at least the reachable-element case is preserved.

Comment thread src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs
Comment thread src/tools/illink/src/linker/Linker/TypeMapHandler.cs Outdated
@jkoritzinsky
Copy link
Copy Markdown
Member

@copilot please address the feedback from @copilot and validate test changes

- Add RemovedTypeInAssembly("test", ...) for unused array trim target types
  to make the expected removal behavior explicit
- Fix misleading comment in AddProxyTypeMapEntry: TypeMapAssociationAttribute
  has two constructor type arguments (source and proxy), not one

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/8d0dbb63-91fb-4d87-8ccd-60e91da2e1e9

Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 1, 2026

@copilot please address the feedback from @copilot and validate test changes

Done in af0ac93:

  1. Added explicit RemovedTypeInAssembly("test", typeof(ArrayTypeTrimTargetUnusedClass)) and RemovedTypeInAssembly("test", typeof(ArrayTypeTrimTargetUnusedTarget)) assertions on the TypeMap class so the expected removal is explicitly verified by the test runner.
  2. Fixed the misleading comment in AddProxyTypeMapEntry — it now says "two constructor type arguments (source and proxy)" instead of "single type argument".

Both Reflection.TypeMap and Reflection.TypeMapEntryAssembly tests still pass.

- TrimTarget1-4 struct declarations now use trailing spaces: TrimTarget1   ;
- Array constructors now match: new TrimTarget1   [1], new TrimTarget2   [1,1], new TrimTarget3   *[1]
- Closing bracket of return array moved to match: ];  (with leading whitespace)

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7cdf5296-b91d-4997-90ed-3fe071042c48

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 15, 2026 03:26
Copilot AI review requested due to automatic review settings April 15, 2026 03:37
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@github-actions

This comment has been minimized.

TypeMapLazyDictionary.ContainsKey throws NotSupportedException.
Use TryGetValue (which is properly supported) to check that
entries D and E are absent from the map.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/b5e7b52a-b2ba-4366-bb40-7b1961844e70

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
@MichalStrehovsky
Copy link
Copy Markdown
Member

This fixes the correctness issue, but doesn't address #126177 (comment) the optimization on native AOT we could do if the element is a valuetype. I've set up the testing to ensure we have coverage of the areas off the top of my head where there are dragons (I expect the tests to fail on the first try at optimizing this).

Do we need the optimization too? If so, we need to add more tests that target the optimization and implement it. Cc @Sergio0694

@Sergio0694
Copy link
Copy Markdown
Contributor

I mean, if we have to root all this stuff for reference types and take the size regression, it would surely be very welcome to get that optimization to at least try to minimize this for all projected structs/enum types, if possible, yes 😅

Copy link
Copy Markdown
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Native AOT part LGTM. We should do the possible optimization separately.

Copilot AI review requested due to automatic review settings April 20, 2026 02:46
@jkoritzinsky jkoritzinsky enabled auto-merge (squash) April 20, 2026 02:47
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@github-actions
Copy link
Copy Markdown
Contributor

test

Generated by Code Review for issue #126380 ·

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

Labels

area-TypeSystem-coreclr linkable-framework Issues associated with delivering a linker friendly framework

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[area-Tools-ILLink]: TypeMap entries for array types result in build failure when PublishTrimmed

5 participants