sentry-java icon indicating copy to clipboard operation
sentry-java copied to clipboard

Recover Jetpack Compose stacktraces

Open romtsn opened this issue 3 years ago • 3 comments

Problem Statement

Since compose is a declarative UI framework, there's a big usage of lambdas, which get transformed into anonymous classes, therefore stackframes that contain compose code do not really look helpful (besides the file name and line numbers, it's hard to figure out what's going on). For example:

FATAL EXCEPTION: main
                 Process: com.google.samples.apps.nowinandroid.debug, PID: 2799
                 java.lang.RuntimeException: TEST
                 	at com.google.samples.apps.nowinandroid.feature.topic.TopicScreenKt$TopicRoute$1.invoke(TopicScreen.kt:71)
                 	at com.google.samples.apps.nowinandroid.feature.topic.TopicScreenKt$TopicRoute$1.invoke(TopicScreen.kt:66)
                 	at com.google.samples.apps.nowinandroid.core.ui.component.ChipKt$NiaFilterChip$1$1.invoke(Chip.kt:48)
                 	at com.google.samples.apps.nowinandroid.core.ui.component.ChipKt$NiaFilterChip$1$1.invoke(Chip.kt:48)
                 	at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke-k-4lQ0M(Clickable.kt:153)
                 	at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke(Clickable.kt:142)
                 	at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1$1.invokeSuspend(TapGestureDetector.kt:220)
                 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                 	at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
                 	at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
                 	at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
                 	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
                 	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
                 	at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
                 	at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:560)
                 	at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:452)
                 	at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:465)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:310)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
                 	at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:179)
                 	at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:98)
                 	at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80)
                 	at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1261)
                 	at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1211)
                 	at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1150)
                 	at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
                 	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
                 	at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
                 	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
                 	at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
                 	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
                 	at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
                 	at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
                 	at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:488)
                 	at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1871)

It'd be nice if we can recover the stackframes and make them pretty by including the actual lambda argument names (in this example it'd be NiaFilterChip$onClick and TopicRoute$onFollowClick instead of NiaFilterChip$1$1 and TopicRoute$1 respectively).

Solution Brainstorm

Probably would require some logic on the bytecode level (either through SAGP or a jvm-agent) to collect the information about lambdas and add them to the proguard-mapping file so they get correctly interpreted on the product side (or maybe we'd require some new logic on the product side as well).

https://github.com/Guardsquare/kotlin-metadata-printer might be useful to read kotlin's metadata.

romtsn avatar May 30 '22 11:05 romtsn

@romtsn do you know if there's a way to trace back to the original stack trace frames? e.g. https://github.com/getsentry/sentry-java/issues/1524#issuecomment-1135506317 I often see libs doing this (rewriting the frames), e.g. for coroutines and rxjava, the problem is kinda the same, I guess.

marandaneto avatar May 31 '22 16:05 marandaneto

Yeah the problem is the usage of lambdas/anonymous classes for all of them I guess. I feel like this can be done through bytecode manipulation (e.g. getting the argument name for lambda), but not sure if there's a silver bullet for all.

romtsn avatar May 31 '22 19:05 romtsn

We should check if there's a library for doing this. If we can find a workaround for the meantime we should add it to the troubleshooting page.

There's a flag in the coroutines library that makes it print more verbose stack traces. This could help.

adinauer avatar Jun 29 '22 13:06 adinauer

We've decided it's not worth the effort, and it's not clear what should be the outcome. At the moment, linenos are still correct and show you exactly where the error has happened. When #633 is in place, this should help even more and show surrounding code.

romtsn avatar Apr 07 '23 08:04 romtsn