Status Update
Comments
sg...@google.com <sg...@google.com>
sg...@google.com <sg...@google.com> #2
Thank you for sharing this sample and the solution you found.
Looking at the code which is failing I don't understand why there is a java.lang.IllegalAccessError
as both the class org.ccil.cowan.tagsoup.Parser
and the field theContentHandler
are public
, so access should be allowed from anywhere (including com.example.sample.MainActivity$$ExternalSyntheticLambda0
).
Opened internal issue
One interesting observation is that running the following code in the failing program:
public static void testAccess() throws Exception {
Class<?> clazz = Class.forName("org.ccil.cowan.tagsoup" + (System.currentTimeMillis() > 0 ? ".Parser" : ".Parser1"));
System.out.println("Modifier PUBLIC: " + Modifier.PUBLIC + "\n");
System.out.println("Modifier PRIVATE: " + Modifier.PRIVATE + "\n");
System.out.println("Class modifiers: " + clazz.getModifiers() + "\n");
Field field = clazz.getDeclaredField("theContentHandler");
System.out.println("Field modifiers: " + field.getModifiers() + "\n");
}
Prints the following:
System.out: Modifier PUBLIC: 1
System.out: Modifier PRIVATE: 2
System.out: Class modifiers: 1
System.out: Field modifiers: 2
Indicating that the field is seen as private from reflection.
sg...@google.com <sg...@google.com> #3
Turns out the reason for this issue is quite obvious but also somewhat unexpected. The Android runtime includes the class org.ccil.cowan.tagsoup.Parser
! This means that even when you have the class in your program the class from the runtime takes precedence. That class still have the instance fields as private fields. The fact that R8 made the members public to be able to inline the constructor and set the instance fields outside the constructor has no effect as the runtime provided instance still has the private fields.
You can check this by creating a new "Empty Activity" (Compose app) and replace:
text = "Hello ${name}!",
with
text = "Hello ${Class.forName("org.ccil.cowan.tagsoup.Parser").canonicalName}!",
This will succeed even when the app does not depend on the org.ccil.cowan.tagsoup:tagsoup:1.2
.
R8 knows about the classes provided by the runtime through the android.jar
(from the Platfrom SDK), but at that does not contain org.ccil.cowan.tagsoup.Parser
R8 assumes it is a program class and it can optimize as much as possible.
If you want to use the org.ccil.cowan.tagsoup:tagsoup:1.2
library I suggest that you use a tool like jarjar
to repackage it into a new namespace so your use does not conflict with the runtime use. I cannot recommend using the class provided by the runtime by just adding keep rules so the program version might match the runtime version. Different runtimes might have different versions and some runtimes might not have it at all. Also the runtime might become optimized by R8 maybe leaving the class with the same name, but with some methods renamed or removed.
Description
Hello!
We are currently updating our legacy codebase to support the latest version of Android Gradle Plugin (AGP) with R8 optimization enabled. During testing, we've encountered a crash specifically in the release build of our application.
Through investigation, we discovered that adding the following ProGuard rule resolves the issue:
We'd greatly appreciate your assistance in properly fixing this bug.
For your reference, I've attached a demo application that reproduces the issue. While the "TEST" button functions normally in debug builds without any crashes, the release build consistently crashes on the first button click with an "IllegalAccessError" exception.
Thank you!