Skip to content

Commit b9f7b6b

Browse files
authored
Merge 1ee2738 into 70118e9
2 parents 70118e9 + 1ee2738 commit b9f7b6b

File tree

4 files changed

+83
-10
lines changed

4 files changed

+83
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Fix crash when unregistering `SystemEventsBroadcastReceiver` with try-catch block. ([#5106](https://github.com/getsentry/sentry-java/pull/5106))
8+
- Identify and correctly structure Java/Kotlin frames in mixed Tombstone stack traces. ([#5116](https://github.com/getsentry/sentry-java/pull/5116))
89

910
## 8.33.0
1011

sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ public class TombstoneParser implements Closeable {
3333
@Nullable private final String nativeLibraryDir;
3434
private final Map<String, String> excTypeValueMap = new HashMap<>();
3535

36+
private static boolean isJavaFrame(@NonNull final TombstoneProtos.BacktraceFrame frame) {
37+
final String fileName = frame.getFileName();
38+
return !fileName.endsWith(".so")
39+
&& !fileName.endsWith("app_process64")
40+
&& (fileName.endsWith(".jar")
41+
|| fileName.endsWith(".odex")
42+
|| fileName.endsWith(".vdex")
43+
|| fileName.endsWith(".oat")
44+
|| fileName.startsWith("[anon:dalvik-")
45+
|| fileName.startsWith("<anonymous:")
46+
|| fileName.startsWith("[anon_shmem:dalvik-")
47+
|| fileName.startsWith("/memfd:jit-cache"));
48+
}
49+
3650
private static String formatHex(long value) {
3751
return String.format("0x%x", value);
3852
}
@@ -108,7 +122,8 @@ private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread
108122
final List<SentryStackFrame> frames = new ArrayList<>();
109123

110124
for (TombstoneProtos.BacktraceFrame frame : thread.getCurrentBacktraceList()) {
111-
if (frame.getFileName().endsWith("libart.so")) {
125+
if (frame.getFileName().endsWith("libart.so")
126+
|| Objects.equals(frame.getFunctionName(), "art_jni_trampoline")) {
112127
// We ignore all ART frames for time being because they aren't actionable for app developers
113128
continue;
114129
}
@@ -118,9 +133,15 @@ private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread
118133
continue;
119134
}
120135
final SentryStackFrame stackFrame = new SentryStackFrame();
121-
stackFrame.setPackage(frame.getFileName());
122-
stackFrame.setFunction(frame.getFunctionName());
123-
stackFrame.setInstructionAddr(formatHex(frame.getPc()));
136+
if (isJavaFrame(frame)) {
137+
stackFrame.setPlatform("java");
138+
stackFrame.setFunction(extractJavaFunctionName(frame.getFunctionName()));
139+
stackFrame.setModule(extractJavaModuleName(frame.getFunctionName()));
140+
} else {
141+
stackFrame.setPackage(frame.getFileName());
142+
stackFrame.setFunction(frame.getFunctionName());
143+
stackFrame.setInstructionAddr(formatHex(frame.getPc()));
144+
}
124145

125146
// inAppIncludes/inAppExcludes filter by Java/Kotlin package names, which don't overlap
126147
// with native C/C++ function names (e.g., "crash", "__libc_init"). For native frames,
@@ -159,6 +180,22 @@ private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread
159180
return stacktrace;
160181
}
161182

183+
private static @Nullable String extractJavaModuleName(String fqFunctionName) {
184+
if (fqFunctionName.contains(".")) {
185+
return fqFunctionName.substring(0, fqFunctionName.lastIndexOf("."));
186+
} else {
187+
return "";
188+
}
189+
}
190+
191+
private static @Nullable String extractJavaFunctionName(String fqFunctionName) {
192+
if (fqFunctionName.contains(".")) {
193+
return fqFunctionName.substring(fqFunctionName.lastIndexOf(".") + 1);
194+
} else {
195+
return fqFunctionName;
196+
}
197+
}
198+
162199
@NonNull
163200
private List<SentryException> createException(@NonNull TombstoneProtos.Tombstone tombstone) {
164201
final SentryException exception = new SentryException();
@@ -296,7 +333,7 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
296333
// Check for duplicated mappings: On Android, the same ELF can have multiple
297334
// mappings at offset 0 with different permissions (r--p, r-xp, r--p).
298335
// If it's the same file as the current module, just extend it.
299-
if (currentModule != null && mappingName.equals(currentModule.mappingName)) {
336+
if (currentModule != null && Objects.equals(mappingName, currentModule.mappingName)) {
300337
currentModule.extendTo(mapping.getEndAddress());
301338
continue;
302339
}
@@ -311,7 +348,7 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
311348

312349
// Start a new module
313350
currentModule = new ModuleAccumulator(mapping);
314-
} else if (currentModule != null && mappingName.equals(currentModule.mappingName)) {
351+
} else if (currentModule != null && Objects.equals(mappingName, currentModule.mappingName)) {
315352
// Extend the current module with this mapping (same file, continuation)
316353
currentModule.extendTo(mapping.getEndAddress());
317354
}

sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,20 @@ class TombstoneParserTest {
101101

102102
for (frame in thread.stacktrace!!.frames!!) {
103103
assertNotNull(frame.function)
104-
assertNotNull(frame.`package`)
105-
assertNotNull(frame.instructionAddr)
104+
if (frame.platform == "java") {
105+
// Java frames have module instead of package/instructionAddr
106+
assertNotNull(frame.module)
107+
} else {
108+
assertNotNull(frame.`package`)
109+
assertNotNull(frame.instructionAddr)
110+
}
106111

107112
if (thread.id == crashedThreadId) {
108113
if (frame.isInApp!!) {
109114
assert(
110-
frame.function!!.startsWith(inAppIncludes[0]) ||
111-
frame.`package`!!.startsWith(nativeLibraryDir)
115+
frame.module?.startsWith(inAppIncludes[0]) == true ||
116+
frame.function!!.startsWith(inAppIncludes[0]) ||
117+
frame.`package`?.startsWith(nativeLibraryDir) == true
112118
)
113119
}
114120
}
@@ -429,6 +435,35 @@ class TombstoneParserTest {
429435
}
430436
}
431437

438+
@Test
439+
fun `java frames snapshot test for all threads`() {
440+
val tombstoneStream =
441+
GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream("/tombstone.pb.gz"))
442+
val parser = TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir)
443+
val event = parser.parse()
444+
445+
val logger = mock<ILogger>()
446+
val writer = StringWriter()
447+
val jsonWriter = JsonObjectWriter(writer, 100)
448+
jsonWriter.beginObject()
449+
for (thread in event.threads!!) {
450+
val javaFrames = thread.stacktrace!!.frames!!.filter { it.platform == "java" }
451+
if (javaFrames.isEmpty()) continue
452+
jsonWriter.name(thread.id.toString())
453+
jsonWriter.beginArray()
454+
for (frame in javaFrames) {
455+
frame.serialize(jsonWriter, logger)
456+
}
457+
jsonWriter.endArray()
458+
}
459+
jsonWriter.endObject()
460+
461+
val actualJson = writer.toString()
462+
val expectedJson = readGzippedResourceFile("/tombstone_java_frames.json.gz")
463+
464+
assertEquals(expectedJson, actualJson)
465+
}
466+
432467
private fun serializeDebugMeta(debugMeta: DebugMeta): String {
433468
val logger = mock<ILogger>()
434469
val writer = StringWriter()
Binary file not shown.

0 commit comments

Comments
 (0)