You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ILLink (Mono.Linker) crashes with System.NotSupportedException when an assembly contains [assembly: TypeMap<TGroup>(jniName, typeof(T[]), typeof(T[]))] (or any TypeMapAttribute whose Type argument is a closed array type). The same code works correctly without trimming and partially works under NativeAOT, so the failure is specific to the ILLink trimmer.
The crash happens in Mono.Linker.TypeMapHandler.RecordTypeMapEntry when it calls LinkContext.Resolve on the dependency-source slot (3-arg form) or in MarkTypeMapAttribute on the target-type slot (2-arg form). Cecil's ArrayType is not a TypeDefinition, so Resolve throws.
This blocks the design pattern where a TypeMap entry maps a JNI-style array name ([Ljava/lang/String;, [[Ljava/lang/String;) to the corresponding closed managed array type — a pattern we want to use in dotnet/android's trimmable typemap to eliminate ~2000 per-TJavaPeerContainerFactory<T> instantiations under NativeAOT (see dotnet/android#11234, dotnet/android#11238).
> dotnet run
System.String
System.String[]
System.String[][]
With ILLink trimming — crashes
> dotnet publish -p:PublishTrimmed=true
ILLink : error IL1012: IL Trimmer has encountered an unexpected error.
Fatal error in IL Linker
Unhandled exception. System.NotSupportedException: TypeDefinition cannot be resolved from 'Mono.Cecil.ArrayType' type
at Mono.Linker.LinkContext.Resolve(TypeReference typeReference)
at Mono.Linker.TypeMapHandler.RecordTypeMapEntry(ValueTuple`2 attr, TypeReference group, TypeReference dependencySource, …)
at Mono.Linker.TypeMapHandler.AddExternalTypeMapEntry(TypeReference group, ValueTuple`2 attr)
at Mono.Linker.TypeMapHandler.TypeMapResolver.Resolve(LinkContext context, TypeMapHandler manager)
at Mono.Linker.TypeMapHandler.Initialize(…)
at Mono.Linker.Steps.MarkStep.Initialize()
at Mono.Linker.Steps.MarkStep.Process(LinkContext context)
at Mono.Linker.Pipeline.Process(LinkContext context)
at Mono.Linker.Driver.Run(ILogger customLogger)
NETSDK1144: Optimizing assemblies for size failed.
With NativeAOT — succeeds (2/3 entries kept; rank-2 dropped because not live)
> dotnet publish -p:PublishAot=true
> bin/Release/net11.0/osx-arm64/publish/TestTypeMapArrays
System.String
System.String[]
Not found
NativeAOT correctly keeps string and string[] (referenced by the parameter to X(string[] args)), and drops string[][] (not referenced anywhere) — exactly the conditional-trim behaviour the 3-arg form is meant to provide.
Expected behavior
ILLink should accept TypeMapAttribute arguments whose Type token is a closed array (SZARRAY or ARRAY element type). Specifically:
MarkTypeMapAttribute (target-type slot) and RecordTypeMapEntry (dependency-source slot) should not crash when the TypeReference is a Mono.Cecil.ArrayType.
Live-ness analysis should mirror NativeAOT: an array Type token is "live" iff its element type is reachable from user code (matches the rank-2 drop in the NativeAOT output above).
Environment
.NET SDK: 11.0.100-preview.3 (also reproduced on preview.4.26215.121)
RecordTypeMapEntry: when dependencySource is an ArrayType, walk down to the element TypeReference and use that for the Resolve call (and the dependency tracking) — the array Type itself doesn't have a TypeDefinition, but its element does, and "element type is live" is the correct trim condition for typeof(T[]) to be kept.
MarkTypeMapAttribute: same treatment for the target-type slot — null-check after Resolve is already there, but Resolve throws before reaching it.
Note
This issue was created with the help of Copilot.
Description
ILLink(Mono.Linker) crashes withSystem.NotSupportedExceptionwhen an assembly contains[assembly: TypeMap<TGroup>(jniName, typeof(T[]), typeof(T[]))](or anyTypeMapAttributewhoseTypeargument is a closed array type). The same code works correctly without trimming and partially works under NativeAOT, so the failure is specific to the ILLink trimmer.The crash happens in
Mono.Linker.TypeMapHandler.RecordTypeMapEntrywhen it callsLinkContext.Resolveon the dependency-source slot (3-arg form) or inMarkTypeMapAttributeon the target-type slot (2-arg form). Cecil'sArrayTypeis not aTypeDefinition, soResolvethrows.This blocks the design pattern where a
TypeMapentry maps a JNI-style array name ([Ljava/lang/String;,[[Ljava/lang/String;) to the corresponding closed managed array type — a pattern we want to use indotnet/android's trimmable typemap to eliminate~2000per-TJavaPeerContainerFactory<T>instantiations under NativeAOT (see dotnet/android#11234, dotnet/android#11238).Reproduction
Program.cs:TestTypeMapArrays.csprojtargetsnet11.0.Without trimming — works
With ILLink trimming — crashes
With NativeAOT — succeeds (2/3 entries kept; rank-2 dropped because not live)
NativeAOT correctly keeps
stringandstring[](referenced by the parameter toX(string[] args)), and dropsstring[][](not referenced anywhere) — exactly the conditional-trim behaviour the 3-arg form is meant to provide.Expected behavior
ILLink should accept
TypeMapAttributearguments whoseTypetoken is a closed array (SZARRAYorARRAYelement type). Specifically:MarkTypeMapAttribute(target-type slot) andRecordTypeMapEntry(dependency-source slot) should not crash when theTypeReferenceis aMono.Cecil.ArrayType.Typetoken is "live" iff its element type is reachable from user code (matches the rank-2 drop in the NativeAOT output above).Environment
11.0.100-preview.3(also reproduced onpreview.4.26215.121)Microsoft.NET.ILLink.Tasks:11.0.0-preview.3.26209.109/11.0.0-preview.4.26215.121Suggested fix
In
Mono.Linker.TypeMapHandler:RecordTypeMapEntry: whendependencySourceis anArrayType, walk down to the elementTypeReferenceand use that for theResolvecall (and the dependency tracking) — the array Type itself doesn't have aTypeDefinition, but its element does, and "element type is live" is the correct trim condition fortypeof(T[])to be kept.MarkTypeMapAttribute: same treatment for the target-type slot — null-check afterResolveis already there, butResolvethrows before reaching it.