Skip to content

[TrimmableTypeMap] Fix stale legacy JCW contamination when switching typemap flavors#11098

Open
simonrozsival wants to merge 4 commits intomainfrom
dev/simonrozsival/fix-trimmable-register-guard
Open

[TrimmableTypeMap] Fix stale legacy JCW contamination when switching typemap flavors#11098
simonrozsival wants to merge 4 commits intomainfrom
dev/simonrozsival/fix-trimmable-register-guard

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented Apr 9, 2026

Summary

Switching _AndroidTypeMapImplementation between llvm-ir and trimmable on an incremental build could leave stale legacy Java callable wrappers under android/src/. That meant a trimmable build could still compile old Runtime.register (...) calls into the APK and fall back to the legacy reflection-based registration path.

Fix

This PR closes the gap in three places:

  • add _AndroidTypeMapImplementation to the build property cache so implementation changes trigger _CleanIntermediateIfNeeded
  • generate trimmable JCWs directly into _AndroidIntermediateJavaSourceDirectory instead of typemap/java plus a copy/overlay step
  • always wire registerJniNativesFn during runtime initialization so unexpected legacy Runtime.register (...) usage is surfaced through the normal runtime path instead of being silently skipped

Tests

  • JcwJavaSourceGeneratorTests.Generate_AcwType_NeverCallsRuntimeRegister
  • BuildTest.SwitchingTypeMapImplementationTriggersClean

The integration test also verifies that switching both directions (llvm-ir <-> trimmable) forces the intermediate clean, while a no-change rebuild still skips it.

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/fix-trimmable-register-guard branch from de004d7 to 36b07f1 Compare April 9, 2026 08:43
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Always wire registerJniNativesFn, fail loudly if trimmable JCW calls Runtime.register() [TrimmableTypeMap] Fix stale legacy JCW contamination when switching typemap flavors Apr 9, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/fix-trimmable-register-guard branch from 36b07f1 to e628b09 Compare April 9, 2026 08:51
…typemap flavors

When switching MonoAndroidTypeMapFlavor between legacy and trimmable
without a clean build, stale legacy JCWs with Runtime.register(typeName,
klass, methods) calls persisted in the android/src/ intermediate
directory alongside trimmable JCWs that only use Runtime.registerNatives().
Both got compiled into the APK, causing the legacy registration path to
execute at runtime — a path incompatible with trimming.

Root cause: the trimmable JCW generator wrote to typemap/java/ and then
copied to android/src/, but never cleaned android/src/ first. Legacy
JCWs with different package names survived the copy overlay.

Three fixes:

1. Write trimmable JCWs directly to _AndroidIntermediateJavaSourceDirectory
   (android/src/) instead of a separate typemap/java/ directory, eliminating
   the copy step entirely. This is the same directory that _FindJavaStubFiles
   globs from, so no files can be missed or stale.

2. Add MonoAndroidTypeMapFlavor to the build properties cache so switching
   between legacy and trimmable triggers _CleanIntermediateIfNeeded,
   removing all stale artifacts from the previous flavor.

3. Always wire registerJniNativesFn so that if a stale JCW somehow calls
   Runtime.register(), TrimmableTypeMapTypeManager.RegisterNativeMembers
   throws UnreachableException with a clear diagnostic instead of the C++
   side silently dropping the call.

Also adds a unit test verifying trimmable JCWs never emit Runtime.register().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/fix-trimmable-register-guard branch from e628b09 to d3cfd10 Compare April 9, 2026 09:08
@simonrozsival simonrozsival added trimmable-type-map copilot `copilot-cli` or other AIs were used to author this labels Apr 9, 2026
simonrozsival and others added 2 commits April 9, 2026 17:44
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival marked this pull request as ready for review April 17, 2026 11:55
Copilot AI review requested due to automatic review settings April 17, 2026 11:55
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

Fixes an incremental-build hazard where switching _AndroidTypeMapImplementation between llvm-ir and trimmable could leave stale legacy JCWs in android/src/, causing Runtime.register() to get compiled into a trimmable APK and break trimming expectations at runtime.

Changes:

  • Emit trimmable JCWs directly into _AndroidIntermediateJavaSourceDirectory to avoid stale overlay artifacts.
  • Add _AndroidTypeMapImplementation to the build properties cache so implementation switches trigger _CleanIntermediateIfNeeded.
  • Always wire registerJniNativesFn during runtime initialization and add unit + integration coverage for the regression.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs Adds a regression assertion that trimmable JCWs never emit the legacy Runtime.register(...) pattern.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets Includes _AndroidTypeMapImplementation in _PropertyCacheItems so switching typemap flavors invalidates intermediates.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs Adds an integration test verifying _CleanIntermediateIfNeeded runs when _AndroidTypeMapImplementation changes.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets Directs typemap generation to write Java sources into android/src/ and removes the copy/overlay step.
src/Mono.Android/Android.Runtime/JNIEnvInit.cs Removes the trimmable-mode guard so registerJniNativesFn is always set.

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