Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dd7b442
Rewrite managed JNI metadata from R8 mappings
simonrozsival Sep 1, 2026
1f881ea
Use documented XA4325 error code for JNI name rewriting failures
simonrozsival Sep 1, 2026
83ee1bd
Fix managed JNI rewrite sequence handling
simonrozsival Sep 1, 2026
afe9106
Complete managed JNI rewrite coverage
simonrozsival Sep 1, 2026
7fbf20a
Address managed JNI rewrite review feedback
simonrozsival Sep 2, 2026
a507655
Rewrite trimmable typemap JNI metadata
simonrozsival Sep 1, 2026
520236a
Handle shared FieldRVA names conservatively
simonrozsival Sep 1, 2026
3eeeaaa
Avoid sharing owner-specific JNI method names
simonrozsival Sep 1, 2026
be9f396
[r8-obfuscation] Enable R8 JNI name obfuscation for CoreCLR
simonrozsival Sep 1, 2026
dbec950
Fix R8 JNI obfuscation integration
simonrozsival Sep 1, 2026
0716939
Fix R8 JNI seed configuration inputs
simonrozsival Sep 1, 2026
c1fdd7e
Route rewritten JNI names to original Java sources
simonrozsival Sep 2, 2026
445c87d
Allow final R8 to apply JNI mappings
simonrozsival Sep 2, 2026
205ad64
Keep manifest entry names stable in seed R8
simonrozsival Sep 2, 2026
fba24bd
Replace early AAPT seed rule generation
simonrozsival Sep 2, 2026
3b99969
Clarify activity alias manifest rules
simonrozsival Sep 2, 2026
52e59cf
Fix final R8 generated keep routing
simonrozsival Sep 2, 2026
134519e
Prepare per-RID manifest merger inputs
simonrozsival Sep 2, 2026
77b3dc4
Fix R8 JNI incremental validation tests
simonrozsival Sep 2, 2026
86bb28f
Handle R8 mapping metadata in integration assertions
simonrozsival Sep 2, 2026
6a05a22
Normalize reverse-mapped Java source paths
simonrozsival Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Documentation/docs-mobile/TOC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@
href: messages/xa4323.md
- name: XA4324
href: messages/xa4324.md
- name: XA4325
href: messages/xa4325.md
- name: XA4326
href: messages/xa4326.md
- name: XA4327
href: messages/xa4327.md
- name: "XA5xxx: GCC and toolchain"
items:
- name: "XA5xxx: GCC and toolchain"
Expand Down
11 changes: 11 additions & 0 deletions Documentation/docs-mobile/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,17 @@ removing the existing one(s) and adding your own AOT profiles.

This property is `False` by default.

## AndroidEnableR8JniNameObfuscation

A boolean property that enables R8 obfuscation of Java type, method, and field names
referenced by managed JNI metadata. The build uses an R8-generated mapping to rewrite
managed assemblies before trimming, then applies the same
mapping during the final R8 invocation.

This property requires `AndroidLinkTool=r8`,
`AndroidTypeMapImplementation=trimmable`, the CoreCLR runtime, and
`PublishTrimmed=true`.
The default value is `False`.

## AndroidEnableRestrictToAttributes

Expand Down
3 changes: 3 additions & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4322](xa4322.md): Skipping library ProGuard configuration file '{file}' (from {source}) because it contains the unsupported global option '{option}'. Global ProGuard options are only allowed in application projects.
+ [XA4323](xa4323.md): Ignoring directory '{directory}' as it does not exist.
+ [XA4324](xa4324.md): [{arch}] Unable to delete source file '{file}'.
+ [XA4325](xa4325.md): Failed to rewrite managed JNI names for R8. {message}
+ [XA4326](xa4326.md): Unable to safely rewrite a JNI member lookup because its class handle does not have one structurally unambiguous `JNIEnv.FindClass` source.
+ [XA4327](xa4327.md): Failed to validate R8 JNI mapping data. {message}

## XA5xxx: GCC and toolchain

Expand Down
56 changes: 56 additions & 0 deletions Documentation/docs-mobile/messages/xa4325.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: .NET for Android error XA4325
description: XA4325 error code
ms.date: 09/01/2026
f1_keywords:
- "XA4325"
---

# .NET for Android error XA4325

## Example messages

```
error XA4325: Failed to rewrite managed JNI names for R8. The 'SourceFiles' and 'DestinationFiles' item groups must contain the same number of items.
```

```
error XA4325: Failed to rewrite managed JNI names for R8. Could not rewrite the JNI names in the assembly 'obj/Release/net11.0-android/android/Acme.App.dll': The file contains no managed metadata.
```

## Issue

When R8 obfuscates Java type and member names, the JNI names embedded in your
managed assemblies must be updated to match the obfuscated names. This error
means that step failed, so the app would not have been able to find its Java
types at run time.

There are two causes:

* **Mismatched item groups.** The `SourceFiles` and `DestinationFiles` item
groups passed to the `RewriteJniNamesForR8` task did not contain the same
number of items. This only happens if a custom target invokes the task
directly, or if a target that produces these item groups has been overridden.

* **An assembly could not be rewritten.** A specific assembly could not be read
or reconstructed. The message names the assembly and includes the underlying
reason, such as the file not containing managed metadata or containing
malformed IL.

## Solution

For the mismatched item groups case, review any custom targets that call
`RewriteJniNamesForR8` and make sure `SourceFiles` and `DestinationFiles` are
populated in matching order, or set `DestinationDirectory` instead of
`DestinationFiles`.

For the assembly failure case, first confirm the named file is a managed
assembly and is not corrupt. Deleting the `bin/` and `obj/` directories and
rebuilding clears a partially written or stale file.

If the named file is a valid managed assembly and the failure persists, this is
unexpected. Please [report an issue][report-issue] and include the full error
message, the name of the assembly, and, if possible, a project that reproduces
the failure.

[report-issue]: https://github.com/dotnet/android/issues/new/choose
39 changes: 39 additions & 0 deletions Documentation/docs-mobile/messages/xa4326.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: .NET for Android warning XA4326
description: XA4326 warning code
ms.date: 09/01/2026
f1_keywords:
- "XA4326"
---

# .NET for Android warning XA4326

## Example message

```
warning XA4326: Unable to safely rewrite a JNI member lookup because its class handle does not have one structurally unambiguous JNIEnv.FindClass source.
```

## Issue

R8 renamed a Java class referenced by a managed JNI member lookup. The class
handle is assigned more than once, comes from an ambiguous control-flow path, or
cannot be proven to come directly from `JNIEnv.FindClass`.

The class name can be rewritten, but the corresponding member name cannot be
safely associated with one original Java class. Guessing could make the managed
assembly request a member from the wrong obfuscated class.

## Solution

This warning is unexpected for code generated by .NET for Android. Please
[report an issue][report-issue] and include the full warning, the affected
assembly, its R8 mapping file, and, if possible, a project that reproduces the
warning.

If the assembly contains custom JNI code, keep each `JNIEnv.FindClass` call and
the member lookup that uses its result in an unambiguous sequence. Initialize
each cached class-handle field from one direct `JNIEnv.FindClass` call and do
not assign another class handle to the same field.

[report-issue]: https://github.com/dotnet/android/issues/new/choose
45 changes: 45 additions & 0 deletions Documentation/docs-mobile/messages/xa4327.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: .NET for Android error XA4327
description: XA4327 error code
ms.date: 09/01/2026
f1_keywords:
- "XA4327"
---

# .NET for Android error XA4327

## Example messages

```
error XA4327: Failed to validate R8 JNI mapping data. The R8 JNI rewrite manifest 'obj/Release/net11.0-android/r8-jni-rewrite-manifest.txt' was not found.
```

```
error XA4327: Failed to validate R8 JNI mapping data. The final R8 mapping did not preserve the JNI seed mapping for class 'com/example/MyView'.
```

## Issue

When R8 obfuscates Java type and member names, the build first generates a seed
mapping and rewrites the corresponding names in managed assemblies. The final R8
invocation must preserve every rewritten name that remains reachable after
trimming.

This error means that a required mapping or manifest could not be read, the
merged Android manifest could not be used to generate seed keep rules, a linked
assembly could not be scanned, the final R8 mapping changed a seeded name, or
final R8 removed a JNI entry that remained reachable from managed code. It can
also mean that an obfuscated post-trim JNI class could not be mapped uniquely
back to the original Java source generated before trimming.

## Solution

Delete the project's `bin/` and `obj/` directories and rebuild to clear stale or
partially written intermediate files. Also review custom ProGuard configuration
for rules that rename or remove the type or member named in the error.

If the failure persists without custom ProGuard rules, please
[report an issue][report-issue] and include the full XA4327 message, the R8
mapping files, and, if possible, a project that reproduces the failure.

[report-issue]: https://github.com/dotnet/android/issues/new/choose
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ sealed class PEAssemblyBuilder
// Avoids creating duplicate __utf8_N types when multiple fields share the same size.
readonly Dictionary<int, TypeDefinitionHandle> _sizedTypeCache = new ();

// Deduplication cache for UTF-8 string RVA fields. Strings like "()V" that repeat across
// many proxy types are stored once and shared via the same FieldDefinitionHandle.
readonly Dictionary<string, FieldDefinitionHandle> _utf8FieldCache = new (StringComparer.Ordinal);
// JNI signatures are owner-independent and can safely share one RVA field. JNI method names
// are owner-specific after R8 rewriting, so each registration receives its own field.
readonly Dictionary<string, FieldDefinitionHandle> _sharedUtf8FieldCache = new (StringComparer.Ordinal);
readonly Dictionary<string, Queue<FieldDefinitionHandle>> _uniqueUtf8FieldCache = new (StringComparer.Ordinal);
TypeDefinitionHandle _privateImplDetailsType;
int _utf8FieldCounter;

Expand Down Expand Up @@ -273,31 +274,57 @@ TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string manage
}

/// <summary>
/// Emits deduplicated RVA fields containing the supplied null-terminated UTF-8 strings.
/// Emits RVA fields containing the supplied null-terminated UTF-8 strings.
/// <paramref name="sharedValues"/> are deduplicated, while every occurrence in
/// <paramref name="uniqueValues"/> receives a separate field.
/// Fields are grouped by size so each group is emitted contiguously on its sized helper
/// type before any consuming types are emitted.
/// </summary>
public void PrepareUtf8Fields (IEnumerable<string> values)
public void PrepareUtf8Fields (IEnumerable<string> sharedValues, IEnumerable<string> uniqueValues)
{
var valuesBySize = new SortedDictionary<int, SortedSet<string>> ();
foreach (string value in values) {
var sharedValuesBySize = new SortedDictionary<int, SortedSet<string>> ();
foreach (string value in sharedValues) {
int size = System.Text.Encoding.UTF8.GetByteCount (value) + 1;
if (!valuesBySize.TryGetValue (size, out var valuesForSize)) {
if (!sharedValuesBySize.TryGetValue (size, out var valuesForSize)) {
valuesForSize = new SortedSet<string> (StringComparer.Ordinal);
valuesBySize.Add (size, valuesForSize);
sharedValuesBySize.Add (size, valuesForSize);
}
valuesForSize.Add (value);
}

foreach (var group in valuesBySize) {
var sizedType = GetOrCreateSizedType (group.Key);
foreach (string value in group.Value) {
AddUtf8Field (value, sizedType);
var uniqueValuesBySize = new SortedDictionary<int, SortedDictionary<string, int>> ();
foreach (string value in uniqueValues) {
int size = System.Text.Encoding.UTF8.GetByteCount (value) + 1;
if (!uniqueValuesBySize.TryGetValue (size, out var valuesForSize)) {
valuesForSize = new SortedDictionary<string, int> (StringComparer.Ordinal);
uniqueValuesBySize.Add (size, valuesForSize);
}
valuesForSize.TryGetValue (value, out int count);
valuesForSize [value] = count + 1;
}

var sizes = new SortedSet<int> (sharedValuesBySize.Keys);
sizes.UnionWith (uniqueValuesBySize.Keys);
foreach (int size in sizes) {
var sizedType = GetOrCreateSizedType (size);
if (sharedValuesBySize.TryGetValue (size, out var sharedForSize)) {
foreach (string value in sharedForSize) {
_sharedUtf8FieldCache.Add (value, AddUtf8Field (value, sizedType));
}
}
if (uniqueValuesBySize.TryGetValue (size, out var uniqueForSize)) {
foreach (var pair in uniqueForSize) {
var fields = new Queue<FieldDefinitionHandle> (pair.Value);
for (int i = 0; i < pair.Value; i++) {
fields.Enqueue (AddUtf8Field (pair.Key, sizedType));
}
_uniqueUtf8FieldCache.Add (pair.Key, fields);
}
}
}
}

void AddUtf8Field (string value, TypeDefinitionHandle sizedType)
FieldDefinitionHandle AddUtf8Field (string value, TypeDefinitionHandle sizedType)
{
// Encode to null-terminated UTF-8 (all JNI names/signatures are ASCII).
_sigBlob.Clear ();
Expand All @@ -313,22 +340,33 @@ void AddUtf8Field (string value, TypeDefinitionHandle sizedType)
Metadata.GetOrAddBlob (_sigBlob));

Metadata.AddFieldRelativeVirtualAddress (fieldHandle, rva);

_utf8FieldCache [value] = fieldHandle;
return fieldHandle;
}

/// <summary>
/// Returns a previously prepared UTF-8 RVA field.
/// </summary>
public FieldDefinitionHandle GetUtf8Field (string value)
{
if (_utf8FieldCache.TryGetValue (value, out var existing)) {
if (_sharedUtf8FieldCache.TryGetValue (value, out var existing)) {
return existing;
}

throw new InvalidOperationException ($"UTF-8 field '{value}' was not prepared before type emission.");
}

/// <summary>
/// Returns and consumes one previously prepared unique UTF-8 RVA field.
/// </summary>
public FieldDefinitionHandle GetUniqueUtf8Field (string value)
{
if (_uniqueUtf8FieldCache.TryGetValue (value, out var fields) && fields.Count > 0) {
return fields.Dequeue ();
}

throw new InvalidOperationException ($"Unique UTF-8 field '{value}' was not prepared before type emission.");
}

void EnsurePrivateImplDetailsType ()
{
if (!_privateImplDetailsType.IsNil) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
Expand Down Expand Up @@ -195,7 +196,10 @@ void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
}
EmitMemberReferences ();

_pe.PrepareUtf8Fields (EnumerateNativeRegistrationStrings (model.ProxyTypes));
var validRegistrations = EnumerateValidNativeRegistrations (model.ProxyTypes);
_pe.PrepareUtf8Fields (
validRegistrations.Select (registration => registration.JniSignature),
validRegistrations.Select (registration => registration.JniMethodName));

// Track wrapper targets → handles for RegisterNatives.
var wrapperHandles = new Dictionary<UcoWrapperTargetData, MethodDefinitionHandle> ();
Expand All @@ -219,17 +223,30 @@ void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
_pe.EmitIgnoresAccessChecksToAttribute (model.IgnoresAccessChecksTo);
}

static IEnumerable<string> EnumerateNativeRegistrationStrings (IReadOnlyList<JavaPeerProxyData> proxies)
static List<NativeRegistrationData> EnumerateValidNativeRegistrations (IReadOnlyList<JavaPeerProxyData> proxies)
{
var wrapperTargets = new HashSet<UcoWrapperTargetData> ();
foreach (var proxy in proxies) {
foreach (var method in proxy.UcoMethods) {
wrapperTargets.Add (UcoWrapperTargetData.From (proxy, method.WrapperName));
}
foreach (var constructor in proxy.UcoConstructors) {
wrapperTargets.Add (UcoWrapperTargetData.From (proxy, constructor.WrapperName));
}
}

var registrations = new List<NativeRegistrationData> ();
foreach (var proxy in proxies) {
if (!proxy.IsAcw) {
continue;
}
foreach (var registration in proxy.NativeRegistrations) {
yield return registration.JniMethodName;
yield return registration.JniSignature;
if (wrapperTargets.Contains (registration.WrapperTarget)) {
registrations.Add (registration);
}
}
}
return registrations;
}

static List<JavaPeerProxyData> OrderProxiesForWrapperTargets (IReadOnlyList<JavaPeerProxyData> proxies)
Expand Down Expand Up @@ -1631,11 +1648,12 @@ void EmitRegisterNatives (JavaPeerProxyData proxy,
return;
}

// Get the prepared, deduplicated RVA fields for each unique name/signature string.
// Method names are unique per registration because R8 member mappings are owner-specific.
// Signatures remain safely deduplicated because descriptor class mappings are owner-independent.
var nameFields = new FieldDefinitionHandle [validRegs.Count];
var sigFields = new FieldDefinitionHandle [validRegs.Count];
for (int i = 0; i < validRegs.Count; i++) {
nameFields [i] = _pe.GetUtf8Field (validRegs [i].Reg.JniMethodName);
nameFields [i] = _pe.GetUniqueUtf8Field (validRegs [i].Reg.JniMethodName);
sigFields [i] = _pe.GetUtf8Field (validRegs [i].Reg.JniSignature);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
ProguardRuleOutput="$(_Aapt2ProguardRules)"
/>
<ItemGroup Condition=" '$(_Aapt2ProguardRules)' != '' And Exists('$(_Aapt2ProguardRules)') ">
<ProguardConfiguration Include="$(_Aapt2ProguardRules)" />
<ProguardConfiguration Include="$(_Aapt2ProguardRules)">
<AndroidGeneratedProguardConfiguration>true</AndroidGeneratedProguardConfiguration>
<AndroidAaptProguardConfiguration>true</AndroidAaptProguardConfiguration>
</ProguardConfiguration>
<FileWrites Include="$(_Aapt2ProguardRules)" />
<FileWrites Include="$(IntermediateOutputPath)android\*\aapt_rules.txt" />
</ItemGroup>
Expand Down
Loading