[TrimmableTypeMap] Add trimmable [Export] and [ExportField] callback support#11123
[TrimmableTypeMap] Add trimmable [Export] and [ExportField] callback support#11123simonrozsival wants to merge 86 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the TrimmableTypeMap pipeline to support legacy [Export] / [ExportField] callbacks (including UCO wrapper + RegisterNatives generation and richer signature/metadata scanning), and adjusts test/build plumbing to stabilize the CoreCLRTrimmable test lane (including excluding Mono.Android.Export from app packaging on the trimmable path).
Changes:
- Add scanner + model support for
[Export]/[ExportField], including signature resolution for Java-bound types and[ExportParameter]legacy marshalling shapes. - Add generator support for direct-dispatch UCO wrappers for
[Export](newExportMethodDispatchEmitter) and align typemap/manifest generation behavior. - Stabilize CI/test lanes: introduce CoreCLRTrimmable flavor + categories, defer registerNatives up base class chains, and ensure
Mono.Android.Exportisn’t packaged on the trimmable path.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs |
Detect/export [Export] metadata, compute JNI signatures with legacy marshalling shapes, and record precise managed type/assembly info. |
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs |
New emitter that generates UCO wrappers which dispatch directly to managed [Export] targets (avoids legacy dynamic callback generation). |
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs |
Integrate export dispatch emitter, refactor RegisterNatives emission, and adjust proxy/UCO emission flow. |
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs |
Manifest rooting rewrite + propagation of deferred registration flags to base classes; pass prepared manifest through generation. |
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets |
Mark Mono.Android.Export references with AndroidSkipAddToPackage=true for trimmable typemap builds. |
src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs |
Skip assemblies marked AndroidSkipAddToPackage when generating native app config sources. |
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/* |
New/updated fixtures and assertions covering export scanning, export dispatch generation, manifest rewriting, and instrumentation defaults. |
tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj |
Add CoreCLRTrimmable configuration defaults (runtime selection, categories, constants). |
build-tools/automation/yaml-templates/stage-package-tests.yaml |
Add CoreCLRTrimmable instrumentation lane and adjust CoreCLR lane args. |
2b68085 to
9007196
Compare
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 Code Review — PR #11123
Verdict:
Summary
Solid implementation of [Export]/[ExportField] for the trimmable typemap pipeline. The static UCO dispatch via [UnmanagedCallersOnly] + RegisterNatives is correct, the IL generation handles all primitive/object/array/stream/XML marshalling shapes, and the test coverage is thorough (162 new test assertions across scanner, model builder, assembly generator, JCW codegen, and build integration). The Mono.Android.Export.dll exclusion from the packaged APK is properly wired.
Positive callouts
- ExportMethodDispatchEmitterContext factory — single-allocation, reused for the entire emit pass. Clean separation from the parent emitter.
- ExportParameterKind support — properly resolves
InputStream,OutputStream,XmlPullParser,XmlResourceParsermarshalling in both directions (JNI→managed and managed→JNI return). - Array copy-back with null guards — the
Brfalse_sskip pattern correctly avoids null-array copy-back crashes. - Cross-assembly type resolution —
TypeRefSignatureTypeProvider+MetadataTypeNameResolverproperly chaseResolutionScopeto the correct assembly reference, and the testGenerate_ExportProxy_UsesExactCrossAssemblyTypeReferencesvalidates it end-to-end.
CI (Xamarin.Android-PR) is still pending — review is based on code analysis only.
|
Blocked by #11091 |
cc98948 to
68aafbe
Compare
|
/android-reviewer |
4824296 to
d7a4d4f
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Propagate CannotRegisterInStaticConstructor through the base class chain
so that base types like TestInstrumentation_1 also use the deferred
__md_registerNatives() pattern instead of static { registerNatives(...); }
which crashes before the managed runtime registers the JNI native.
- Revert C++ host-jni.cc/hh registerNatives bridge — the managed
[UnmanagedCallersOnly] registration in TrimmableTypeMap.RegisterNatives()
handles this without needing a C++ bridge.
- Add targetPackage default for instrumentation in ComponentElementBuilder.
- Switch proxy base type to generic JavaPeerProxy<T> in TypeMapAssemblyEmitter.
- Add CannotRegisterInStaticConstructor to JavaPeerProxyData model.
- Normalize manifest android:name to actual JNI names.
- Add test exclusions for TrimmableIgnore and SSL categories.
- Add TRIMMABLE_TYPEMAP define constant for conditional compilation.
- Add unit tests for base class propagation and manifest normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… refactoring Revert files that are not about [Export] support: - CI lane (stage-package-tests.yaml) - Test exclusions/categories (TrimmableIgnore, DoNotGenerateAcw) - NUnitInstrumentation test plumbing - Mono.Android.NET-Tests.csproj trimmable setup - TrimmableTypeMapGenerator manifest refactoring (from #11105) - TrimmableTypeMapGeneratorTests manifest/propagation tests Keep only Export-related changes: - CoreCLRIgnore removal from Export tests in JnienvTest.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Revert cast spacing changes in AssemblyIndex.cs, MetadataTypeNameResolver.cs - Revert indentation changes in JavaPeerScanner.cs, GenerateNativeApplicationConfigSources.cs - Revert whitespace in PackagingTest.cs, GeneratePackageManagerJavaTests.cs, TypeMapAssemblyGeneratorTests.cs - Move EmitRegisterNatives back after EmitUcoConstructor to match main's method ordering Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
LoadArgument + LoadConstantI4(0) were emitted unconditionally before the switch statement. When exportKind is Unspecified (the default for parameters without [ExportParameter] attributes), the method returned false without consuming those two stack values, corrupting the IL evaluation stack. Move the LoadArgument + LoadConstantI4(0) into each case block so they are only emitted when the method will also emit the consuming Call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The _AndroidTypeMapImplementation=trimmable forcing and Assert.Ignore removal for NativeAOT belong in the separate CI setup PR, not here. This PR should only add the Export code generation support without modifying CI configuration or device test behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The JcwJavaSourceGenerator was not emitting the 'static' keyword for static [Export] methods, which would cause a runtime crash. Add the keyword to both the wrapper method and the native declaration when method.IsStatic is true. Add a regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ssing Mono.Android.Export reference Fix two CI issues: 1. JcwJavaSourceGenerator.WriteMethods routed [ExportField] methods (which have Connector="__export__") to the connector branch, generating non-static @OverRide wrappers. When the field initializer calls the instead of Connector != null so both [Export] and [ExportField] methods go through the export-aware branch with proper static/access handling. 2. Mono.Android.NET-Tests.csproj includes ExportTests.cs with [Export]- attributed types but was missing Mono.Android.Export reference. On the LLVM-IR typemap path, the JCW generator needs this assembly to produce valid export callback wrappers. Without it, native method registrations are incomplete and the app crashes at startup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sUnreferencedCode The Build_WithExport_ProducesNoTrimWarningsTargetingExportCodegen test now builds successfully (previous javac error fixed), but fails its assertion because IL2026 warnings reference ExportShapes.cs. These IL2026 warnings are about ExportAttribute's own [RequiresUnreferencedCode] annotation — they're expected and unavoidable when using [Export]. Filter out IL2026 lines that mention both 'ExportAttribute' and 'RequiresUnreferencedCode' since those originate from the attribute definition itself, not from the generated typemap codegen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The [Export] device tests (ExportPrimitives, ExportThrowing, ExportReentrant in ExportTests.cs and ContainsExportedMethods in JnienvTest.cs) crash all non-trimmable device test configurations at startup: ExportPrimitives.<clinit> → Runtime.register → Mono.Android.Export callback codegen → TargetInvocationException → JNI abort (SIGABRT) The crash occurs because Mono.Android.Export's DynamicCallbackCodeGenerator fails to create callback delegates for the export fixture types. The trimmable path uses ExportMethodDispatchEmitter instead, which generates proper UCO wrappers at build time. Exclude the Export category from all non-trimmable test legs. The trimmable path already does not exclude Export, so the tests continue to run on CoreCLRTrimmable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit b1b94b6.
…oid.Export The DynamicCallbackCodeGenerator in Mono.Android.Export resolves Java.Lang.Object.GetObject via string-based reflection (GetMethod). In trimmed Release builds, the 3-parameter GetObject<T>(IntPtr, IntPtr, JniHandleOwnership) overload has no direct callers and gets trimmed, causing a NullReferenceException → TargetInvocationException → JNI abort when any non-trivial [Export] method registers its native callbacks. Add [DynamicDependency] to preserve GetObject across trimming. Also revert the Export category exclusion — the goal is to run Export tests on all configurations (llvm-ir uses Mono.Android.Export, trimmable uses the new ExportMethodDispatchEmitter). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1285a4c to
da27f9d
Compare
…ng, add defense-in-depth DynamicDependency - Replace exportShapesText! with a proper Assert.IsNotNull check - Remove stray double blank line in Mono.Android.NET-Tests.csproj - Add [DynamicDependency] on DynamicInvokeTypeInfo static ctor for defense-in-depth — it also resolves GetObject via string reflection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
Review Summary
✅ LGTM (with minor cleanup)
This is a thorough, well-structured PR that adds trimmable [Export] / [ExportField] callback support to the trimmable typemap pipeline. The architecture is clean — the new ExportMethodDispatchEmitter is properly separated from the existing UCO emitter, the scanner extensions for export shapes are well-documented, and the fallback to legacy activation-ctor path for unsupported parameter types is a sensible safety net.
Positive callouts:
- Excellent test coverage: 9 device tests, 27→29 integration tests, 476→508 unit tests, plus new build-level tests and a tripwire for exclusion count growth
- The
CtorFallbackReasondiagnostic plumbing (scanner → model → logger) makes the silent activation-ctor fallback visible in MSBuild-bloutput — great for debugging - The
EmitUserVisibleCtorWrapperpattern correctly mirrorsTypeManager.ActivatewithGetUninitializedObject+SetPeerReference+ direct ctor call - The exception-routing wrapper (
BeginMarshalMethod/try/catch/finally) in the export dispatch matches the legacy contract - Smart lazy initialization of
ExportMethodDispatchEmitterso non-export assemblies pay nothing
Issues by severity
| Severity | Count |
|---|---|
| 2 | |
| 💡 Suggestion | 2 |
- Unused
encodeCallbackSiginExportMethodDispatchEmitter.EmitUcoMethod— dead closure allocation - Duplicate signature decode in
JavaPeerScanner.AddMarshalMethod—managedSigandsigare identical;sigis never used
💡 Suggestions:
3. Ctor arity-matching comment — TryFindMatchingManagedCtorParams returns the first ctor with matching arity by metadata order; a one-line comment noting this would make the "first match wins" semantics explicit
4. Unconditional TrimmerRootAssembly — Java.Interop-Tests and StartupHook lost their Condition attributes in the test csproj
CI: Both public checks pass (license/cla ✅, dotnet-android ✅).
Generated by Android PR Reviewer for issue #11123 · ● 16.7M
| string managedName = index.Reader.GetString (methodDef.Name); | ||
| var managedSig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); | ||
| string jniSignature = registerInfo.Signature ?? "()V"; | ||
| var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); |
There was a problem hiding this comment.
🤖 sig (line 1064) is a duplicate of managedSig (line 1062) — both decode the same method signature with the same provider. sig is never referenced after its declaration; all uses were replaced by managedSig during this PR. Remove the duplicate decode.
| var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); |
Rule: Remove unused code
| Action<BlobEncoder> encodeCallbackSig = sig => sig.MethodSignature ().Parameters (paramCount, | ||
| rt => { if (isVoid) rt.Void (); else JniSignatureHelper.EncodeClrTypeForCallback (rt.Type (), returnKind); }, | ||
| p => { | ||
| p.AddParameter ().Type ().IntPtr (); | ||
| p.AddParameter ().Type ().IntPtr (); | ||
| for (int j = 0; j < jniParams.Count; j++) { | ||
| JniSignatureHelper.EncodeClrTypeForCallback (p.AddParameter ().Type (), jniParams [j]); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🤖 encodeCallbackSig is computed but never referenced. It was likely copy-pasted from TypeMapAssemblyEmitter.EmitUcoMethod (where it's used for AddMemberRef), but the export dispatch path uses AddExportMethodDispatchRef instead, which builds its own signature. This is dead code that allocates a closure on every EmitUcoMethod call.
Rule: Remove unused code
…mmable-typemap-export-attribute # Conflicts: # tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.Helpers.cs
Remove stale constructor activation metadata and ensure trimmable test runs select CoreCLR even when Debug defaults set UseMonoRuntime=true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore generated proxy CreateInstance behavior so inherited activation constructors do not synthesize managed activation when the target type has no leaf activation constructor or invoker. Add device and generator regression coverage for the inherited activation shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve the NormalizeCrc64 implementation from the recent scanner comparison fix and isolate the additional regex normalization to JNI method signatures/connectors used by the new marshal-method comparison. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the pre-existing constructor activation helper flow and local constructor marshalling helpers so the [Export] work keeps the TypeMapAssemblyEmitter diff focused on the required dispatch hook and matching-constructor metadata. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the LogUserCtorFallbacks diagnostic-only path and fail model building for [Export] constructors when no matching user-visible managed constructor exists. Keep the legacy activation fallback for non-[Export] registered constructors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the broad primitive constructor argument helper and keep only the special cases that need explicit handling: JNI boolean byte conversion and string marshalling. Other primitive arguments continue through the existing direct-load path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tighten constructor matching so same-arity mismatches do not claim a managed constructor match, remove dead scanner code and low-value tests, and simplify small manifest/rooting and comment cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep only the roots that are required for NUnit test discovery or startup-hook initialization in the CoreCLR trimmable runtime test app. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original trimmer-root ordering and conditions where behavior does not need to change, and add ExportTests.cs in the existing Java.Interop compile order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reduce churn in constructor model data, remove unneeded Mono.Android.Export dynamic dependencies, condition the runtime-test Mono.Android.Export reference away from the trimmable typemap path, and keep constructor matching restricted to public managed ctors with regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Add UCO (UnmanagedCallersOnly) wrapper codegen for
[Export]methods and[ExportField]fields in the trimmable typemap pipeline, and extend UCO constructor codegen to invoke user-visible managed ctors (parameterless and object-reference parameterized) so types whose Java-side activation triggers user-defined ctor logic — including allThrowablesubclasses with(Throwable cause)ctors and types using[Export]ctors — work correctly under trimming + CoreCLR.#11091 (trimmable test plumbing + CoreCLRTrimmable CI lane) has merged.
Part of #10788
Changes
Export method dispatch support
[Export]and[ExportField]attributes on Java peer types; resolve JNI signatures for non-primitive Java-bound parameter types; collect Java access modifiers and throws clausesMarshalMethodInfocarriesIsExport,JavaAccess,ThrownNames, andSuperArgumentsStringfor export metadatanativemethods with correct access modifiers and throws clauses for[Export]methods; generate Java field declarations for[ExportField][Register]native callbacksExportMethodDispatchEmitter: New emitter class handling the PE metadata generation for export dispatch, separate from the UCO constructor emitterMono.Android.Export: Exclude theMono.Android.Exportassembly from trimmable packages — itsDynamicMethod-based codegen is incompatible with AOT/trimming; uses[ExportAttribute]/[ExportFieldAttribute]from the user assembly's[Register]-scanned types insteadUCO constructor wrappers — invoke user-visible managed ctors
The legacy UCO constructor codegen always built the managed peer via the JI activation ctor
(IntPtr, JniHandleOwnership), which is sufficient for plain[Register]types but skips any user-defined ctor body. That broke types whose Java-side activation must run user code — most notably[Export]-using types (whoseConstructed = true/SuperArgumentsStringinitialization happens in the user ctor) andThrowablesubclasses that need to forward thecauseargument.This PR generalizes UCO constructor codegen to mirror
Java.Interop.TypeManager.Activate:JavaPeerScanner.TryFindMatchingManagedCtorParamsmatches each registered Java ctor signature to a managed..ctor. The match requires equal arity and (currently) all-object-reference JNI args; primitive args fall through to the legacy activation-ctor path. Match results are recorded onJavaConstructorInfo.ManagedParameterTypesand plumbed throughModelBuilder→UcoConstructorData.EmitUserVisibleCtorWrapperhelper emits, when a match exists:Java.Lang.Object.GetObject (IntPtr, JniHandleOwnership, Type)helper is reachable from the generated assembly via the always-on[IgnoresAccessChecksTo("Mono.Android")]attribute thatModelBuilderemits.Java.Lang.Thread+RunnableImplementor, which only has parameterized managed ctors but registers a()VJava ctor via JCW codegen) or when the JNI signature contains a primitive arg, the emitter falls back to the legacy activation-ctor path so we never emit a metadata reference to a non-existent method.Bug fixes
statickeyword in Java codegen for static[Export]methodsTryEmitExportParameterArgument(wrong local variable index)targetPackagedefaulting to use the passed-in package name parameterregisterNativesto base classes so inherited exports are registered correctlyMissingMethodException: Default constructor not found for type Java.Lang.Thread+RunnableImplementor) — see "UCO constructor wrappers" aboveTests
New device tests in
Mono.Android-Testscovering the activation contract:ActivatedDirectThrowableSubclasses_ThrowableCtor_ShouldForwardArgs— single-arg(Throwable cause)ctor invoked from JavaActivatedDirectThrowableSubclasses_MultipleCtors_ShouldDispatchToCorrectCtor— multi-arity dispatch (()Vand(Throwable)V)ContainsExportedMethods/[Export]test fixtures, which exercise the parameterless user-ctor pathJava.Interop/ExportTests.cs— new fixture exercising[Export]parameter / return marshalling end-to-end viaJNIEnv.GetMethodID+Call*Method. Each enabled device test runs under both the legacyllvm-irtypemap (which defines the contract) and the trimmable typemap (which must match it). Verified locally after rebase: all 11 / 11 currently enabledExportTestspass on_AndroidTypeMapImplementation=trimmable+UseMonoRuntime=false:Export_Method_Primitive_RoundTripint -> intExport_Method_Bool_RoundTripbool -> bool(byte / bool ABI)Export_Method_String_RoundTripstring -> stringExport_Method_PeerArg_RoundTripJava.Lang.Objectarg unwrapExport_Method_PeerArg_NullArg_HandledGracefullyExport_Method_IntArray_RoundTrip_AndCopyBackint[]arg + copy-backExport_Method_PeerArray_RoundTripJava.Lang.Object[]arg/returnExport_Method_Throws_PrimitiveReturn_SurfacesAsManagedExceptionOnUserUnhandledExceptionExport_Method_Throws_ObjectReturn_SurfacesAsManagedExceptionOnUserUnhandledExceptionExport_Method_Throws_FollowedBySecondCall_DoesNotLeakPendingExceptionExport_Method_NestedJniCall_PreservesExceptionFromInnerExportGroup B verifies the new
[Export]UCO marshal-method wrapper: each exported method body now runs insideBeginMarshalMethod/try/catch(route viaJniRuntime.OnUserUnhandledException) /finally(EndMarshalMethod), mirroring the trimmable UCO ctor wrapper. Without this, an unhandled managed exception aborts the CoreCLR process. Note: unlike legacyAndroidEnvironment.UnhandledException(which translated toJava.Lang.Throwable),JniRuntime.OnUserUnhandledExceptionpreserves the original managed exception viaJniTransition.SetPendingException, so callers see the original C# exception type with the original message.Also:
JavaPeerScanner.TryFindMatchingManagedCtorParamsnow skips parameterized[Export]ctors with generic / by-ref / pointer parameter types (falling back to the activation-ctor path), fixing a pre-existing build failure onXamarin.Android.NUnitLite'sTestDataAdapterctor whoseJavaList<T>parameter triggeredXAGTT7015.Verified locally on
_AndroidTypeMapImplementation=trimmable×UseMonoRuntime=false: 928 passed / 0 failed / 56 ignored, plus the 11 new Export tests.Scanner integration coverage for
[Export]shapesA dedicated integration test project (
Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests) walks the new scanner over a fixture assembly (UserTypesFixture) and asserts JNI signatures / connectors / metadata for every shape the scanner needs to handle. Coverage was expanded over the course of the PR to 29 test cases and surfaced 5 real scanner bugs that the unit suite did not catch:[Export]shapes (enum,ICharSequence, non-generic collections,[ExportField],[ExportParameter])[ExportField]returning a user-peer type emittedLjava/lang/Object;instead of the peer's CRC64 JNI name. Fixed by extendingTryResolveJniObjectDescriptorto fall back toComputeAutoJniNamesfor types extending a Java peer without[Register].[Export],Throws, mixed[Register]+[Export], virtual + derived, custom JNI name)[Export(Throws = …)]Type[]was silently dropped — scanner only read the internalThrownNames(string[]). Fixed by resolving eachtypeof()arg to its JNI internal name. (2)ExportAttributeisInherited=false, butFindBaseRegisteredMethodInfopropagated base[Export]registrations to derived overrides. Fixed by restricting propagation to[Register]/[JniConstructorSignature]-direct registrations.[ExportField], overloaded names)[Export]on[Register]'d-base override)[Export]on a[Register]'d-base override prevented Pass 3 from also emitting the override entry (onlyonCreateExport, noonCreate). Fixed by skipping the dedup key for[Export]/[ExportField].[Export]on a[Register]'d interface implementorIOnClickListener.OnClickexport and renamed-JNI-name export without reusing the interface registration name.Marshalling parity follow-ups (commits in this PR)
These extend the trimmable typemap's
[Export]parameter/return marshalling to mirror legacyMono.Android.Export/CallbackCodefor reference / value types whose JNI ABI requires more than the genericIJavaObjectpath:enum(Int32 / Byte / Int16 / Int64 backed)I/B/S/J)Java.Lang.ICharSequenceLjava/lang/CharSequence;Android.Runtime.CharSequence.ToLocalJniHandle (ICharSequence)System.Collections.IListLjava/util/List;Android.Runtime.JavaList.ToLocalJniHandle (IList)System.Collections.IDictionaryLjava/util/Map;Android.Runtime.JavaDictionary.ToLocalJniHandle (IDictionary)System.Collections.ICollectionLjava/util/Collection;Android.Runtime.JavaCollection.ToLocalJniHandle (ICollection)Scanner unit tests cover each new descriptor; emitter changes are exercised through the existing
JavaPeerScannerTests/TypeMapAssemblyGeneratorTestscoverage.Related issues
Rebase validation (2026-05-05)
Rebased onto current
mainand resolved the test-project trimmable root conflict. Additional rebase fallout fixed in16382c25d:InvokerActivationCtorStylewhen generating interface invoker activation so Java.Interop-style invokers use the by-refJniObjectReferenceconstructor path.[Export]wrapper and assert the catch/finally wrapper shape emitted by export dispatch.Validation after the rebase:
git diff --checkmake prepare CONFIGURATION=Release./bin/Release/dotnet/dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal --no-restore— 476 passed./bin/Release/dotnet/dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj -c Release -v minimal --no-restore— 27 passedmake all CONFIGURATION=ReleaseMSBUILDDISABLENODEREUSE=1 ANDROID_SERIAL=emulator-5554 ./dotnet-local.sh build -t:RunTestApp tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c Release -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false -p:AdbTarget="-s emulator-5554" -nr:false— Passed: 878, Failed: 0, Skipped: 56, Total: 934. ConfirmedExportTestsand the activated direct throwable subclass tests executed successfully.Current branch follow-ups
The branch has been refreshed against current
mainand review cleanup has narrowed the diff to the trimmable typemap export work and its tests.Current follow-up scope:
[Export]on[Register]'d interface implementations (IOnClickListener.OnClickand a renamed JNI export).Export_Method_Throws_FollowedBySecondCall_DoesNotLeakPendingExceptionandExport_Method_NestedJniCall_PreservesExceptionFromInnerExportto Group B ofExportTests.CreateInstanceregression covered byConstructorActivationTests.TrimmableIgnorecategory.Latest recorded validation
./bin/Release/dotnet/dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -c Release— 508 passed../bin/Release/dotnet/dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj -c Release— 29 passed../bin/Release/dotnet/dotnet test src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj -c Release --filter "FullyQualifiedName~TrimmableTypeMap_ExcludedTestNames"— passed before the exclusion-growth tripwire was removed during cleanup.Device validation
Device validation for the final cleanup commits is delegated to CI. Earlier local device validation covered the trimmable CoreCLR lane and the
ExportTests/ activated direct throwable subclass tests; the current Group BExportTestsadd the two re-entrancy cases described above.