Skip to content

[TrimmableTypeMap] Refuse [JniAddNativeMethodRegistrationAttribute] with XA4251#11274

Open
simonrozsival wants to merge 18 commits into
mainfrom
trimmable-virtual-constructor
Open

[TrimmableTypeMap] Refuse [JniAddNativeMethodRegistrationAttribute] with XA4251#11274
simonrozsival wants to merge 18 commits into
mainfrom
trimmable-virtual-constructor

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented May 3, 2026

Refuses [JniAddNativeMethodRegistrationAttribute] in the trimmable typemap.

[JniAddNativeMethodRegistrationAttribute] is a legacy Java.Interop public API. Its primary consumer — the jnimarshalmethod-gen build-time generator that used to emit [JniAddNativeMethodRegistrationAttribute] static void __RegisterNativeMembers stubs on every JCW type — was removed in dotnet/java-interop#1405. The only remaining in-tree uses are Java.Interop's own JavaProxyObject / JavaProxyThrowable infrastructure types (already bypassed in the trimmable path by #11271) and a couple of upstream Java.Interop test fixtures. Supporting it in the trimmable typemap would mean adding scanner + ModelBuilder support for an obsolete code path with no in-tree consumers and a near-zero customer base.

This PR makes the design decision explicit:

  • JavaPeerScanner now takes an optional ITrimmableTypeMapLogger. When it encounters [JniAddNativeMethodRegistrationAttribute] on any method, it calls ITrimmableTypeMapLogger.LogJniAddNativeMethodRegistrationAttributeError directly from the per-type scan loop. No JavaPeerInfo property is propagated; the orchestrator simply passes its logger into new JavaPeerScanner(logger).
  • The MSBuild logger maps the call to XA4251, so GenerateTrimmableTypeMap fails the build via TaskLoggingHelper.HasLoggedErrors.
  • The message instructs developers to either avoid the attribute or switch off the trimmable typemap (for example, by using the llvm-ir type map implementation), and to report the scenario at https://github.com/dotnet/android/issues so the team can evaluate whether to support it.
  • Java.InteropTests.InvokeVirtualFromConstructorTests (which uses the attribute) is permanently excluded under the trimmable typemap with an explanatory comment pointing at this policy.
  • tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj excludes the upstream CallVirtualFromConstructor{Base,Derived}.cs and InvokeVirtualFromConstructorTests.cs from the test assembly compile when _AndroidTypeMapImplementation == trimmable, so the device-test build can compile through the trimmable typemap generator. (The other upstream user of the attribute, TestType.cs, is already gated by #if !NO_MARSHAL_MEMBER_BUILDER_SUPPORT, which the test csproj defines.)

Tests:

  • JavaPeerScannerTests.Scan_JniAddNativeMethodRegistrationAttribute_LogsError (new) gives the scanner a recording ITrimmableTypeMapLogger and asserts it logs XA4251 for HandWrittenNativeRegistrationPeer and not for unrelated fixture types.
  • TrimmableTypeMapGeneratorTests.Execute_WithJniAddNativeMethodRegistrationAttribute_ReportsXA4251 (new) verifies the orchestrator end-to-end emits XA4251 against the same fixture.

Device validation against an Android emulator with the CoreCLRTrimmable lane:

ANDROID_SERIAL=emulator-5554 MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build \
    tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
    -t:RunTestApp -c Release \
    -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false \
    -nr:false -m:1

TestResult-Mono.Android.NET_Tests-ReleaseCoreCLRTrimmable.xml: 888 total, 0 errors, 0 failures, 21 ignored.

Related issues

@simonrozsival simonrozsival changed the title Use trimmable virtual-constructor fixtures [TrimmableTypeMap] Use trimmable virtual-constructor fixtures May 3, 2026
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 3, 2026
@simonrozsival simonrozsival force-pushed the trimmable-java-proxy-object branch from 1467afa to e66930e Compare May 4, 2026 18:21
@simonrozsival simonrozsival force-pushed the trimmable-virtual-constructor branch from 711aeae to 7e2737b Compare May 4, 2026 18:23
simonrozsival and others added 3 commits May 4, 2026 20:31
Add trimmable-specific java_runtime jars that replace Java.Interop's JavaProxyObject and JavaProxyThrowable sources only for the trimmable typemap path. Keep the existing runtime jars on Java.Interop's native-registration behavior and select the trimmable jars when _AndroidTypeMapImplementation is trimmable.

Re-enable the affected Java.Interop runtime tests and add focused coverage for JavaProxyObject marshaling/object methods under the trimmable typemap path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add comments documenting runtime jar selection and Java proxy identity semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enable the Java.Interop virtual-constructor tests in the trimmable typemap lane by swapping in Android-owned Java fixtures that use Runtime.registerNatives and generated n_* callbacks instead of ManagedPeer.

Teach the trimmable typemap scanner/model to emit native registrations for hand-written Java peers that opt into JniAddNativeMethodRegistrationAttribute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the trimmable-java-proxy-object branch from e66930e to 0644b70 Compare May 4, 2026 18:31
@simonrozsival simonrozsival force-pushed the trimmable-virtual-constructor branch from 7e2737b to dc13b42 Compare May 4, 2026 18:31
simonrozsival and others added 7 commits May 5, 2026 11:31
Use repository Java formatting for trimmable proxy sources and strengthen the proxy hashCode test so it compares against Java identityHashCode instead of itself.

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

# Conflicts:
#	tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs
#	tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs
Drop stale _net6 Java runtime artifact names and collapse the trimmable runtime jar/dex to a single shared artifact for CoreCLR and NativeAOT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Base automatically changed from trimmable-java-proxy-object to main May 11, 2026 07:16
…structor

# Conflicts:
#	src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs
#	tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs
@simonrozsival simonrozsival marked this pull request as ready for review May 11, 2026 09:11
Copilot AI review requested due to automatic review settings May 11, 2026 09:11
…mmableTypeMapBuildTests

This cleanup is not related to fixing InvokeVirtualFromConstructorTests
and belongs in a separate change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
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

This PR re-enables Java.InteropTests.InvokeVirtualFromConstructorTests for the CoreCLR trimmable typemap lane by swapping desktop-JVM Java.Interop fixtures for Android/trimmable-compatible copies and extending the trimmable typemap generator to support native registrations for hand-written Java peers.

Changes:

  • Removes the trimmable-typemap exclusion for Java.InteropTests.InvokeVirtualFromConstructorTests and adds trimmable Java/C# fixtures for CallVirtualFromConstructor*.
  • Extends the trimmable typemap scanner/model to detect JniAddNativeMethodRegistrationAttribute usage and emit proxy/registration support accordingly.
  • Adds cleanup/validation for obsolete java_runtime_*_net6 / java_runtime_trimmable_clr artifacts (delete during build; assert not packaged in SDK tests).

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/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs Re-enables InvokeVirtualFromConstructorTests by removing its name-based exclusion.
tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-trimmable/CallVirtualFromConstructorDerived.cs Adds trimmable managed peer fixture using generated n_* callbacks + JniAddNativeMethodRegistrationAttribute.
tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.targets Switches trimmable builds to include Android-owned Java fixtures in the test JAR and exclude the desktop versions.
tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj Conditionally swaps the managed CallVirtualFromConstructorDerived source between desktop and trimmable variants.
tests/Mono.Android-Tests/Java.Interop-Tests/java-trimmable/net/dot/jni/test/CallVirtualFromConstructorDerived.java Adds Android/trimmable Java fixture using mono.android.Runtime.registerNatives(Class) via reflection.
tests/Mono.Android-Tests/Java.Interop-Tests/java-trimmable/net/dot/jni/test/CallVirtualFromConstructorBase.java Adds Android/trimmable base Java fixture using registerNatives(Class) via reflection.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs Verifies obsolete net6/trimmable_clr runtime artifacts are not shipped in the SDK pack.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Detects method-level JniAddNativeMethodRegistrationAttribute to inform generation decisions.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs Stores whether a peer uses JniAddNativeMethodRegistrationAttribute.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs Treats certain DoNotGenerateAcw peers with native registrations as needing proxy/registration emission.
src/java-runtime/java-runtime.targets Deletes obsolete java_runtime_*_net6 / java_runtime_trimmable_clr artifacts during build/clean.

#nullable enable

using System;
using System.Runtime.CompilerServices;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The file this comment was on (tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-trimmable/CallVirtualFromConstructorDerived.cs) has since been removed — this PR no longer adds Java.Interop-trimmable fixtures for the attribute; we refuse the attribute outright with XA4251 instead.

var isInterface = (typeDef.Attributes & TypeAttributes.Interface) != 0;
var isAbstract = (typeDef.Attributes & TypeAttributes.Abstract) != 0;
var isGenericDefinition = typeDef.GetGenericParameters ().Count > 0;
var hasJniAddNativeMethodRegistrationAttribute = HasJniAddNativeMethodRegistrationAttribute (typeDef, index);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 1a71bb3. The doNotGenerateAcw short-circuit no longer applies after the rework — XA4251 should fire even for non-peer types — but the per-type method-attribute walk is now gated on a cheap assembly-level fast-check (AssemblyIndex.MayUseJniAddNativeMethodRegistrationAttribute), so the inner loop runs only when the assembly's TypeRefs / TypeDefs actually mention the attribute name.

…emap

[JniAddNativeMethodRegistrationAttribute] is a legacy Java.Interop API whose
primary consumer — the jnimarshalmethod-gen build-time generator — was
removed in dotnet/java-interop#1405. The trimmable typemap deliberately does
not support this attribute.

When the scanner detects the attribute on any method, the generator emits
XA4251 telling the developer to either avoid it or switch off the trimmable
typemap (for example, by using the 'llvm-ir' type map implementation) and
report the scenario at https://github.com/dotnet/android/issues. The build
fails via TaskLoggingHelper.HasLoggedErrors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Use trimmable virtual-constructor fixtures [TrimmableTypeMap] Refuse [JniAddNativeMethodRegistrationAttribute] with XA4251 May 11, 2026
simonrozsival and others added 3 commits May 11, 2026 12:05
Address review feedback:

- Drop JavaPeerInfo.HasJniAddNativeMethodRegistrationAttribute.  The scanner
  now takes an optional ITrimmableTypeMapLogger and reports XA4251 directly
  when it encounters the attribute, instead of propagating a bool the rest
  of the pipeline doesn't otherwise need.
- Drop the orchestrator's post-scan loop; TrimmableTypeMapGenerator just
  passes its logger into JavaPeerScanner.
- Reset external/Java.Interop and external/xamarin-android-tools to the
  SHAs on main; this PR doesn't need either submodule moved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t/dot/jni/test/GetThis.java

This file is unrelated to the XA4251 work; it was accidentally removed during
an earlier 'git rm -r java-trimmable' cleanup. It's still needed by
JavaObjectTest.DisposeAccessesThis under the trimmable typemap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The upstream Java.Interop test fixture CallVirtualFromConstructorDerived
uses [JniAddNativeMethodRegistrationAttribute]. Now that the trimmable
typemap refuses that attribute with XA4251, the offending sources need to
be excluded from the test assembly when building for the trimmable type
map; otherwise the build cannot reach the device tests at all.

Validated locally:

    ANDROID_SERIAL=emulator-5554 MSBUILDDISABLENODEREUSE=1 \
      ./dotnet-local.sh build \
      tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
      -t:RunTestApp -c Release \
      -p:_AndroidTypeMapImplementation=trimmable \
      -p:UseMonoRuntime=false -nr:false -m:1

TestResult-Mono.Android.NET_Tests-ReleaseCoreCLRTrimmable.xml: 888 total,
0 errors, 0 failures, 21 ignored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Code-review caught that the prior placement of the XA4251 check sat *after*
the scanner's per-type filtering, so a type carrying the attribute but no
[Register] (and not extending a Java peer) would slip past the diagnostic
and the build would silently succeed.

Hoist the check to immediately after the <Module> skip so it fires uniformly
for every non-<Module> type, regardless of whether the type would otherwise
have been added to the typemap. Add a NonPeerNativeRegistration fixture to
the scanner test to guard the regression.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the trimmable-virtual-constructor branch from 7ad46cf to 7d607c5 Compare May 11, 2026 10:53
…bute

Address Copilot reviewer feedback on the per-type method-attribute walk.
AssemblyIndex.Build now does a cheap pass over the TypeReferences and
TypeDefinitions tables to set MayUseJniAddNativeMethodRegistrationAttribute,
and the scanner short-circuits the per-method walk in the overwhelmingly
common case where the assembly neither imports nor declares the attribute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit that referenced this pull request May 11, 2026
Squashed prerequisites from PR #11274 (trimmable-virtual-constructor):
reject [JniAddNativeMethodRegistrationAttribute] with XA4251 in the
trimmable typemap path and exclude the upstream
InvokeVirtualFromConstructor fixtures so the trimmable test assembly
builds.

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

Labels

copilot `copilot-cli` or other AIs were used to author this trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants