Status Update
Comments
ch...@google.com <ch...@google.com> #2
Thanks for the feedback. Would it be possible for you to share a compiler dump file, which contains the inputs to the R8 compilation? This includes the app's Java class files, keep rules etc., and makes it possible to reproduce the given build.
You can generate such a file by running:
./gradlew assembleRelease --no-daemon -Dcom.android.tools.r8.dumpinputtodirectory=/path/to/dumps
You can share this artifact privately with
da...@gmx.at <da...@gmx.at> #3
I just sent you an e-mail with the requested file.
sg...@google.com <sg...@google.com> #4
Thank you for the provided sample project. Looking at the input, input has this constructor for com.example.DemoModelB
:
public com.example.DemoModelB(int, int, kotlinx.serialization.internal.SerializationConstructorMarker);
descriptor: (IILkotlinx/serialization/internal/SerializationConstructorMarker;)V
flags: (0x1001) ACC_PUBLIC, ACC_SYNTHETIC
Code:
stack=3, locals=4, args_size=4
0: iconst_1
1: iconst_1
2: iload_1
3: iand
4: if_icmpeq 18
7: iload_1
8: iconst_1
9: getstatic #79 // Field com/example/DemoSerializerB.INSTANCE:Lcom/example/DemoSerializerB;
12: invokevirtual #83 // Method com/example/DemoSerializerB.getDescriptor:()Lkotlinx/serialization/descriptors/SerialDescriptor;
15: invokestatic #89 // Method kotlinx/serialization/internal/PluginExceptionsKt.throwMissingFieldException:(IILkotlinx/serialization/descriptors/SerialDescriptor;)V
18: aload_0
19: iload_1
20: aload_3
21: invokespecial #92 // Method com/example/DemoModelBase."<init>":(ILkotlinx/serialization/internal/SerializationConstructorMarker;)V
24: aload_0
25: iload_2
26: putfield #13 // Field id:I
29: return
The original reference to com/example/DemoSerializerB.INSTANCE
is here af pc 9. However the class com.example.DemoSerializerB
has no field INSTANCE
and no method getDescriptor()
(it only has a constructor). Looking at the code it looks as if com.example.DemoModelB$$serializer
has the field INSTANCE
and method getDescriptor()
.
To trigger the branch with the access to INSTANCE
you can do:
println(demo.test("{}", DemoModelType.B))
Which in debug mode will throw:
java.lang.NoSuchFieldError: No static field INSTANCE of type Lcom/example/DemoSerializerB; in class Lcom/example/DemoSerializerB; or its superclasses
So the code never gets to the throwMissingFieldException
which I think is intended.
When R8 see references to missing items it tries to leave it alone to give the same runtime exception. In this case the exception is changed from java.lang.NoSuchFieldError
to
java.lang.NoClassDefFoundError: Failed resolution of: LT0/DemoSerializerB;
Changing the runtime exception here is not optimal, but it seems like the Kotlin serialization code has some issues to trigger this.
sg...@google.com <sg...@google.com> #5
Opened
Info on reproduction received by email:
To make reproduction easier, I created a demo application here:
- 764cc890c156bb4ff85cba8237138dc63d77f6c1 The first commit just creates an "Empty Activity" project with the default configuration of Android Studio (2024.3.1 Patch 1).
- 1591e94b84b9a6a110f67604ad287d190eb78345 Then, the code demonstrating the issue is added. So this is probably the most interesting part.
- 6ce2c8285f993d0b222761929b02554f6880f41a This one just adds a dummy signing configuration so the application can actually be installed on a device or in an emulator.
Investigating the DEX file from the resulting APK I get the result described in the issue:
$ ~/Android/Sdk/build-tools/35.0.0/dexdump -d classes.dex | grep -F
DemoSerializer
0694ce: 6204 2504 |000b: sget-object v4,
LT0/DemoSerializerB;.INSTANCE:LT0/DemoSerializerB; // field@0425
da...@gmx.at <da...@gmx.at> #6
I still need to go through the details, but I am confused by the issue you opened in kotlinx-datetime
. Should this be an issue in the kotlinx-serialization
repository?
sg...@google.com <sg...@google.com> #7
Sorry about using the wrong GitHub project and thank you for pointing it out. (I had kotlinx-datetime
open in a tab looking at some other issue, and didn't look close enough). Opened
sg...@google.com <sg...@google.com> #8
da...@gmx.at <da...@gmx.at> #9
Thanks for investigating this issue and taking care of getting it fixed in kotlinx-serialization
.
To my understanding it is not the NoSuchFieldError
or its replacement that is thrown in my case. Instead, the exception is raised by the runtime verifier right before the method is invoked.
Quote from my initial message:
However, I get a
VerifyError
because of the invalid class reference when loading the corresponding DEX file dynamically at runtime. Is there any difference in the verification of DEX files between those that are part of the APK and those that are loaded dynamically?
Do you have any explanation for this behavior? To me it would be understandable if the verifier would reject this (dead) code, but why is this not always the case?
Description
I am experiencing a strange behavior of the R8 minifier when using Kotlin serialization (https://github.com/Kotlin/kotlinx.serialization ). This is a minimal mockup of the code added to my Android application with which I can still reproduce the issue:
Compilation finishes successfully, but on closer inspection I find the following in the DEX file extracted from the APK:
So
DemoSerializerB
is still accessed in the DEX code. Note that this is the only result ofgrep
, there is no definition of theDemoSerializerB
class. According tomapping.txt
the constructor ofDemoSerializerB
was inlined into the one ofDemoSerializerA
. There are no further mentions ofDemoSerializerB
in the R8 output files. I am wondering why this does not cause any issues, the code surprisingly (at least to me) works as expected.Out of curiosity I tried to lookup the class at runtime right after its usage (according to the DEX code):
But the class can not be found:
Can someone please explain this behavior? What puzzles me even more than the occurrence of
DemoSerializerB
without definition is that this DEX code actually works fine (in the usual scenario) :)However, I get a
VerifyError
because of the invalid class reference when loading the corresponding DEX file dynamically at runtime. Is there any difference in the verification of DEX files between those that are part of the APK and those that are loaded dynamically?Versions: