Skip to content

[TrimmableTypeMap] Add UCO wrappers, JCW generator, and JNI signature helpers#10917

Merged
jonathanpeppers merged 21 commits intomainfrom
dev/simonrozsival/trimmable-typemap-04-jcw-generator
Mar 12, 2026
Merged

[TrimmableTypeMap] Add UCO wrappers, JCW generator, and JNI signature helpers#10917
jonathanpeppers merged 21 commits intomainfrom
dev/simonrozsival/trimmable-typemap-04-jcw-generator

Conversation

@simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Mar 11, 2026

Closes #10799

Summary

Adds UCO (UnifiedCallableObject) wrapper generation, JCW (Java Callable Wrapper) Java source generation, and JNI signature parsing utilities on top of the TypeMap proxy groundwork from PR #10808.

Generator additions

  • JcwJavaSourceGenerator — generates .java source files for each ACW type with registerNatives in static {} blocks, native method declarations, constructor chains with super() calls, and @Override annotations
  • JniSignatureHelper — JNI signature parsing (parameter types, return types, JNI↔Java type conversion, CLR type encoding for IL emission)
  • ModelBuilder — extended with UCO method/constructor building, native registration data, and isAcw classification
  • TypeMapAssemblyEmitter — extended with EmitUcoMethod (UnmanagedCallersOnly wrappers that delegate to n_* callbacks), EmitUcoConstructor, EmitRegisterNatives (JNI native method registration), and IAndroidCallableWrapper interface implementation for ACW proxy types

Scanner enrichment

  • JavaPeerInfo — added BaseJavaName, ImplementedInterfaceJavaNames, JavaConstructors, JniParameterInfo, ThrownNames for downstream generators
  • JavaPeerScanner — resolver methods for base types, interfaces, constructors; fixed NativeCallbackName for constructors (n_ctor instead of invalid n_.ctor)

Model additions

  • UcoMethodData / UcoConstructorData — describe UnmanagedCallersOnly wrappers to emit
  • NativeRegistrationData — JNI native method registrations for RegisterNatives

Tests

  • 215 tests covering all generators, model builder scenarios, scanner behavior, and edge cases
  • Test fixtures extended with ExportWithThrows type
  • Stream-based test helpers avoid disk I/O for faster, more reliable tests

Follow-up work

Note on GetFunctionPointer

The issue spec mentioned GetFunctionPointer(int) as an alternative dispatch mechanism. Since we use RegisterNatives (which binds JNI native methods to UCO wrappers at class load time), GetFunctionPointer is not needed.

simonrozsival

This comment was marked as outdated.

simonrozsival

This comment was marked as outdated.

simonrozsival

This comment was marked as outdated.

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-04-jcw-generator branch from 03016b7 to 6a9778f Compare March 11, 2026 21:17
simonrozsival

This comment was marked as outdated.

simonrozsival

This comment was marked as outdated.

simonrozsival

This comment was marked as outdated.

Copy link
Member Author

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review Summary

Verdict: ✅ LGTM

Found 0 issues.

Reviewed all 17 changed files against 14 rule categories. Read full source files (not just diff) to understand invariants and call patterns.

Checked and verified:

  • No null-forgiving !, no Array.Empty<T>() in new code, no string.Empty, no #nullable enable, no empty catch blocks
  • JNI boolean correctly encoded as byte (blittable for [UnmanagedCallersOnly])
  • JI-style activation preserved from #10808 (JniObjectReference conversion + JNIEnv.DeleteRef cleanup)
  • Content-dependent deterministic MVID (ComputeContentFingerprint)
  • Proxy name collision guard (usedProxyNames)
  • Implementor/EventDispatcher trimmability heuristic with documented trade-off
  • Input validation on all public methods; internal Generate(type, writer) has single call site with pre-validated args
  • No resource leaks — all IDisposable properly managed

👍 Clean scanner → model → emitter pipeline. UCO wrappers, JCW Java source, and JNI signature parsing integrate well with the #10808 proxy groundwork. 219 tests with good edge-case coverage and stream-based helpers.


Review generated by android-reviewer from review guidelines.

@simonrozsival simonrozsival marked this pull request as ready for review March 12, 2026 08:07
@simonrozsival
Copy link
Member Author

simonrozsival commented Mar 12, 2026

@jonathanpeppers this is ready for review. Adds UCO wrappers, JCW Java source generator, and JNI signature helpers on top of #10808.

Copilot AI review requested due to automatic review settings March 12, 2026 08:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds TypeMap generator support for ACW scenarios by emitting UCO (UnmanagedCallersOnly) wrapper methods, JNI RegisterNatives registration, a JCW .java source generator, and shared JNI signature parsing helpers—building on the existing TrimmableTypeMap scanner/model/emitter pipeline.

Changes:

  • Extend scanning/model/emission pipeline to represent and emit ACW-specific data (constructors, UCO wrappers, native registrations, RegisterNatives).
  • Add JcwJavaSourceGenerator and JniSignatureHelper for generating Java stubs and parsing JNI signatures/types.
  • Expand/adjust tests and fixtures to cover UCO wrappers, native registration, JNI signature parsing, and throws clauses.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs Adds ExportWithThrows fixture type used to validate throws clause generation.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs Extends ExportAttribute stub with ThrownNames to support fixture scanning/tests.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs Adds model-level tests for ACW classification, UCO wrapper/ctor generation, and native registrations.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs Adds PE metadata tests verifying RegisterNatives and UCO-related metadata presence.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs Updates root generator tests for empty and multi-target cases.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs New tests covering JCW Java output structure, signatures, filtering, and throws clauses.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs Enhances fixture helpers for ACW peers and adds temp directory helpers for file-based JCW tests.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Adds constructor extraction and fixes native callback naming for constructors.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs Enriches scanned model with constructors and additional marshal/native metadata.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs Emits UCO wrappers, RegisterNatives, and ACW proxy interface implementation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs Builds ACW/UCO/native-registration model data and tracks cross-assembly callback dependencies.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs Adds model records for UCO methods/ctors and native registrations, plus IsAcw proxy flag.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs New helper for parsing JNI signatures and mapping to CLR/Java types, plus JNI name validation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JcwJavaSourceGenerator.cs New generator producing JCW .java files for ACW types based on scanned JavaPeerInfo.

@simonrozsival
Copy link
Member Author

Re: the two unresolved review comments:

IAndroidCallableWrapper assembly reference — This type doesn't exist yet; it will be defined when we implement the runtime side. The assembly placement (Mono.Android vs Java.Interop) is a design decision we'll make at that point. This reference was already present in PR #10808 (on main) with the same MonoAndroidRef. We'll fix it to match whichever assembly the type lands in.

Nullable after Assert.NotNull — This is main's existing test code (the Fixture_HelperType_IsUnconditional test). xUnit's Assert.NotNull has [NotNull] annotation which the compiler respects — the project builds with <WarningsAsErrors>Nullable</WarningsAsErrors> and produces no warnings here.

Copy link
Member Author

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review Summary

Verdict: ⚠️ Needs Changes

Found 3 issues: 1 warning and 2 suggestions. CI is still pending (build 1332295).

  • ⚠️ Error handling: Empty catch block swallows IOException silently in DeleteTempDir (FixtureTestBase.cs:141)
  • 💡 Formatting: Missing newline at end of file in JcwJavaSourceGeneratorTests.cs, TypeMapAssemblyGeneratorTests.cs, and TypeMapModelBuilderTests.cs
  • 💡 Testing: ClassDeclaration, AbstractType, and NestedType tests use Assert.Contains("...\n", java) while the 5 analogous failing tests use AssertContainsLine. These happen to work because they assert writer.Write($"...\n") output (not raw strings), but consider using AssertContainsLine throughout for consistency

👍 Excellent test coverage — 215 tests covering all generators, model builder, scanner, and edge cases. The JNI validation in ValidateJniName properly prevents path traversal. The UCO/RegisterNatives emission architecture is well-documented with clear XML docs and a helpful pseudo-Java example in the class remarks. The AssertContainsLine helper is a clean fix for the cross-platform newline issue. Good use of record types for data models.


Review generated by android-reviewer from review guidelines.

simonrozsival and others added 10 commits March 12, 2026 15:20
… helpers

Add the UCO (UnifiedCallableObject) wrapper generation, JCW (Java Callable
Wrapper) Java source generator, and JNI signature parsing utilities on top
of the TypeMap proxy groundwork from PR #10808.

### Generator additions
- JcwJavaSourceGenerator: generates .java source files for JCW types
- JniSignatureHelper: JNI signature parsing (parameter types, return types,
  CLR type encoding)
- Extended ModelBuilder with UCO method/constructor handling
- Extended TypeMapAssemblyEmitter with UCO wrapper IL emission

### Scanner enrichment
- JavaPeerInfo: added BaseJavaName, ImplementedInterfaceJavaNames,
  JavaConstructors, JniParameterInfo for downstream generators
- JavaPeerScanner: resolver methods for base types, interfaces, constructors

### Tests
- 219 tests covering all generators, model builder scenarios, scanner
  behavior, and edge cases
- Test fixtures extended with ExportWithThrows type

Rebased on main after PR #10808 merge — adapted test code to use init-only
record patterns (with {}) and added missing required members.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Proactively address feedback patterns from the #10808 code review:

- Remove null! from TypeMapAssemblyEmitter: make _pe readonly, init in ctor
- Fix misleading JniParamKind.Boolean comment (Z → bool, not sbyte)
- Make AddActivationCtorRef style-aware: support both XamarinAndroid
  (IntPtr, JniHandleOwnership) and JavaInterop (ref JniObjectReference,
  JniObjectReferenceOptions) activation constructor styles
- Remove default parameter values from MakeInterfacePeer test helper
- Replace disk I/O with MemoryStream in generator tests: add Stream
  overloads to Emit/Generate, use MemoryStream in tests for faster
  and more reliable test execution

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix NativeCallbackName bug: constructors now produce 'n_ctor' instead
  of invalid 'n_.ctor' (derived from managed method name '.ctor')
- Merge ParseParameterTypeStrings + ParseJniParameters into single
  JniSignatureHelper.ParseParameters returning List<JniParameterInfo>
- Extract EmitCreateInstance into 5 named methods (EmitNullCreateInstance,
  EmitGenericCreateInstance, EmitInvokerCreateInstance, etc.)
- Inline MakeMarshalMethod helper: replace with direct MarshalMethodInfo
  initializers at all 16 call sites (reviewer feedback)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merge 6 individual [Fact] tests into 2 [Theory] tests with [InlineData]:
- ParseParameterTypes_SingleParam_MapsToCorrectKind (2 cases)
- ParseReturnType_MapsToCorrectKind (4 cases)

Uses int casts in [InlineData] to work around JniParamKind being internal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove null-forgiving operator on TargetTypeReference; use explicit
  null check with throw instead
- Validate IndexOf results in JniSignatureHelper: throw ArgumentException
  for malformed signatures missing ')' or ';' instead of silently
  producing incorrect results
- Replace Array.Empty<T>() with [] per repo convention
- Add #nullable enable to new files (JcwJavaSourceGenerator,
  JniSignatureHelper, TypeMapAssemblyData)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…llable

- Remove #nullable enable from files (project-level Nullable already enabled)
- Remove blank lines inside /// doc comment blocks
- Fix JniParamKind enum indentation (was missing tab indent)
- Fix DeleteTempDir: add braces around if body, expand try-catch
- Restore MetadataHelper.ComputeContentFingerprint and content-dependent
  DeterministicMvid that were accidentally simplified away
- Restore PEAssemblyBuilder.EmitPreamble contentFingerprint parameter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restores the JI-style activation code (EmitCreateInstanceViaJavaInteropNewobj,
EmitCreateInstanceInheritedJavaInteropCtor, JNIEnv.DeleteRef, etc.) that was
inadvertently removed from TypeMapAssemblyEmitter.cs and ModelBuilder.cs.

Re-applies UCO (UnifiedCallableObject) additions on top:
- EmitUcoMethod, EmitUcoConstructor, EmitRegisterNatives
- IAndroidCallableWrapper interface implementation for ACW types
- IsImplementorOrEventDispatcher heuristic for trimmability
- BuildUcoMethods, BuildUcoConstructors, BuildNativeRegistrations in ModelBuilder
- Restores 5-arg EmitBody overload with local variables support

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
JNI jboolean is an unsigned byte. UnmanagedCallersOnly methods require
blittable parameter types, so Boolean must be encoded as byte (not CLR
bool) in UCO wrapper signatures. This matches the issue requirement:
'Non-blittable params converted correctly (byte for bool)'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests were accidentally dropped during the rebase refactoring:
- EmitterBehavior: Emit_CalledTwice_Throws, signature blob corruption
- DeterminismTests: different/identical content MVIDs
- JavaInteropActivation: JI-style ctor type refs, byref param, DeleteRef

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival and others added 8 commits March 12, 2026 15:20
…blyGenerator to main

Undo unnecessary renames (stream → output) and refactoring in files that
don't need changes for this PR. Main already has the stream overloads
needed by tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore main's flat test structure instead of reorganizing into nested
classes. Only add 10 new tests (ACW proxy, UCO, JniSignatureHelper,
edge cases) as pure additions with zero deletions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore main's test structure and append only new UCO tests as pure
additions. Fix Implementor/EventDispatcher test to match new trimmability
heuristic. Reduces diff from +730/-49 to +206/-4.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove IsImplementorOrEventDispatcher name-based heuristic and related
test. This optimization is tracked in #10911 and should not be part of
this PR. Restore main's behavior where these types are unconditional.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tShortName

Restore main's implementations and only add the necessary changes:
- n_ctor fix for constructor callback names
- NativeCallbackName, JniReturnType, Parameters fields in AddMarshalMethod
- BuildJavaConstructors method
- JavaConstructors assignment in peer building

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compute return type and parameter lists on-demand from JniSignature in
JcwJavaSourceGenerator instead of storing them on MarshalMethodInfo and
JavaConstructorInfo. Simplifies the data model — JniSignature is the
single source of truth.

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>
simonrozsival

This comment was marked as outdated.

Copy link
Member Author

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review Summary (v3)

Verdict: ⚠️ Needs Changes

All previous findings have been addressed. No new code issues found.

  • ⚠️ CI: All legs are pending (build 1332407). The previous run's Linux failure (CS0246: AdbDeviceInfo) was a pre-existing main issue from the xamarin-android-tools submodule bump — not caused by this PR. Cannot confirm merge readiness until CI is green.

👍 Clean diff: no null!, no empty catch blocks, no Array.Empty, no disk IO in tests, all files end with newlines. AssertContainsLine properly handles cross-platform line endings. 214 tests cover generators, model builder, scanner, and edge cases.


Review generated by android-reviewer from review guidelines.

simonrozsival and others added 3 commits March 12, 2026 16:03
…dings

Add AssertContainsLine helper that normalizes line endings before
comparing, so tests pass on Windows where raw string literals in the
generator produce \r\n.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor tests to avoid temp directory creation and cleanup:
- Filtering test now checks the scanned peer list directly
- JNI name validation tests call ValidateJniName directly
- Remove CreateTempDir/DeleteTempDir helpers and the empty catch block
- Remove Generate_CreatesCorrectFileStructure integration test

All content generation tests already use in-memory StringWriter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-04-jcw-generator branch from d28814e to 04e0b2e Compare March 12, 2026 15:04
@jonathanpeppers jonathanpeppers merged commit f158bed into main Mar 12, 2026
5 of 6 checks passed
@jonathanpeppers jonathanpeppers deleted the dev/simonrozsival/trimmable-typemap-04-jcw-generator branch March 12, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TrimmableTypeMap] TypeMap assembly + JCW Java source generators

3 participants