Eduard Boloș
06/03/2024, 3:09 PMcom.apollographql.apollo3.exception.JsonDataException: Expected an int but was 4294967297 at path [errors, 0, locations, 0, line]
(full stacktrace in 🧵)Eduard Boloș
06/03/2024, 3:10 PMcom.apollographql.apollo3.exception.JsonDataException: Expected an int but was 4294967297 at path [errors, 0, locations, 0, line]
at com.apollographql.apollo3.api.json.BufferedSourceJsonReader.nextInt(BufferedSourceJsonReader.kt:615)
at com.apollographql.apollo3.api.internal.ResponseParser.readErrorLocation(ResponseParser.kt:137)
at com.apollographql.apollo3.api.internal.ResponseParser.readErrorLocations(ResponseParser.kt:124)
at com.apollographql.apollo3.api.internal.ResponseParser.readError(ResponseParser.kt:80)
at com.apollographql.apollo3.api.internal.ResponseParser.readErrors(ResponseParser.kt:62)
at com.apollographql.apollo3.api.internal.ResponseParser.parse(ResponseParser.kt:34)
at com.apollographql.apollo3.api.Operations.parseJsonResponse(Operations.kt:63)
at com.apollographql.apollo3.network.http.HttpNetworkTransport.singleResponse(HttpNetworkTransport.kt:104)
at com.apollographql.apollo3.network.http.HttpNetworkTransport.access$singleResponse(HttpNetworkTransport.kt:37)
at com.apollographql.apollo3.network.http.HttpNetworkTransport$execute$1.invokeSuspend(HttpNetworkTransport.kt:91)
at com.apollographql.apollo3.network.http.HttpNetworkTransport$execute$1.invoke(HttpNetworkTransport.kt:0)
at com.apollographql.apollo3.network.http.HttpNetworkTransport$execute$1.invoke(HttpNetworkTransport.kt:0)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230)
at kotlinx.coroutines.flow.internal.ChannelFlowOperatorImpl.flowCollect(ChannelFlow.kt:195)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collect$suspendImpl(ChannelFlow.kt:167)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collect(ChannelFlow.kt:0)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt.catchImpl(Errors.kt:156)
at kotlinx.coroutines.flow.FlowKt.catchImpl(Unknown Source)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catch$$inlined$unsafeFlow$1.collect(SafeCollector.common.kt:114)
at com.apollographql.apollo3.cache.normalized.internal.ApolloCacheInterceptor$interceptMutation$1.invokeSuspend(ApolloCacheInterceptor.kt:160)
at com.apollographql.apollo3.cache.normalized.internal.ApolloCacheInterceptor$interceptMutation$1.invoke(ApolloCacheInterceptor.kt:0)
at com.apollographql.apollo3.cache.normalized.internal.ApolloCacheInterceptor$interceptMutation$1.invoke(ApolloCacheInterceptor.kt:0)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
...
Eduard Boloș
06/03/2024, 3:10 PM{"errors":[{"message":"Enter your secret code.","locations":[{"line":1,"column":134}],"path":["signup"],"code":"needs-pin"}],"data":{"signup":null}}
Eduard Boloș
06/03/2024, 3:11 PMEduard Boloș
06/03/2024, 3:12 PMbod
06/03/2024, 3:14 PM4294967297
comes frombod
06/03/2024, 3:15 PMEduard Boloș
06/03/2024, 3:19 PM2^31 + value
🙃bod
06/03/2024, 3:21 PMBufferedSourceJsonReader.peekNumber
if that's possible, or open a reproducer indeedbod
06/03/2024, 3:25 PMEduard Boloș
06/03/2024, 3:46 PMisDebuggable = true
for the release build variant, the bug is gonebod
06/03/2024, 4:01 PMprintln
is the only tool that can help us 🙂Eduard Boloș
06/03/2024, 4:27 PMbod
06/03/2024, 4:28 PMEduard Boloș
06/03/2024, 4:36 PMbod
06/04/2024, 8:04 AM4.0.0-beta.6
by any chance?Eduard Boloș
06/04/2024, 8:30 AMmbonnin
06/04/2024, 8:46 AMEduard Boloș
06/04/2024, 8:47 AMmbonnin
06/04/2024, 8:48 AMcould it be that the Kotlin update introduced the issue?Looks like a good candidate
mbonnin
06/04/2024, 8:50 AMEduard Boloș
06/04/2024, 8:56 AMmbonnin
06/04/2024, 8:58 AMgit checkout sha1_at_1.9
./gradlew :apollo-api:compileKotlinJvm
javap -v libraries/apollo-api/build/classes/kotlin/jvm/main/com/apollographql/apollo3/api/json/BufferedSourceJsonReader.class
git checkout sha1_before_1.9
./gradlew :apollo-api:compileKotlinJvm
javap -v libraries/apollo-api/build/classes/kotlin/jvm/main/com/apollographql/apollo3/api/json/BufferedSourceJsonReader.class
mbonnin
06/04/2024, 8:58 AMmbonnin
06/04/2024, 8:58 AMEduard Boloș
06/04/2024, 9:23 AMv4
branch in the repo linked above, fwiwEduard Boloș
06/04/2024, 9:24 AMandroid.enableR8.fullMode
set to both true and false respectively, same resultmbonnin
06/04/2024, 9:51 AMmbonnin
06/04/2024, 9:52 AMbod
06/04/2024, 9:53 AMbod
06/04/2024, 9:54 AMmbonnin
06/04/2024, 9:55 AMmbonnin
06/04/2024, 9:55 AMmbonnin
06/04/2024, 10:05 AMmbonnin
06/04/2024, 10:06 AMmbonnin
06/04/2024, 10:07 AMoatdump
on lollipop devices, that is the question...mbonnin
06/04/2024, 10:13 AMAleksandrs Vitjukovs
06/04/2024, 10:24 AMEduard Boloș
06/04/2024, 10:26 AMmbonnin
06/04/2024, 10:26 AMEduard Boloș
06/04/2024, 10:26 AMmbonnin
06/04/2024, 10:27 AMwould that explain that v3.8.2 works?My hunch is something in the dex triggers an optimization that doesn’t work on 21
mbonnin
06/04/2024, 10:27 AMmbonnin
06/04/2024, 10:29 AMEduard Boloș
06/04/2024, 10:29 AMmbonnin
06/04/2024, 10:30 AM5.0.2 (LRX22G)
?Eduard Boloș
06/04/2024, 10:32 AMmbonnin
06/04/2024, 10:32 AMEduard Boloș
06/04/2024, 10:34 AMmbonnin
06/04/2024, 10:42 AMEduard Boloș
06/04/2024, 11:06 AMpeekNumber()
, the constructed value
is negative (for a positive number, I mean): value = *-*(c - '0'.code.toByte()).toLong()
, val newValue = value * 10 *-* (c - '0'.code.toByte())
? Is there a reason for that? If I change the sign, then the bug goes away, fwiw.mbonnin
06/04/2024, 11:08 AMmbonnin
06/04/2024, 11:08 AMEduard Boloș
06/04/2024, 11:09 AMvar value: Long = 0 // Negative to accommodate Long.MIN_VALUE more easily.
Eduard Boloș
06/04/2024, 11:09 AMmbonnin
06/04/2024, 11:10 AMEduard Boloș
06/04/2024, 11:12 AMmbonnin
06/04/2024, 11:14 AMEduard Boloș
06/04/2024, 12:58 PMEduard Boloș
06/04/2024, 1:10 PMmbonnin
06/04/2024, 1:18 PMmbonnin
06/04/2024, 1:18 PMmbonnin
06/04/2024, 1:29 PMmbonnin
06/04/2024, 1:30 PMEduard Boloș
06/04/2024, 1:34 PMmbonnin
06/04/2024, 1:37 PMfitsInLong = fitsInLong and (value > MIN_INCOMPLETE_INTEGER) || value == MIN_INCOMPLETE_INTEGER && newValue < value
mbonnin
06/04/2024, 1:38 PMfitsInLong = fitsInLong and (value < MAX_INCOMPLETE_INTEGER) || value == MAX_INCOMPLETE_INTEGER && newValue > value
mbonnin
06/04/2024, 1:39 PMPEEKED_NUMBER
(“parse string”) case.mbonnin
06/04/2024, 1:40 PMnextLong()
in the API)Eduard Boloș
06/04/2024, 1:49 PMAlso the test must check that we’re not falling back to theHmm, not sure how to write a test for that, both(“parse string”) case.PEEKED_NUMBER
doPeek
and peekNumber
are private. Should I just do a step-by-step through them to see? Or should I make them internal? 😬mbonnin
06/04/2024, 1:49 PMmbonnin
06/04/2024, 1:50 PMmbonnin
06/04/2024, 1:50 PMEduard Boloș
06/04/2024, 1:51 PMmbonnin
06/04/2024, 1:51 PMvalue == MAX_INCOMPLETE_INTEGER && newValue < value
and so fitInLongs
becomes false and so we allocate a stringEduard Boloș
06/04/2024, 1:51 PMEduard Boloș
06/04/2024, 1:51 PMmbonnin
06/04/2024, 1:52 PM9223372036854775808
cannot be represented in a LongEduard Boloș
06/04/2024, 2:03 PMmbonnin
06/04/2024, 2:04 PMmbonnin
06/04/2024, 2:04 PMEduard Boloș
06/04/2024, 2:04 PMmbonnin
06/04/2024, 2:05 PMvalue
becomes 0x100000001
?Eduard Boloș
06/04/2024, 2:06 PMmbonnin
06/04/2024, 2:06 PMmbonnin
06/04/2024, 2:10 PMfitsInLong
branch anyway. It’s a single “1”mbonnin
06/04/2024, 2:11 PMmbonnin
06/04/2024, 2:17 PM"1"
• We track the negative value so value = -1
. In hex, this is 0xffffffffffffffff
• Then we invert this
◦ complement: 0x00000000000000000
◦ add 1: 0x0000000000000001
• And we have "1"
as a longmbonnin
06/04/2024, 2:19 PMEduard Boloș
06/04/2024, 2:32 PMXXX: c: 49, c - '0'.code.toByte(): 1, -(c - '0'.code.toByte()).toLong(): -1371107853288341505
after: XXX: c: 49, c - '0'.code.toByte(): 1, (c - '0'.code.toByte()).toLong(): 1, -(c - '0'.code.toByte()).toLong(): -1
in the NUMBER_CHAR_SIGN, NUMBER_CHAR_NONE
caseEduard Boloș
06/04/2024, 2:32 PMEduard Boloș
06/04/2024, 2:39 PM(c - '0'.code.toByte()).toLong().also { value = -it }
This works lolEduard Boloș
06/04/2024, 2:39 PMmbonnin
06/04/2024, 2:42 PMmbonnin
06/04/2024, 2:42 PMmbonnin
06/04/2024, 2:43 PMEduard Boloș
06/04/2024, 2:44 PMEduard Boloș
06/04/2024, 2:45 PMmbonnin
06/04/2024, 2:54 PMEduard Boloș
06/04/2024, 3:08 PMvalue = -(c - '0'.code.toByte()).toLong()
and (c - '0'.code.toByte()).toLong().also { value = -it }
is the same when code is optimized with R8. Given these, how keen would you be to merging that change in? 🙏 This would make a couple thousand people very happy, and it would give them back access to affordable financial services 😄Eduard Boloș
06/04/2024, 4:03 PMis the same when code is optimized with R8Hmm, actually, if I turn on R8 in the repro project, no bug... which prompted me to check the ProGuard output in our project, where we have R8 enabled... and it looks like the Apollo classes are not being optimized 😅 So that explains the bug too... but no idea why they are not optimized. Gonna investigate some more.
mbonnin
06/04/2024, 4:05 PMmbonnin
06/04/2024, 4:06 PMbod
06/04/2024, 4:16 PM"proguard-android-optimize.txt"
to "proguard-android.txt"
change anything?Eduard Boloș
06/04/2024, 4:26 PM"proguard-android.txt"
. Maybe we should use "proguard-android-optimize.txt"
instead? 😄mbonnin
06/04/2024, 4:45 PMbod
06/04/2024, 4:46 PMwe useyour repro project uses the optimize one so I guess that's not the culprit. Was worth a try I guess 😅. Maybe we should use"proguard-android.txt"
instead? 😄"proguard-android-optimize.txt"
Eduard Boloș
06/04/2024, 4:51 PM"proguard-android-optimize.txt"
does help? In our real project we use the other file. Unfortunately, I can't test this anymore today, I will have to wait until tomorrow to get access to the Nexus 7 again.bod
06/04/2024, 4:52 PMisMinifyEnabled = false
Aleksandrs Vitjukovs
06/04/2024, 5:54 PMmbonnin
06/04/2024, 6:29 PMmbonnin
06/04/2024, 6:29 PMAleksandrs Vitjukovs
06/04/2024, 6:34 PMproguard-android.txt
to proguard-android-optimize.txt
. That's it. I'm not sure if that's the same as enabling R8?Stylianos Gakis
06/04/2024, 6:34 PMmbonnin
06/04/2024, 6:35 PMbod
06/04/2024, 6:38 PMprintln
also un-triggered it IIUC?) .
However, to be honest, this could potentially trigger other issues 🙈Eduard Boloș
06/04/2024, 9:03 PMdon't leave people hanging. It did workYes, sorry, Alex was able to test the change very last minute 😄 proguard-android.txt contains
-dontoptimize
, whereas proguard-android-optimize.txt does not, but it has some exceptions (-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
). I really don't expect issues with these optimizations, as now they are the default.
This is what ultimately triggered me to get to the solution, the fact that in Kotlin Explorer (thanks Martin for reminding me about it) the DEX code for both value = -(c - '0'.code.toByte()).toLong()
and (c - '0'.code.toByte()).toLong().also { value = -it }
were the same when "Optimize with R8" was checked, and obviously Benoit's question about which rules were used (thanks! 😄).
Now I no longer have the DEX code around, but what I noticed was that the optimized dex code was using one fewer register to calculate the value than the non-optimized version. So I imagine that somehow that extra register was getting corrupted? Not sure though, just an assumption.
Anyway, thanks Martin and Benoit for all your support, and your patience! You were great as always! I just wish I would've noticed earlier that I didn't have ProGuard enabled on the repro project, so that we wouldn't have reached 100+ messages on this thread 😄 But oh, well, at least I feel like a learned a lot on this journey 🙂
Until next time! 👋