[TrimmableTypeMap] Emit alias-base entry as unconditional for now#11220
Conversation
The alias-group base entry (e.g. java/lang/String -> alias holder) was emitted as a 3-arg conditional TypeMap attribute regardless of the ForceUnconditionalEntries workaround for dotnet/runtime#127004. When that workaround is in effect, the indexed alias entries are 2-arg (unconditional) and the alias holder is reachable only via TypeMapAssociation. Per #127004 the trimmer strips the association when a TypeMap entry references the same type, removing the holder and making the dictionary key 'java/lang/String' (and 'java/lang/Object', 'java/lang/Throwable', ...) disappear at runtime. The hierarchy walker then advances past the most-derived JNI class and CreatePeer falls back to the targetType-based lookup, returning a base-class peer. Concrete symptom (verified on device): GetObject_ReturnsMostDerivedType hierarchy: jni='java/lang/String' targetType='Java.Lang.Object' proxyCount=0 hierarchy: jni='java/lang/Object' targetType='Java.Lang.Object' proxyCount=0 -> Expected Java.Lang.String but was Java.Lang.Object Route the alias-base entry through the same unconditional decision used by BuildEntry: emit it as 2-arg whenever ForceUnconditionalEntries is on, the JNI name is in EssentialRuntimeTypes, or any peer in the alias group already needs to be unconditional. After the fix the same lookup reports proxyCount=2 and selects Java.Lang.String, and the test (and the full Mono.Android.NET-Tests trimmable suite) passes with 0 failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes incorrect most-derived Java peer resolution when using the trimmable typemap by ensuring the alias-group base TypeMap entry is emitted as unconditional when required, preventing the trimmer from stripping the alias holder mapping (workaround for dotnet/runtime#127004). It also re-enables the previously excluded runtime test that validates most-derived type selection.
Changes:
- Update
ModelBuilder.EmitPeersto emit the alias-base entry as 2-arg/unconditional under the same conditions as indexed entries (notably whenForceUnconditionalEntriesapplies). - Remove the trimmable-typemap exclusion for
Java.LangTests.ObjectTest.GetObject_ReturnsMostDerivedType.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs |
Aligns alias-base entry emission with unconditional/conditional decision logic to avoid alias holder trimming and missing base JNI keys at runtime. |
tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs |
Removes an exclusion so the most-derived-type regression test runs under the trimmable typemap. |
| bool aliasBaseUnconditional = ForceUnconditionalEntries | ||
| || EssentialRuntimeTypes.Contains (jniName) | ||
| || peersForName.Any (IsUnconditionalEntry); | ||
| model.Entries.Add (new TypeMapAttributeData { | ||
| JniName = jniName, | ||
| ProxyTypeReference = holderRef, | ||
| TargetTypeReference = holderRef, | ||
| TargetTypeReference = aliasBaseUnconditional ? null : holderRef, | ||
| }); |
There was a problem hiding this comment.
The new alias-base unconditional decision (aliasBaseUnconditional) is a key trimming workaround, but there doesn’t appear to be a unit/integration test asserting that the base alias entry (e.g., "test/Dup" → holder) is emitted as a 2-arg/unconditional TypeMap attribute (i.e., TargetTypeReference == null) when ForceUnconditionalEntries is enabled (and/or when any peer in the alias group is unconditional). Adding a regression test for this would help prevent reintroducing the original issue where only the indexed entries honored ForceUnconditionalEntries.
1c03ddc
into
dev/simonrozsival/trimmable-test-plumbing
Summary
Fixes
Java.LangTests.ObjectTest.GetObject_ReturnsMostDerivedTypeunder the trimmable typemap.Java.Lang.Object.GetObject<Java.Lang.Object>(jstring)was returning aJava.Lang.Objectpeer instead of the most-derivedJava.Lang.String.Root cause
The alias-group base TypeMap entry (e.g.
java/lang/String→ alias holder) inModelBuilder.EmitPeerswas always emitted as a 3-arg conditional attribute, bypassing theForceUnconditionalEntriesworkaround for dotnet/runtime#127004 that the indexed entries already use.When that workaround is in effect, the indexed alias entries are 2-arg (unconditional) and the alias holder is reachable only via
TypeMapAssociation. Per #127004 the trimmer strips the association when a TypeMap entry references the same type, removing the holder and making the dictionary keyjava/lang/String(andjava/lang/Object,java/lang/Throwable, …) disappear at runtime. The hierarchy walker then advances past the most-derived JNI class andCreatePeerfalls back to the targetType-based lookup, returning a base-class peer.Concrete symptom (verified on device with diagnostic logging):
Fix
Route the alias-base entry through the same unconditional decision used by
BuildEntry: emit it as 2-arg wheneverForceUnconditionalEntriesis on, the JNI name is inEssentialRuntimeTypes, or any peer in the alias group is itself unconditional.After the fix, the same lookup reports
proxyCount=2and selectsJava.Lang.String. Test exclusion removed in the same change.Verification
Full
Mono.Android.NET-TestsRelease+CoreCLR+Trimmable run on device: 917 tests, 0 failures, 0 errors.