[Xamarin.Android.Build.Tasks] Wire opt-in R8 runtime remapping - #12692
simonrozsival wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It makes cross-cutting changes across MSBuild targets, build tasks, native runtime lookup code, and public API surface that require careful human validation beyond automated review.
Review tier: Lite
Findings: 3
New issues introduced by this change (3)
| Severity | Finding |
|---|---|
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateJniRemappingNativeCodeTests.cs — ❌ error: Avoid the null-forgiving operator (!) in tests as well; it hides real nullability issues… |
|
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/R8Tests.cs — |
|
src/Xamarin.Android.Build.Tasks/Utilities/JniRemapping/R8Mapping.cs — |
What changed in this PR
Adds an experimental opt-in for R8 obfuscation in .NET for Android by generating and consuming runtime JNI remapping tables (rather than rewriting managed assemblies), enabling obfuscated DEX outputs while preserving managed JNI name expectations.
Changes:
- Introduces a two-pass R8 pipeline (seed mapping pre-trim + final R8
-applymapping) and generates JNI remapping XML/native tables for CoreCLR and NativeAOT. - Extends runtime remapping to cover reverse type lookups, rewritten method descriptors, and field remapping; shares native lookup code between CoreCLR and NativeAOT.
- Adds/updates tests and documentation for new public properties and XA4327/8/9 diagnostics.
| File | Description |
|---|---|
| tests/MSBuildDeviceIntegration/Tests/R8RuntimeRemappingTests.cs | Device test validating obfuscated members/types and incremental/missing-output recovery. |
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets | Wires new R8 inputs/outputs and enables mapping input/output + obfuscation flag. |
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | Adds opt-in properties, validation (XA4329), incremental inputs, and AAPT rules tracking changes. |
| src/Xamarin.Android.Build.Tasks/Utilities/JniRemapping/R8Mapping.cs | Extends mapping parsing/projection for class/method/field data used by remapping generation. |
| src/Xamarin.Android.Build.Tasks/Utilities/JniRemapping/NativeAotJniRetention.cs | NativeAOT ELF-based literal retention to conservatively select required remap entries. |
| src/Xamarin.Android.Build.Tasks/Utilities/JniRemapping/JniDescriptorText.cs | Converts Java source-form types to JNI tokens + builds method descriptors. |
| src/Xamarin.Android.Build.Tasks/Utilities/JniRemapping/JniAssemblyRewriter.cs | Adds scan-only entrypoint for linked-assembly analysis (no rewriting). |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/R8Tests.cs | Adds unit coverage for keep-option and config generation behavior. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs | Verifies NativeAOT proguard generation respects allowobfuscation. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateJniRemappingNativeCodeTests.cs | New tests for native remap table emission, ordering, and legacy compatibility. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/InvalidConfigTests.cs | Tests defaults + invalid configuration errors for new MSBuild properties. |
| src/Xamarin.Android.Build.Tasks/Tasks/R8.cs | Adds seed mapping mode, applymapping support, and conditional dontobfuscate removal. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateR8JniRemapping.cs | Generates JNI remapping XML from R8 mapping + existing remaps; supports NativeAOT retention path. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateR8JniManifestProguardConfiguration.cs | Generates manifest keep rules to stabilize seed mapping applicability. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs | Emits allowobfuscation on keep rules when runtime remapping is enabled. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotProguardConfiguration.cs | Emits allowobfuscation for NativeAOT-generated keep rules when enabled. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs | Extends generated tables to include reverse types + fields; exposes info for tests. |
| src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfg | Adds/adjusts keep rules needed for stable seed/final graphs and bootstrap types. |
| src/Xamarin.Android.Build.Tasks/Resources/proguard_trimmable_nativeaot.cfg | Aligns NativeAOT baseline keep rules with remapping needs and seed/final stability. |
| src/Xamarin.Android.Build.Tasks/Properties/Resources.resx | Adds XA4327/8/9 localized strings for errors/warnings/validation. |
| src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs | Updates generated resource accessors for XA4327/8/9. |
| src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets | Moves AAPT proguard rule tracking to incremental parent target. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets | Imports new R8 JNI remapping targets last to override pre-trim outputs as needed. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets | Includes new properties in incremental stamps; passes obfuscation state into proguard generation. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets | Reworks linked-assembly proguard inputs; adds remapping-assembly prep + incremental inputs. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets | Adds remapping enable flag into proguard generation and incremental inputs. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.R8JniRemapping.targets | New MSBuild pipeline for seed mapping, remap XML, and NativeAOT late-linked table build. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets | Links remapping object into NativeAOT shared library and updates incremental inputs. |
| src/native/nativeaot/include/runtime-base/internal-pinvokes.hh | Adds internal pinvokes for reverse-type and field lookup. |
| src/native/nativeaot/host/jni-remapping-tables-stub.cc | Provides weak empty table symbols for apps without remapping objects. |
| src/native/nativeaot/host/internal-pinvoke-stubs.cc | Removes now-shared remapping pinvoke stubs from NativeAOT host stubs. |
| src/native/nativeaot/host/host.cc | Plumbs jniRemappingInUse via shared JniRemapping::is_in_use(). |
| src/native/nativeaot/host/CMakeLists.txt | Adds shared remapping sources and stub table compilation to NativeAOT host build. |
| src/native/native.targets | Includes shared remapping sources/headers in NativeAOT flavor build inputs. |
| src/native/mono/xamarin-app-stub/xamarin-app.hh | Updates stub ABI structs to include target_signature + field remapping structures. |
| src/native/mono/xamarin-app-stub/application_dso_stub.cc | Updates stub table initializers for new method signature field. |
| src/native/mono/runtime-base/internal-pinvokes.hh | Adds internal pinvoke declarations for reverse-type and field lookup. |
| src/native/mono/pinvoke-override/pinvoke-tables.include | Extends pinvoke table entries/count for the new remapping exports. |
| src/native/mono/pinvoke-override/generate-pinvoke-tables.cc | Adds new internal pinvoke names to generator input list. |
| src/native/mono/monodroid/internal-pinvokes.cc | Adds MonoVM-safe placeholder exports for new remapping entrypoints. |
| src/native/clr/xamarin-app-stub/application_dso_stub.cc | Extends CLR stub tables to include reverse types + fields + signature pinning. |
| src/native/clr/runtime-base/jni-remapping.cc | Implements binary-search remapping lookups (types, reverse types, methods, fields) + is_in_use(). |
| src/native/clr/pinvoke-override/precompiled.cc | Maps new internal pinvoke entrypoints to implementations. |
| src/native/clr/include/xamarin-app.hh | Declares remapping table symbols and adds field + reverse type structures. |
| src/native/clr/include/runtime-base/jni-remapping.hh | Declares shared lookup surface including reverse type and field lookup. |
| src/native/clr/include/runtime-base/internal-pinvokes.hh | Declares new remapping pinvokes for CoreCLR runtime. |
| src/native/clr/host/internal-pinvokes-shared.cc | Centralizes shared remapping pinvoke implementations for CoreCLR/NativeAOT. |
| src/native/clr/host/internal-pinvokes-clr.cc | Removes remapping implementations now provided by shared file. |
| src/native/clr/host/host.cc | Uses JniRemapping::is_in_use() for init flag and includes remapping header. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs | Ensures FindClass uses replacement type name when remapping is enabled. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs | Adds reverse-type handling for Java-to-managed lookups; uses replacement type for signatures. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Uses reverse type for proxy lookup and replacement type for FindClass checks. |
| src/Mono.Android/Microsoft.Android.Runtime/JniRemappingLookup.cs | Adds reverse type + field lookup plumbing and supports target-method-signature. |
| src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs | Adds LibraryImport declarations for reverse type + field lookup pinvokes. |
| src/Mono.Android/Android.Runtime/AndroidRuntime.cs | Exposes GetOriginalTypeCore via reverse-type lookup. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs | Adds tests validating remapped field names and pinned target signatures. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs | Extends test runtime type manager with field replacement support. |
| external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt | Records new public API surface additions for replacement fields + original type. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniType.cs | Adds TryGet{Static,Instance}Field helpers to support remapped field probing. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs | Adds null default implementation for field replacement in reflection manager. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs | Adds ReplacementFieldInfo + original type + replacement field APIs. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs | Uses replacement lookup keyed by original type name (compat + remapping). |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs | Adds remapped static field probing and fallback to original lookup. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs | Tracks original vs effective JNI type names; remaps ctor/method lookup accordingly. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs | Adds remapped instance field probing and fallback behavior. |
| external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs | Tracks original type name and performs replacement lookups across base types/types. |
| Documentation/docs-mobile/TOC.yml | Adds XA4327/8/9 docs to TOC. |
| Documentation/docs-mobile/messages/xa4329.md | Documents invalid/unsupported configuration errors and resolutions. |
| Documentation/docs-mobile/messages/xa4328.md | Documents remapping incompleteness warnings (conflicts/signature conversion). |
| Documentation/docs-mobile/messages/xa4327.md | Documents remapping generation failures and troubleshooting steps. |
| Documentation/docs-mobile/messages/index.md | Adds XA4327/8/9 to messages index. |
| Documentation/docs-mobile/building-apps/build-properties.md | Documents AndroidEnableR8Obfuscation + AndroidR8ObfuscationMode properties. |
Files not reviewed (1)
- src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs: Generated file
Suppressed comments (1)
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateJniRemappingNativeCodeTests.cs:112
- ❌ error: This uses the null-forgiving operator (
info!) afterAssert.IsNotNull, which the compiler can’t reason about. Prefer?? throwso nullability is enforced without suppressions.
Context: #12692 Follow the existing Intune contract: member lookup keys contain the replaced owner type and the original managed member name and descriptor. Generate R8 member entries and collision keys in that same form rather than teaching every consumer to try both original and replaced owners. Remove JniPeerOriginalTypeName, the duplicate constructor identity, and the now-unused Java.Interop reverse-type hook. Keep reverse lookup for peer activation and derive desugared companion names in the Android remapping helper where the original name is actually needed. Cover residual-owner keys, descriptors, MAM conflicts and duplicates, and real renamed-class construction and member access on both runtimes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
❌ Needs Changes — 1 error found.
The remapping pipeline is well covered and the runtime/native lookup design is coherent, but field-entry conflict detection currently omits the JNI descriptor from the key. Under R8 class merging this can discard a valid field remap and cause runtime lookup failure. CI has no observed failures, with the latest build checks still in progress.
Generated by Android PR Reviewer for #12692 · gpt56 · 770.9 AIC · ⌖ 5.5 AIC · ⊞ 26.3K
Comment /review to run again
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
⚠️ Needs Changes
Found 2 errors in JNI field remapping: inherited remaps can override hidden derived fields, and the newly enabled field fixtures run on MonoVM even though its field-remapping entry point is a stub. The overall cross-runtime design and focused coverage are substantial, but these correctness gaps should be addressed before merge.
CI has not run for the latest commit: the dotnet-android check is currently action_required (awaiting pipeline authorization); CLA passes.
Generated by Android PR Reviewer for #12692 · gpt56 · 1.1K AIC · ⌖ 18.7 AIC · ⊞ 25.7K
Comment /review to run again
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
❌ Needs changes
Found 4 errors in the new runtime-remapping paths:
- Member lookup can select a renamed base member before an unchanged hiding member on the current type.
- Java-to-managed lookup can resolve a residual-name/original-name collision to the wrong managed type.
- MonoVM wildcard and parameter-only remaps can shadow more-specific descriptors because of generated sort order.
- Generated LLVM member-array symbols can collide or become invalid for Unicode JNI names.
The overall single-pass R8 pipeline is thoughtfully integrated across CoreCLR and NativeAOT, with substantial build, unit, and device coverage. All 44 current CI checks completed successfully, but the cases above require adversarial regression coverage before merge.
Generated by Android PR Reviewer for #12692 · copilot · gpt56 · 1.8K AIC · ⌖ 11.5 AIC · ⊞ 21K
Comment /review to run again
|
/review |
|
✅ Android PR Reviewer completed successfully! Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "azcliprod.blob.core.windows.net"See Network Configuration for more information.
|
There was a problem hiding this comment.
The runtime-remapping implementation has broad coverage across Java.Interop, CoreCLR, NativeAOT, MSBuild incrementality, and device/build tests. One MSBuild ordering issue remains: the post-R8 NativeAOT link can run after _CompileToDalvik fails and may consume stale output or obscure the primary failure.
CI is still in progress: the completed checks are passing, with no failures currently reported.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
azcliprod.blob.core.windows.net
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "azcliprod.blob.core.windows.net"See Network Configuration for more information.
Generated by Android PR Reviewer for #12692 · copilot · gpt56 · 738.1 AIC · ⌖ 18.2 AIC · ⊞ 21K
Comment /review to run again
## Motivation The existing Intune/MAM JNI method-remapping path stores target type and method names as stable NUL-terminated UTF-8 strings in generated native data. The old path materialized those names as managed UTF-16 strings and then encoded them back to UTF-8 for `FindClass` and `GetMethodID`. That round trip is unnecessary and becomes more important as remapping is reused by larger consumers such as R8. ## Approach - Add pointer-backed target type, method-name, and method-signature values to `ReplacementMethodInfo`. - Keep pointer and string representations independent so reading a compatibility string property does not implicitly decode native memory. - Add `JniType` lookup paths for pointer/pointer and mixed pointer/span member names and signatures. - Let `JniPeerMembers` retain a replacement type pointer and use it directly for `FindClass` and later member-remapping lookups. - Keep native pointers in `JniMethodInfo` Debug metadata and decode them only if `Name`, `Signature`, or `ToString()` is explicitly requested. - Retain the existing string/span paths for custom `JniTypeManager` implementations. - Document UTF-8 encoding, NUL termination, ownership, and lifetime requirements for every pointer API. The successful generated-remapping path therefore passes the pregenerated UTF-8 type, method name, and optional signature directly to JNI without copying them or converting them to a managed string. This PR is method-only and does not add R8, field remapping, reverse-type mapping, inherited-member fallback, or NativeAOT remapping support. ## Relationship to other PRs - This PR is based directly on `main` and provides the Java.Interop representation and JNI lookup primitives. - #12796 supplies the generated pointers, performs managed table search, and removes the native remapping P/Invokes. - #12692 can build on this stack to add the R8-specific field, reverse-type, inherited-member, and NativeAOT pieces. ## Validation - Java.Interop Debug build. - Java.Interop `JniPeerMembersTests`: 12 passed, 1 skipped. - The JVM fixture exercises stable unmanaged UTF-8 type/name/signature storage, signature fallback, instance/static lookup, and instance-to-static remapping.
f8b006c to
1dee9ed
Compare
## Summary Remove the experimental build-time managed-assembly rewriting approach for R8 so any future assembly-rewriting design can start from a clean foundation. This cleanup is related to #12535 and the original prototype in #12575. ## What this PR undoes This removes the managed rewriting implementation developed across the R8 obfuscation stack: - #12629 added the PE metadata rebuild substrate. - #12630 added managed JNI metadata rewriting from R8 mappings. - #12631 added rewriting for generated trimmable type-map assemblies. - #12632 and #12634 integrated and tested that rewriting approach for CoreCLR and NativeAOT. Concretely, this PR removes the rewrite task, rewrite-only metadata/IL utilities, dedicated tests and fixtures, and the XA4325/XA4326 resources and documentation. The intent is to abandon this implementation rather than preserve an unused rewriting stack that a future design would need to work around. ## What remains in place - The generic `R8Mapping` parser introduced in #12628 remains, along with its tests and the `MSBuildDeviceIntegration` consumer. It is independently useful for reading R8 mapping files. - The ordinary R8 configuration and `private-members` obfuscation/optimization policy from #12668 remain unchanged. This PR does **not** disable R8 or remove private-member obfuscation. - Existing D8/R8 packaging, ProGuard rule handling, and non-rewriting build behavior remain unchanged. - Runtime remapping remains active as the stacked follow-up work in #12847, #12848, #12692, and #12844. Those PRs implement the alternative opt-in strategy without managed assembly rewriting and are not part of this cleanup diff. We may revisit managed assembly rewriting in .NET 12 based on customer feedback and performance data, but with a fresh design rather than this implementation. ## Validation - Built `Xamarin.Android.Build.Tasks` - Ran `Microsoft.Android.Build.Tasks.Tests` - Ran `Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests` - Built `MSBuildDeviceIntegration` - Ran focused `R8MappingTests`
1dee9ed to
f549677
Compare
Add the runtime-remapping mode, validation, CoreCLR and NativeAOT build ordering, incrementality, documentation, and end-to-end coverage on top of the runtime and table-generation layers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pass the generated remapping table aggregate to NativeAOT initialization, provide an empty weak fallback, and track legacy ProGuard configuration files as R8 incremental inputs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
f549677 to
5255c18
Compare

Context: #12535
This is the product policy and build-orchestration layer for opt-in R8 runtime JNI remapping. It is stacked on:
A final dependent PR, #12844, moves the .NET-only R8 mapping task into
Microsoft.Android.Build.Tasks.This PR does not rewrite managed assemblies. Managed bindings retain their original JNI names; the lower layers generate native tables that translate lookups to the names and descriptors emitted by R8.
Opt-in
For a trimmed CoreCLR or NativeAOT application:
runtime-remappingis the sole opt-in; there is no separate enable property. It does not apply to library projects. The existingprivate-membersanddisabledbehavior remains unchanged. Unknown mode values report XA1050, while incompatible runtime-remapping configurations report XA4329.Build orchestration
Coverage in this layer
Host tests cover mode defaults, configuration validation, keep-rule policy, NativeAOT ProGuard configuration, packaging metadata, and incremental behavior. Build/device integration tests cover renamed types and members, constructors, overloads, inherited lookups, peer activation, single-pass ordering, multi-RID builds, missing-output recovery, and opt-out for CoreCLR and NativeAOT.
The generic lookup semantics and mapping/table-generation tests live in #12847 and #12848 respectively.
Experimental limitations
NativeAOT literal matching is conservative and can retain extra entries. Arbitrarily computed JNI names may require explicit remaps or keep rules. Conservative public/nested-class, interface, bootstrap, and native-callback keeps limit obfuscation. Existing Intune/R8 conflict handling is not full remapping-chain composition, and ambiguous reverse mappings for merged classes are omitted. This remains an experimental opt-in, not a production-readiness claim.
Fixes: #12535