diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a45ec3fe37..c8c0ac1e5ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Prevents inclusion of `null.` prefix before default-package class names when parsing Java and JNI frames from Android ANR thread dumps ([#5979](https://github.com/getsentry/sentry-java/pull/5979)) + ### Dependencies - Bump Native SDK from v0.16.2 to v0.16.3 ([#5962](https://github.com/getsentry/sentry-java/pull/5962)) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java index 08432d2f41d..bfb829ce1a9 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java @@ -275,7 +275,7 @@ private SentryStackTrace parseStacktrace( final SentryStackFrame frame = new SentryStackFrame(); final String packageName = javaRe.group(1); final String className = javaRe.group(2); - final String module = String.format("%s.%s", packageName, className); + final String module = toModuleName(packageName, className); frame.setModule(module); frame.setFunction(javaRe.group(3)); frame.setFilename(javaRe.group(4)); @@ -312,7 +312,7 @@ private SentryStackTrace parseStacktrace( final SentryStackFrame frame = new SentryStackFrame(); final String packageName = jniRe.group(1); final String className = jniRe.group(2); - final String module = String.format("%s.%s", packageName, className); + final String module = toModuleName(packageName, className); frame.setModule(module); frame.setFunction(jniRe.group(3)); frame.setInApp(stackTraceFactory.isInApp(module)); @@ -390,6 +390,11 @@ private SentryStackTrace parseStacktrace( return stackTrace; } + private @NotNull String toModuleName( + final @Nullable String packageName, final @NotNull String className) { + return packageName == null || packageName.isEmpty() ? className : packageName + "." + className; + } + private void markThreads() { for (final @NotNull SentryThread thread : threads) { if (Boolean.TRUE.equals(thread.isMain())) { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt index c5798be2111..e514ecbb88b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt @@ -2,7 +2,9 @@ package io.sentry.android.core.internal.threaddump import io.sentry.SentryLockReason import io.sentry.SentryOptions +import java.io.BufferedReader import java.io.File +import java.io.StringReader import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -219,6 +221,56 @@ class ThreadDumpParserTest { assertEquals("main", threads.first().name) } + @Test + fun `uses class name as module when a Java frame has no package`() { + val lines = + Lines.readLines( + BufferedReader( + StringReader( + """ + ----- pid 123 at 2023-04-04 22:06:31.064728684+0200 ----- + "main" prio=5 tid=1 Runnable + | sysTid=123 nice=-10 cgrp=top-app sched=0/0 handle=0x7deceb74f8 + at MainActivity.run(MainActivity.java:177) + """ + .trimIndent() + ) + ) + ) + val parser = ThreadDumpParser(SentryOptions(), false) + + parser.parse(lines) + + val frame = parser.threads.single().stacktrace!!.frames!!.last() + assertEquals("MainActivity", frame.module) + } + + @Test + fun `uses class name as module when a JNI frame has no package`() { + val lines = + Lines.readLines( + BufferedReader( + StringReader( + """ + ----- pid 123 at 2023-04-04 22:06:31.064728684+0200 ----- + "main" prio=5 tid=1 Runnable + | sysTid=123 nice=-10 cgrp=top-app sched=0/0 handle=0x7deceb74f8 + at MainActivity.nativeRun(Native method) + """ + .trimIndent() + ) + ) + ) + val parser = ThreadDumpParser(SentryOptions(), false) + + parser.parse(lines) + + val frame = parser.threads.single().stacktrace!!.frames!!.last() + assertEquals("MainActivity", frame.module) + assertEquals("nativeRun", frame.function) + assertEquals(true, frame.isNative) + } + @Test fun `thread dump garbage`() { val lines = Lines.readLines(File("src/test/resources/thread_dump_bad_data.txt"))