Skip to content

[typemap] Validate exported Java fields - #12596

Open
simonrozsival wants to merge 5 commits into
simonrozsival-jcw-interface-parityfrom
simonrozsival-export-field-parity
Open

[typemap] Validate exported Java fields#12596
simonrozsival wants to merge 5 commits into
simonrozsival-jcw-interface-parityfrom
simonrozsival-export-field-parity

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

  • match legacy [ExportField] initializer validation in the trimmable scanner with localized XA4205/XA4207/XA4208 diagnostics
  • match Java.Interop.ExportFieldAttribute by full identity during validation, field collection, and registration parsing
  • stop invalid initializer members before the task writes typemap or Java outputs
  • extend the shared classfile comparator/javac fixture with ordered static/instance/protected fields and Java reads
  • add focused build and device coverage for static and instance field initialization
  • inspect plural root/per-assembly typemap DLLs and both trimmable/llvm-ir Java output locations in atomic-output assertions

TDD evidence

Before the fix, parameterized and void [ExportField] initializers failed with XA4205/XA4208 under llvm-ir and trimmable CoreCLR. Trimmable NativeAOT instead emitted _UnnamedProject.TypeMap.dll plus invalid ValidationPeer.java, then failed later in javac because the initializer was called with no arguments or because it emitted a void field.

After the fix, llvm-ir CoreCLR, trimmable CoreCLR, and trimmable NativeAOT all report the matching XA diagnostic without writing typemap assemblies or partial Java field output.

A review suggested that a parameterized initializer returning void should report both codes. Execution disproved that premise: legacy Diagnostic.Error throws on XA4205, so llvm-ir never reaches XA4208. The combined-invalid regression proves XA4205 is present, XA4208 is absent, and no outputs are written for llvm-ir CoreCLR, trimmable CoreCLR, and trimmable NativeAOT.

Generic declaring types exposed a separate parity gap. LLVM-ir reports XA4207 before inspecting the initializer signature. Before the fix, trimmable CoreCLR could write typemap/Java outputs before a later linker XA4207, parameter/void shapes reported XA4205/XA4208 first, and NativeAOT lacked XA4207. Trimmable now reports localized XA4207 first for valid, parameterized, void, and combined generic initializers in both runtimes, without partial outputs.

Simple-name matching also treated unrelated user-defined ExportFieldAttribute types as Java.Interop.ExportFieldAttribute. The scanner now uses full namespace/name matching consistently at all three in-scope paths. A signature-invalid lookalike produces no field, marshal method, or diagnostic, while the real attribute remains recognized.

Duplicate and invalid Java field names remain javac failures in all three configurations, matching llvm-ir; no trimmable-only diagnostic was introduced. Arbitrary managed return types remain deferred to the general unsupported-export-signatures layer because llvm-ir does not support that shape either.

Validation

  • 777 Microsoft.Android.Sdk.TrimmableTypeMap.Tests tests
  • 25 Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests tests, including classfile semantic parity and javac
  • legacy GenerateExportedMembers
  • valid fixture builds: llvm-ir CoreCLR, trimmable CoreCLR, trimmable NativeAOT
  • complete invalid parameter/void/combined/generic build matrix: 21/21 across llvm-ir CoreCLR, trimmable CoreCLR, and trimmable NativeAOT
  • focused artifact discovery covers per-assembly typemap DLL, _Microsoft.Android.TypeMaps.dll, trimmable Java, and llvm-ir Java
  • duplicate/invalid-name classification builds: llvm-ir CoreCLR, trimmable CoreCLR, trimmable NativeAOT
  • on-device Export category: llvm-ir CoreCLR 13 passed/4 expected skips; trimmable CoreCLR 17 passed; trimmable NativeAOT 17 passed
  • git diff --check; every added line is at most 180 characters

Stacked on #12585. Tracks #12561 and #11289.

Match legacy XA4205 and XA4208 validation in the trimmable scanner, before invalid field members can reach generated typemap or Java outputs. Extend semantic, build, and device coverage for valid static and instance ExportField behavior and classify legacy-unsupported field names.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 31, 2026 07:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity · 1 Low severity

New issues introduced by this change (2)
Severity Finding
Medium severity src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​TrimmableTypeMapBuildTests.cs — ❌ error — The typemap output assertion only checks for *.TypeMap.dll, but trimmable typemap…
Low severity src/​Microsoft.Android.Sdk.TrimmableTypeMap/​Scanner/​JavaPeerScanner.cs — 💡 suggestion — ValidateExportField() introduces an extra per-method custom-attribute scan and…
What changed in this PR

This PR tightens trimmable typemap parity with the legacy [ExportField] pipeline by validating invalid exported-field initializer shapes early (parameterized/void), emitting the corresponding XA4205/XA4208 diagnostics, and ensuring the generator stops before writing partial typemap/Java outputs. It also expands the Java-source semantic parity fixture and adds new host/device coverage for static/instance/protected exported fields.

Changes:

  • Add [ExportField] signature validation in the trimmable scanner and surface XA4205/XA4208 through the shared logger/task plumbing.
  • Extend generator + integration fixtures/tests to assert “no partial Java fields / marshal methods” for invalid [ExportField] shapes and to validate ordered field semantics (static/instance/protected).
  • Add on-device coverage for instance primitive [ExportField] initialization visibility.
File Description
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​ExportTests.cs Adds a device test validating instance primitive exported-field initialization is visible via JNI.
tests/​Microsoft.Android.Sdk.TrimmableTypeMap.Tests/​TestFixtures/​TestTypes.cs Introduces invalid [ExportField] fixture types (parameterized + void return) for regression testing.
tests/​Microsoft.Android.Sdk.TrimmableTypeMap.Tests/​Generator/​TrimmableTypeMapGeneratorTests.cs Adds generator-level assertions for legacy XA4205/XA4208 diagnostics and absence of partial Java/typemap members.
tests/​Microsoft.Android.Sdk.TrimmableTypeMap.Tests/​Generator/​ExportFieldTests.cs Adds focused unit coverage ensuring invalid export fields produce no Java field or marshal wrapper.
tests/​Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/​JavaSourceSemanticParityTests.cs Extends the semantic parity fixture to cover field ordering/modifiers and adds a Java consumer for reads.
tests/​Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/​JavaSourceParityFixture/​JavaSourceParityTypes.cs Adds a protected exported field to increase fixture coverage for visibility and instance fields.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​TrimmableTypeMapBuildTests.cs Adds build-time coverage asserting invalid [ExportField] stops before writing typemap/Java outputs and matches legacy diagnostics.
src/​Xamarin.Android.Build.Tasks/​Tasks/​GenerateTrimmableTypeMap.cs Plumbs XA4205/XA4208 into the MSBuild logger using localized Java.Interop resources.
src/​Microsoft.Android.Sdk.TrimmableTypeMap/​Scanner/​JavaPeerScanner.cs Adds scanner-side validation to reject parameterized/void [ExportField] initializers before member emission.
src/​Microsoft.Android.Sdk.TrimmableTypeMap/​ITrimmableTypeMapLogger.cs Extends the shared logger interface with new ExportField-specific diagnostic hooks.
}

Assert.IsEmpty (
Directory.GetFiles (typemapDirectory, "*.TypeMap.dll", SearchOption.TopDirectoryOnly),
Comment on lines 656 to 662
if (!ValidateExportField (methodDef, index)) {
continue;
}

// Check for [ExportField] — produces both a marshal method AND a field
CollectExportField (methodDef, index, fields);

Prove that parameter-count validation takes precedence over void-return validation for an initializer violating both rules, matching measured llvm-ir behavior across trimmable CoreCLR and NativeAOT without partial outputs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
simonrozsival and others added 3 commits August 31, 2026 09:36
Match legacy XA4207 precedence for ExportField methods declared on generic types before parameter-count or void-return validation, and reject them before typemap or Java outputs are written.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cover plural root and per-assembly typemap DLLs plus trimmable and llvm-ir Java source locations when asserting that invalid ExportField metadata produced no partial outputs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Ignore unrelated attributes that share the ExportFieldAttribute simple name by matching the Java.Interop namespace consistently during validation, field collection, and marshal-method registration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

2 participants