[TrimmableTypeMap] Remove ForceUnconditionalEntries workaround#11345
Open
simonrozsival wants to merge 14 commits into
Open
[TrimmableTypeMap] Remove ForceUnconditionalEntries workaround#11345simonrozsival wants to merge 14 commits into
simonrozsival wants to merge 14 commits into
Conversation
Restore conditional TypeMap entries for non-essential MCW bindings now that the runtime trimmer issue has been fixed. Co-authored-by: Copilot <[email protected]>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR removes the ForceUnconditionalEntries workaround from trimmable typemap model generation, restoring conditional 3-arg TypeMapAttribute emission for non-essential MCW bindings while keeping essential runtime/user ACW entries unconditional.
Changes:
- Removes the global force-unconditional switch from
ModelBuilder. - Restores conditional target references for non-essential MCW entries.
- Updates model and PE blob tests to expect restored 3-arg behavior.
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 |
Removes workaround logic and reverts unconditional decisions to IsUnconditionalEntry() plus alias-specific rules. |
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs |
Updates assertions and test names/comments for restored conditional MCW typemap entries. |
…e name collision - Replace [DynamicDependency] attributes on FindX509TrustManager with a HackToPreserveInvokers method using [MethodImpl(NoInlining)] to prevent the linker from trimming IX509TrustManagerInvoker and X509ExtendedTrustManagerInvoker. - Remove obsolete API 21-23 Conscrypt TrustManagerImpl workaround. - Rename generated enum from AppFunctionState to AppFunctionEnabledState to avoid name collision with the new Android API 37 android.app.appfunctions.AppFunctionState class. Co-authored-by: Copilot <[email protected]>
The isinst instruction properly preserves conditional TypeMap entries for interfaces through the MarkType -> MarkRequirementsForInstantiatedTypes -> ProcessType chain in ILLink. The typeof() and [DynamicDependency] additions were based on an incorrect analysis that ProcessType was skipped for interfaces - while true in the isinst handler's inner switch, MarkType itself calls MarkRequirementsForInstantiatedTypes for interfaces which calls ProcessType without any interface skip. Verified with both ILLink and NativeAOT using a multi-assembly repro that mirrors the Android typemap assembly layout. Co-authored-by: Copilot <[email protected]>
When the getSuperclass() class hierarchy walk fails to find a targetType-compatible TypeMap proxy (e.g., because an intermediate class entry like X509ExtendedTrustManager was trimmed), fall back to walking Java interfaces via getInterfaces() at each level of the class hierarchy. This allows the runtime to find TypeMap entries for Java interfaces (e.g., javax/net/ssl/X509TrustManager) that are preserved by the trimmer but unreachable via getSuperclass() alone. The interface walk only runs when targetType is an interface, keeping the common path (class-based resolution) unchanged. Also adds a device test TrustManagerFactory_GetTrustManagers_ReturnsIX509TrustManager that verifies TrustManagerFactory.GetTrustManagers() returns elements that can be cast to IX509TrustManager. This test runs on both llvm-ir and trimmable typemap paths. Co-authored-by: Copilot <[email protected]>
…es method ID - Check interfaces inline during the getSuperclass() walk instead of a separate second pass, avoiding redundant GetObjectClass/getSuperclass calls - Cache the JNI method ID for getInterfaces() in a static field - Rename TryGetProxyFromInterfacesOfClass to TryMatchInterfaces Co-authored-by: Copilot <[email protected]>
…aces scope Co-authored-by: Copilot <[email protected]>
…ound The [DynamicDependency] attributes on FindX509TrustManager are needed by both llvm-ir and trimmable typemap paths to preserve invoker types. Without them, the llvm-ir linker also trims X509ExtendedTrustManager entries, causing the same IX509TrustManager resolution failure. Removed the API 21-23 Conscrypt TrustManagerImpl workaround since minimum supported API level is now higher. Co-authored-by: Copilot <[email protected]>
Address review feedback: add coverage that all-MCW alias groups emit a conditional (3-arg) base alias-holder entry, mixed ACW/MCW alias groups stay unconditional (2-arg), and essential runtime types remain unconditional regardless of peer types. Co-authored-by: Copilot <[email protected]>
Replace raw JNIEnv.GetMethodID/CallObjectMethod/GetArrayLength/ GetObjectArrayElement/DeleteLocalRef with their JniEnvironment equivalents (JniMethodInfo, JniObjectReference, JniEnvironment.Arrays, JniEnvironment.InstanceMethods). This matches the style of the surrounding hierarchy walk code and uses proper ref disposal via JniObjectReference.Dispose. Co-authored-by: Copilot <[email protected]>
Wrap the JNI class handle in Java.Lang.Class with DoNotTransfer ownership and call the existing GetInterfaces() binding. This reuses the JniPeerMembers method ID caching, thread-safe lookup, and JNI remapping that the generated binding already handles. Co-authored-by: Copilot <[email protected]>
… test - Use DoNotTransfer | DoNotRegister when wrapping JNI class handles in Java.Lang.Class to avoid peer table registration overhead - Remove extra blank line before GetProxyForJavaObject - Remove verbose diagnostic logging from device test, keep only the assertion with descriptive failure message - Remove unused Log import from test - Keep hierarchy walk using JniObjectReference (not Java.Lang.Class) to avoid potential recursion into TypeMap during class resolution Co-authored-by: Copilot <[email protected]>
Java.Lang.Class.GetInterfaces() returns Java.Lang.Class[] where each element holds a global ref. Dispose them in a finally block to avoid leaking grefs until GC collection. Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Remove the
ForceUnconditionalEntriesworkaround from the trimmable typemap generator and fix the underlying runtime type resolution gap that it was masking.Background
ForceUnconditionalEntrieswas a temporary workaround for dotnet/runtime#127004 that forced allTypeMapentries to be emitted as 2-arg (unconditional), preventing the trimmer from removing unused framework bindings. Removing it restores 3-arg conditional entries, enabling proper trimming of unused MCW bindings.The problem it was masking
With conditional entries enabled,
TrustManagerFactory.GetTrustManagers()started returningITrustManagerInvokerinstead ofIX509TrustManagerInvokerin the trimmable typemap path, causing 5 SSLServerCertificateCustomValidationCallbacktests to fail.Root cause: Both the llvm-ir and trimmable typemap runtimes resolve Java objects to managed types by walking the Java class hierarchy via
getSuperclass()— neither checks Java interfaces. The concrete trust manager's class hierarchy is:The llvm-ir path works because its custom trimming preserves
javax/net/ssl/X509ExtendedTrustManager(a Java class in the hierarchy). When the hierarchy walk encounters it, it resolves toX509ExtendedTrustManagerInvokerwhich implementsIX509TrustManager.The trimmable path's ILLink-based trimming removes
X509ExtendedTrustManagerbecause nothing in managed code references it. This creates a gap: the hierarchy walk goes from the vendor class directly tojava/lang/Objectwithout finding a match. Thejavax/net/ssl/X509TrustManagerTypeMap entry IS preserved (becauseisinst IX509TrustManagerinFindX509TrustManagerkeeps it alive), but it's unreachable —getSuperclass()never returns interfaces.The fallback
TryGetProxyFromTargetTypelooks uptypeof(ITrustManager)(the array element type requested by the marshalling code), which createsITrustManagerInvoker— andis IX509TrustManagerfails.The fix
Add a Java interface walk fallback to
TryGetProxyFromHierarchy. WhentargetTypeis an interface, at each level of thegetSuperclass()walk, also check the Java interfaces viaJava.Lang.Class.GetInterfaces()(which handles method ID caching and JNI remapping internally). SincegetInterfaces()only returns directly declared interfaces (not transitive), we recurse into super-interfaces. AllJava.Lang.Classwrappers and interface array elements are properly disposed to avoid gref leaks.This gives the trimmable path the same resolution power that llvm-ir gets implicitly via its preserved class entries. See #11386 for longer-term investigation of interface-based TypeMap resolution.
Other changes
ForceUnconditionalEntriesconstant and all conditional logic gated on itTypeMapAttributeentries for non-essential MCW bindings[DynamicDependency]attributes onFindX509TrustManager— they're needed by both typemap paths to keep the invoker types alive (confirmed: removing them breaks llvm-ir too)TrustManagerImplworkaround (min API is now higher)AppFunctionStatetoAppFunctionEnabledStateto fix name collision with Android API 37android.app.appfunctions.AppFunctionStateclassTrustManagerFactory_GetTrustManagers_ReturnsIX509TrustManagerthat runs on both typemap pathsValidation
Mono.Android.NET-Testspassed on device (0 failures)Mono.Android.NET-Testspassed on device (0 failures)ServerCertificateCustomValidationCallback_*tests pass on both pathsTrustManagerFactory_GetTrustManagers_ReturnsIX509TrustManagertest passes on both paths