Skip to content

[r8] Enable private-member obfuscation and optimization by default - #12668

Open
jonathanpeppers wants to merge 4 commits into
dotnet:mainfrom
jonathanpeppers:jonathanpeppers-selective-r8-obfuscation
Open

[r8] Enable private-member obfuscation and optimization by default#12668
jonathanpeppers wants to merge 4 commits into
dotnet:mainfrom
jonathanpeppers:jonathanpeppers-selective-r8-obfuscation

Conversation

@jonathanpeppers

@jonathanpeppersjonathanpeppers commented Sep 3, 2026

Copy link
Copy Markdown
Member

Description

Enable R8 optimization and selective obfuscation by default while preserving JNI-sensitive Java names without rewriting managed IL.

The selective obfuscation defaults keep Java class names and public/protected member names stable while allowing private and package-private members to be renamed:

-keep,allowshrinking,allowoptimization class **
-keepclassmembers,allowshrinking,allowoptimization class ** {
public protected *;
}

The SDK now ships both Android default R8 configurations and selects proguard-android-optimize.txt by default. Runtime rules explicitly preserve net.dot.android.ApplicationRegistration, which managed code loads by name. NativeAOT also preserves the package-private java.lang.Class fields on mono.android.Runtime because native hosts resolve them by literal name during startup.

Escape hatches

$(_AndroidR8DontObfuscate) and $(_AndroidR8DontOptimize) are private escape hatches only. Setting either to true restores the corresponding previous global behavior (-dontobfuscate or the legacy non-optimizing Android configuration). They are not supported public build properties or alternative configuration modes, and both remain blank by default.

Tests

  • Added R8ObfuscationTests device coverage for CoreCLR and NativeAOT using an AndroidJavaSource probe. It verifies stable class/public/protected names, renamed private/package-private methods, and successful JNI execution.
  • Extended AAB metadata coverage for CoreCLR and NativeAOT to verify optimization is enabled by default and disabled by the escape hatch.
  • Updated the affected XForms CoreCLR/R8 app-size baseline.

Preserve Java class names and public/protected member names while allowing R8 to obfuscate private and package-private members. Keep _AndroidR8DontObfuscate as a private escape hatch that only restores the previous global -dontobfuscate behavior.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf67f43d-2eef-46ac-9d52-e9a677671fc8

CopilotAI 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.

🟡 Changes recommended

The new device test can leave apps installed on failure paths, which risks flakiness and cross-test interference in device CI runs.

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

Pull request overview

This PR changes the default R8 behavior to allow obfuscation of private/package-private Java implementation details while keeping Java class names and public/protected member names stable for JNI compatibility, with a private MSBuild escape hatch to restore the previous global -dontobfuscate behavior.

Changes:

  • Add a new selective-obfuscation default by generating base ProGuard/R8 rules at build time (and wiring an internal $(_AndroidR8DontObfuscate) toggle).
  • Update embedded ProGuard config resources to no longer globally disable obfuscation, and add a NativeAOT-specific keep rule for mono.android.Runtime startup fields.
  • Add device integration tests validating mapping + DEX output + runtime execution for CoreCLR and NativeAOT.
File summaries
FileDescription
tests/MSBuildDeviceIntegration/Tests/R8ObfuscationTests.csAdds device test coverage verifying selective obfuscation via mapping/DEX inspection and a runtime probe.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targetsPlumbs a private MSBuild escape hatch property into the R8 task invocation.
src/Xamarin.Android.Build.Tasks/Tasks/R8.csImplements default selective-obfuscation ProGuard directives generation and supports DontObfuscate.
src/Xamarin.Android.Build.Tasks/Resources/proguard_xamarin.cfgRemoves the global -dontobfuscate default from the common R8 config resource.
src/Xamarin.Android.Build.Tasks/Resources/proguard_trimmable_nativeaot.cfgRemoves -dontobfuscate and adds a keep rule for mono.android.Runtime fields needed at NativeAOT startup.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment threadtests/MSBuildDeviceIntegration/Tests/R8ObfuscationTests.cs Outdated
Track successful installation and attempt uninstall from a finally block so assertion failures do not leave packages on the test device.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf67f43d-2eef-46ac-9d52-e9a677671fc8
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf67f43d-2eef-46ac-9d52-e9a677671fc8
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12668

@github-actionsgithub-actionsBot 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.

⚠️Needs Changes — 0 errors, 1 warning, 0 suggestions.

The selective R8 rules preserve class and JNI-visible public/protected member names while allowing private implementation details to be renamed, and the runtime-specific keep configuration is consistent with the native field lookups. The new device coverage exercises both CoreCLR and NativeAOT, but its shared logcat marker is not isolated between parameterized cases and can produce a false pass; please address the inline testing comment. CI is green.

Generated by Android PR Reviewer for #12668 · gpt56 · 125.5 AIC · ⌖ 9.02 AIC · ⊞ 25.7K
Comment /review to run again

AssertDexDoesNotContainMethod (dexFiles, "packagePrivateEntry");
AssertDexDoesNotContainMethod (dexFiles, "privateEntry");

RunProjectAndAssert (proj, builder, doNotCleanupOnUpdate: true);

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.

🤖 ⚠️Testing — Clear logcat immediately before launching this case (as the neighboring device tests do), or make SuccessMarker runtime-specific. MonitorAdbLogcat() first scans all buffered output via logcat -d, and both TestCases emit the same marker, so the NativeAOT case can satisfy line 134 with the earlier CoreCLR run even if the NativeAOT app never reaches this code.

Rule: Deterministic test isolation

@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

The changes here, get us this far with a dotnet new maui -sc project template:

image

Previously, obfuscation was at 0%.

I'm investigating what else is missing, if we should add more changes here or a separate PR.

Use Android's optimized default R8 configuration unless the private _AndroidR8DontOptimize escape hatch is enabled. Ship both configurations and preserve ApplicationRegistration, which is loaded by name from managed code.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf67f43d-2eef-46ac-9d52-e9a677671fc8
@jonathanpeppersjonathanpeppers changed the title [r8] Obfuscate private Java members by default[r8] Enable private-member obfuscation and optimization by defaultSep 4, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

This is now getting this far:

image

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jonathanpeppers