Anamorphosee / stacktrace-decoroutinator

Small lib for recovering stack trace in exceptions thrown in Kotlin coroutines
Apache License 2.0
202 stars 5 forks source link

Can't get it to work with proguard with minifyEnabled true #28

Closed nordfalk closed 4 months ago

nordfalk commented 4 months ago

Hi, I want to get good stack traces in a production Android app environment - ie with using proguard, and enabling minify:

buildTypes {
    release {
        minifyEnabled true

However, when I use a coroutine, the app crashes:

         kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for DispatchedContinuation[Dispatchers.IO, dk.dinero.dinero.app.navigation.NavigationViewModel$loadOrganizations$1@c023e0]. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
            at kotlinx.coroutines.i0.e(DispatchedTask.kt:43)
            at kotlinx.coroutines.i0.run(DispatchedTask.kt:213)
            at kotlinx.coroutines.internal.n$a.run(LimitedDispatcher.kt:4)
            at sc.i.run(Tasks.kt:3)
            at kotlinx.coroutines.scheduling.CoroutineScheduler.l(CoroutineScheduler.kt:1)
            at kotlinx.coroutines.scheduling.CoroutineScheduler$c.d(CoroutineScheduler.kt:15)
            at kotlinx.coroutines.scheduling.CoroutineScheduler$c.p(CoroutineScheduler.kt:29)
            at kotlinx.coroutines.scheduling.CoroutineScheduler$c.run(CoroutineScheduler.kt:1)
            Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [r1{Active}@4a0ee56, Dispatchers.IO]
         Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Ldev/reformator/stacktracedecoroutinator/common/Registry_commonKt;
            at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(continuation-stdlib.kt:21)
            at kotlinx.coroutines.i0.run(DispatchedTask.kt:129)
            ... 6 more
         Caused by: java.lang.ClassNotFoundException: dev.reformator.stacktracedecoroutinator.common.Registry_commonKt
            ... 8 more

If I add -keep directives to proguard-rules.pro, like -keep class dev.reformator.stacktracedecoroutinator.** { *; } the problem just keeps popping up in a new place in kotlin libraries (kotlin.jvm.internal, kotlin.collection, ...).

Adding

-keep class kotlin.** { *; }
-keep class dev.reformator.stacktracedecoroutinator.** { *; }

the problem pops up in my own code:

         kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for DispatchedContinuation[Dispatchers.IO, Continuation at dk.dinero.dinero.app.navigation.NavigationViewModel$loadOrganizations$1.invokeSuspend(NavigationViewModel.kt)@cd60016]. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
            at kotlinx.coroutines.i0.e(DispatchedTask.kt:43)
            at kotlinx.coroutines.i0.run(DispatchedTask.kt:213)
            at kotlinx.coroutines.internal.n$a.run(LimitedDispatcher.kt:4)
            at ab.i.run(Tasks.kt:3)
            at kotlinx.coroutines.scheduling.CoroutineScheduler.l(CoroutineScheduler.kt:1)
            at kotlinx.coroutines.scheduling.CoroutineScheduler$c.d(CoroutineScheduler.kt:15)
            at kotlinx.coroutines.scheduling.CoroutineScheduler$c.p(CoroutineScheduler.kt:29)
            at kotlinx.coroutines.scheduling.CoroutineScheduler$c.run(CoroutineScheduler.kt:1)
            Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [r1{Active}@ed18957, Dispatchers.IO]
         Caused by: java.lang.ExceptionInInitializerError
            at dev.reformator.stacktracedecoroutinator.common.Utils_commonKt.getLookup(utils-common.kt:1)
            at dev.reformator.stacktracedecoroutinator.common.BaseDecoroutinatorStacktraceMethodHandleRegistry.regenerateClassesForMissingElements(methodHandleRegistry-common.kt:393)
            at dev.reformator.stacktracedecoroutinator.common.BaseDecoroutinatorStacktraceMethodHandleRegistry.getStacktraceMethodHandles(methodHandleRegistry-common.kt:26)
            at dev.reformator.stacktracedecoroutinator.stdlib.StdlibKt.fillStacktraceArrays(stdlib.kt:75)
            at dev.reformator.stacktracedecoroutinator.stdlib.StdlibKt.decoroutinatorResumeWith(stdlib.kt:33)
            at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(continuation-stdlib.kt:27)
            at kotlinx.coroutines.i0.run(DispatchedTask.kt:129)
            ... 6 more
         Caused by: java.lang.NoSuchMethodException: a.unknown [class [Ljava.lang.invoke.MethodHandle;, class [I, int, interface java.util.function.BiFunction, class java.lang.Object, class java.lang.Object]
            at java.lang.Class.getMethod(Class.java:2072)
            at java.lang.Class.getDeclaredMethod(Class.java:2050)
            at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:782)
            at dev.reformator.stacktracedecoroutinator.common.Utils_commonKt.<clinit>(utils-common.kt:55)
            ... 13 more

Is it possible to get stack traces with minify/obfuscated code? Or is this library just plan not unusable in production?

Anamorphosee commented 4 months ago

Hi, @nordfalk Could you please try this Proguard config:

-keep class kotlin.** { *; }
-keep class dev.reformator.stacktracedecoroutinator.** { *; }
-keep class UnknownKt { *; }
-keep @kotlin.coroutines.jvm.internal.DebugMetadata class * { *; }

If it will help I'll think how to simplify the using of Proguard.

nordfalk commented 4 months ago

Thanks you, it seems to work !

Do you have a performance hit estimate on Android, or do you have some suggestions how I would make one ?

I dont mind a minor performance penalty for end users - having good stack traces will speed up development significantly, and they'll benefit from that :-)

Anamorphosee commented 4 months ago

I tried to take some measurements (https://github.com/Anamorphosee/stacktrace-decoroutinator/blob/master/stacktrace-decoroutinator-android/src/androidTest/kotlin/performance-test-android.kt) but only in the simulator. In my case median performance hit is about 1ms for the stack of depth 10. If you want to measure yourself, pay attention that the first "awaking" of each class is much slower than the second and subsequent ones.

Anamorphosee commented 4 months ago

Since 2.3.9 ProGuard config can be simplified to:

-keep class kotlin.** { *; }
-keep class dev.reformator.stacktracedecoroutinator.** { *; }
-keep @kotlin.coroutines.jvm.internal.DebugMetadata class * { *; }