[TrimmableTypeMap] Use proxy associations for managed -> JNI lookups - #11100
Conversation
7faf5f5 to
e70f93a
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the TrimmableTypeMap pipeline to support reliable managed → JNI lookups for proxied types by emitting proxy associations and persisting the resolved JNI name on generated proxy attributes, then teaching the runtime to consult this proxy mapping before falling back to attribute-based name resolution.
Changes:
- Emit
TypeMapAssociation<Java.Lang.Object>entries for all proxied types and extend generated proxy metadata to include the resolved JNI name. - Update runtime managed → JNI resolution to consult the proxy type map first (via
TryGetJniNameForManagedType) and adjust lookup ordering accordingly. - Expand tests to validate CRC64 package naming for unregistered types and verify association/proxy model fields and emitted metadata filtering.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs | Adds a test asserting CRC64-based Java package naming for unregistered fixture types. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs | Verifies proxies now store JniName, adds association test, and tightens PE-attribute filtering to skip association blobs. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs | Updates assertion to handle the generic TypeMapAssociationAttribute type name. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs | Switches managed → JNI lookups to consult the trimmable typemap proxy mapping first. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Adds proxy type mapping + cache behavior and a TryGetJniNameForManagedType helper that prefers proxies. |
| src/Mono.Android/Java.Interop/JavaPeerProxy.cs | Extends proxy attribute ctor to accept and expose JniName for reverse lookups. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs | Updates emitted proxy ctor call to pass JniName and emits generic TypeMapAssociationAttribute ctor references. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs | Populates proxy JniName and emits associations for all proxied types (aliases map to the primary proxy). |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs | Adds required JniName field to the proxy model. |
1bacfe5 to
50285c7
Compare
Use TypeMapAssociation + JavaPeerProxy.JniName to support reverse managed-to-JNI lookups in the trimmable type map path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread the final emitted JNI name through generated proxy metadata and add coverage for CRC64/ACW-renamed peers so managed-to-JNI lookups resolve via the emitted name instead of compat fallbacks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make Scan_UnregisteredType_UsesCrc64PackageName carry the expected Java name directly in theory data so the cases are explicit and easier to review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the resurrected emitter getter helper, require the explicit 3-arg JavaPeerProxy constructor, and keep JNI-name fallback logic in the runtime type-map path rather than the proxy attribute base class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit generated proxy types as JavaPeerProxy<TTarget> and call the 2-arg generic constructor so TargetType comes from the type argument rather than being redundantly passed through IL. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Strengthen the generic proxy-base test so it inspects emitted proxy TypeDefinition.BaseType metadata and verifies the proxies really inherit from closed JavaPeerProxy<T> instantiations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restrict managed-type resolution in the trimmable runtime path to the generated proxy map only, and stop falling back to attribute-based JNI-name discovery when no proxy exists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f07ad09 to
23bd54d
Compare
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 AI Review Summary
Verdict:
Found 2 issues:
- ❌ API design:
JavaPeerProxyreplaces its existing protected constructor with a new signature, which is a source/binary break for external subclasses (src/Mono.Android/Java.Interop/JavaPeerProxy.cs:21). ⚠️ Patterns:TryGetJniNameForManagedType()no longer falls back to[Register]/[JniTypeSignature], so the base-type walk can miss non-proxied peers (src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs:92).
I like the overall direction here: persisting the resolved JNI name on the proxy makes the managed → JNI path much easier to reason about. Public CI is green, but the internal Xamarin.Android-PR pipeline is still running, so this is not ready for ✅ yet.
Review generated by android-reviewer from review guidelines.
| /// </summary> | ||
| /// <param name="targetType">The managed peer type this proxy represents.</param> | ||
| /// <param name="invokerType">The invoker type for interfaces/abstract classes, or <c>null</c> for concrete types.</param> | ||
| protected JavaPeerProxy ( |
There was a problem hiding this comment.
🤖 ❌ API design — JavaPeerProxy is a public type, and swapping the existing protected ctor for (string jniName, Type targetType, Type? invokerType) is a source/binary-breaking change for any downstream subclass. Please keep the old overload and chain it to the new implementation so external proxies do not have to rebuild or change signatures just to pick up this runtime fix.
Rule: Public API changes must preserve compatibility when possible.
There was a problem hiding this comment.
This feature has not been released publicly so it's OK to still change the API.
| if (type.GetCustomAttributes (typeof (IJniNameProviderAttribute), inherit: false) is [IJniNameProviderAttribute provider, ..] | ||
| && !string.IsNullOrEmpty (provider.Name)) { | ||
| jniName = provider.Name.Replace ('.', '/'); | ||
| var proxy = GetProxyForManagedType (managedType); |
There was a problem hiding this comment.
🤖 TryGetJniNameForType() also handled plain [Register] / [JniTypeSignature] attributes, and GetSimpleReferences() still walks base types expecting that fallback. Can we keep the attribute-based lookup when _proxyTypeMap misses (or add a regression test proving every relevant base type is proxy-backed)?
Rule: Verify behavior regressions when replacing an existing lookup path.
There was a problem hiding this comment.
The proxy type map is generated based on the [Register] and [JniTypeSignature] attributes. It can't be missing.
TypeMapAssociation<Java.Lang.Object>for proxied types so the trimmable runtime has a managed -> proxy mappingJavaPeerProxyand use it for reverse managed -> JNI lookups