Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- Android: Add proguard rules to prevent error about missing Replay classes ([#5153](https://github.com/getsentry/sentry-java/pull/5153))
- Android: Remove the dependency on protobuf-lite for tombstones ([#5157](https://github.com/getsentry/sentry-java/pull/5157))

## 8.34.0

Expand Down
5 changes: 1 addition & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ spotless = "7.0.4"
gummyBears = "0.12.0"
camerax = "1.3.0"
openfeature = "1.18.2"
protobuf = "3.25.8"

[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Expand All @@ -61,7 +60,6 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" }
jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" }
kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" }
protobuf = { id = "com.google.protobuf", version = "0.9.5" }
vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" }
springboot2 = { id = "org.springframework.boot", version.ref = "springboot2" }
springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" }
Expand Down Expand Up @@ -145,8 +143,7 @@ otel-javaagent-extension-api = { module = "io.opentelemetry.javaagent:openteleme
otel-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "otelSemanticConventions" }
otel-semconv-incubating = { module = "io.opentelemetry.semconv:opentelemetry-semconv-incubating", version.ref = "otelSemanticConventionsAlpha" }
p6spy = { module = "p6spy:p6spy", version = "3.9.1" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf"}
protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
epitaph = { module = "com.abovevacant:epitaph", version = "0.1.0" }
quartz = { module = "org.quartz-scheduler:quartz", version = "2.3.0" }
reactor-core = { module = "io.projectreactor:reactor-core", version = "3.5.3" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
Expand Down
10 changes: 1 addition & 9 deletions sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ plugins {
alias(libs.plugins.jacoco.android)
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.protobuf)
}

android {
Expand Down Expand Up @@ -84,7 +83,7 @@ dependencies {
implementation(libs.androidx.lifecycle.common.java8)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.core)
implementation(libs.protobuf.javalite)
implementation(libs.epitaph)

errorprone(libs.errorprone.core)
errorprone(libs.nopen.checker)
Expand Down Expand Up @@ -113,10 +112,3 @@ dependencies {
testRuntimeOnly(libs.androidx.fragment.ktx)
testRuntimeOnly(libs.timber)
}

protobuf {
protoc { artifact = libs.protoc.get().toString() }
generateProtoTasks {
all().forEach { task -> task.builtins { create("java") { option("lite") } } }
}
}
3 changes: 0 additions & 3 deletions sentry-android-core/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@

-keepnames class io.sentry.android.core.ApplicationNotResponding

# protobuf-java lite
# https://github.com/protocolbuffers/protobuf/blob/5d876c9fec1a6f2feb0750694f803f89312bffff/java/lite.md#r8-rule-to-make-production-app-builds-work
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }

##---------------End: proguard configuration for android-core ----------

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package io.sentry.android.core.internal.tombstone;

import androidx.annotation.NonNull;
import com.abovevacant.epitaph.core.BacktraceFrame;
import com.abovevacant.epitaph.core.MemoryMapping;
import com.abovevacant.epitaph.core.Register;
import com.abovevacant.epitaph.core.Signal;
import com.abovevacant.epitaph.core.Tombstone;
import com.abovevacant.epitaph.core.TombstoneThread;
import com.abovevacant.epitaph.wire.TombstoneDecoder;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SentryStackTraceFactory;
Expand All @@ -27,7 +34,7 @@

public class TombstoneParser implements Closeable {

private final InputStream tombstoneStream;
@Nullable private final InputStream tombstoneStream;
Comment thread
sentry[bot] marked this conversation as resolved.
@NotNull private final List<String> inAppIncludes;
@NotNull private final List<String> inAppExcludes;
@Nullable private final String nativeLibraryDir;
Expand All @@ -38,7 +45,14 @@ private static String formatHex(long value) {
}

public TombstoneParser(
@NonNull final InputStream tombstoneStream,
@NotNull List<String> inAppIncludes,
@NotNull List<String> inAppExcludes,
@Nullable String nativeLibraryDir) {
this(null, inAppIncludes, inAppExcludes, nativeLibraryDir);
}

public TombstoneParser(
@Nullable final InputStream tombstoneStream,
@NotNull List<String> inAppIncludes,
@NotNull List<String> inAppExcludes,
@Nullable String nativeLibraryDir) {
Expand All @@ -58,10 +72,14 @@ public TombstoneParser(

@NonNull
public SentryEvent parse() throws IOException {
@NonNull
final TombstoneProtos.Tombstone tombstone =
TombstoneProtos.Tombstone.parseFrom(tombstoneStream);
if (tombstoneStream == null) {
throw new IOException("No InputStream provided; use parse(Tombstone) instead.");
}
return parse(TombstoneDecoder.decode(tombstoneStream));
}

@NonNull
public SentryEvent parse(@NonNull final Tombstone tombstone) {
final SentryEvent event = new SentryEvent();
event.setLevel(SentryLevel.FATAL);

Expand All @@ -79,19 +97,18 @@ public SentryEvent parse() throws IOException {

@NonNull
private List<SentryThread> createThreads(
@NonNull final TombstoneProtos.Tombstone tombstone, @NonNull final SentryException exc) {
@NonNull final Tombstone tombstone, @NonNull final SentryException exc) {
final List<SentryThread> threads = new ArrayList<>();
for (Map.Entry<Integer, TombstoneProtos.Thread> threadEntry :
tombstone.getThreadsMap().entrySet()) {
final TombstoneProtos.Thread threadEntryValue = threadEntry.getValue();
for (Map.Entry<Integer, TombstoneThread> threadEntry : tombstone.threads.entrySet()) {
final TombstoneThread threadEntryValue = threadEntry.getValue();

final SentryThread thread = new SentryThread();
thread.setId(Long.valueOf(threadEntry.getKey()));
thread.setName(threadEntryValue.getName());
thread.setName(threadEntryValue.name);

final SentryStackTrace stacktrace = createStackTrace(threadEntryValue);
thread.setStacktrace(stacktrace);
if (tombstone.getTid() == threadEntryValue.getId()) {
if (tombstone.tid == threadEntryValue.id) {
thread.setCrashed(true);
// even though we refer to the thread_id from the exception,
// the backend currently requires a stack-trace in exception
Expand All @@ -104,38 +121,38 @@ private List<SentryThread> createThreads(
}

@NonNull
private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread thread) {
private SentryStackTrace createStackTrace(@NonNull final TombstoneThread thread) {
final List<SentryStackFrame> frames = new ArrayList<>();

for (TombstoneProtos.BacktraceFrame frame : thread.getCurrentBacktraceList()) {
if (frame.getFileName().endsWith("libart.so")) {
for (BacktraceFrame frame : thread.backtrace) {
if (frame.fileName.endsWith("libart.so")) {
// We ignore all ART frames for time being because they aren't actionable for app developers
continue;
}
if (frame.getFileName().startsWith("<anonymous") && frame.getFunctionName().isEmpty()) {
if (frame.fileName.startsWith("<anonymous") && frame.functionName.isEmpty()) {
// Code in anonymous VMAs that does not resolve to a function name, cannot be symbolicated
// in the backend either, and thus has no value in the UI.
continue;
}
final SentryStackFrame stackFrame = new SentryStackFrame();
stackFrame.setPackage(frame.getFileName());
stackFrame.setFunction(frame.getFunctionName());
stackFrame.setInstructionAddr(formatHex(frame.getPc()));
stackFrame.setPackage(frame.fileName);
stackFrame.setFunction(frame.functionName);
stackFrame.setInstructionAddr(formatHex(frame.pc));

// inAppIncludes/inAppExcludes filter by Java/Kotlin package names, which don't overlap
// with native C/C++ function names (e.g., "crash", "__libc_init"). For native frames,
// isInApp() returns null, making nativeLibraryDir the effective in-app check.
// Protobuf returns "" for unset function names, which would incorrectly return true
// epitaph returns "" for unset function names, which would incorrectly return true
// from isInApp(), so we treat empty as false to let nativeLibraryDir decide.
final String functionName = frame.getFunctionName();
final String functionName = frame.functionName;
@Nullable
Boolean inApp =
functionName.isEmpty()
? Boolean.FALSE
: SentryStackTraceFactory.isInApp(functionName, inAppIncludes, inAppExcludes);

final boolean isInNativeLibraryDir =
nativeLibraryDir != null && frame.getFileName().startsWith(nativeLibraryDir);
nativeLibraryDir != null && frame.fileName.startsWith(nativeLibraryDir);
inApp = (inApp != null && inApp) || isInNativeLibraryDir;

stackFrame.setInApp(inApp);
Expand All @@ -151,74 +168,73 @@ private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread
stacktrace.setInstructionAddressAdjustment(SentryStackTrace.InstructionAddressAdjustment.NONE);

final Map<String, String> registers = new HashMap<>();
for (TombstoneProtos.Register register : thread.getRegistersList()) {
registers.put(register.getName(), formatHex(register.getU64()));
for (Register register : thread.registers) {
registers.put(register.name, formatHex(register.value));
}
stacktrace.setRegisters(registers);

return stacktrace;
}

@NonNull
private List<SentryException> createException(@NonNull TombstoneProtos.Tombstone tombstone) {
private List<SentryException> createException(@NonNull Tombstone tombstone) {
final SentryException exception = new SentryException();

if (tombstone.hasSignalInfo()) {
final TombstoneProtos.Signal signalInfo = tombstone.getSignalInfo();
exception.setType(signalInfo.getName());
exception.setValue(excTypeValueMap.get(signalInfo.getName()));
if (tombstone.hasSignal()) {
final Signal signalInfo = tombstone.signal;
exception.setType(signalInfo.name);
exception.setValue(excTypeValueMap.get(signalInfo.name));
exception.setMechanism(createMechanismFromSignalInfo(signalInfo));
}

exception.setThreadId((long) tombstone.getTid());
exception.setThreadId((long) tombstone.tid);
final List<SentryException> exceptions = new ArrayList<>(1);
exceptions.add(exception);

return exceptions;
}

@NonNull
private static Mechanism createMechanismFromSignalInfo(
@NonNull final TombstoneProtos.Signal signalInfo) {
private static Mechanism createMechanismFromSignalInfo(@NonNull final Signal signalInfo) {

final Mechanism mechanism = new Mechanism();
mechanism.setType(NativeExceptionMechanism.TOMBSTONE.getValue());
mechanism.setHandled(false);
mechanism.setSynthetic(true);

final Map<String, Object> meta = new HashMap<>();
meta.put("number", signalInfo.getNumber());
meta.put("name", signalInfo.getName());
meta.put("code", signalInfo.getCode());
meta.put("code_name", signalInfo.getCodeName());
meta.put("number", signalInfo.number);
meta.put("name", signalInfo.name);
meta.put("code", signalInfo.code);
meta.put("code_name", signalInfo.codeName);
mechanism.setMeta(meta);

return mechanism;
}

@NonNull
private Message constructMessage(@NonNull final TombstoneProtos.Tombstone tombstone) {
private Message constructMessage(@NonNull final Tombstone tombstone) {
final Message message = new Message();
final TombstoneProtos.Signal signalInfo = tombstone.getSignalInfo();
final Signal signalInfo = tombstone.signal;

// reproduce the message `debuggerd` would use to dump the stack trace in logcat
String command = String.join(" ", tombstone.getCommandLineList());
if (tombstone.hasSignalInfo()) {
String abortMessage = tombstone.getAbortMessage();
String command = String.join(" ", tombstone.commandLine);
if (tombstone.hasSignal()) {
String abortMessage = tombstone.abortMessage;
message.setFormatted(
String.format(
Locale.ROOT,
"%sFatal signal %s (%d), %s (%d), pid = %d (%s)",
!abortMessage.isEmpty() ? abortMessage + ": " : "",
signalInfo.getName(),
signalInfo.getNumber(),
signalInfo.getCodeName(),
signalInfo.getCode(),
tombstone.getPid(),
signalInfo.name,
signalInfo.number,
signalInfo.codeName,
signalInfo.code,
tombstone.pid,
command));
} else {
message.setFormatted(
String.format(Locale.ROOT, "Fatal exit pid = %d (%s)", tombstone.getPid(), command));
String.format(Locale.ROOT, "Fatal exit pid = %d (%s)", tombstone.pid, command));
}

return message;
Expand All @@ -236,11 +252,11 @@ private static class ModuleAccumulator {
long beginAddress;
long endAddress;

ModuleAccumulator(TombstoneProtos.MemoryMapping mapping) {
this.mappingName = mapping.getMappingName();
this.buildId = mapping.getBuildId();
this.beginAddress = mapping.getBeginAddress();
this.endAddress = mapping.getEndAddress();
ModuleAccumulator(MemoryMapping mapping) {
this.mappingName = mapping.mappingName;
this.buildId = mapping.buildId;
this.beginAddress = mapping.beginAddress;
this.endAddress = mapping.endAddress;
}

void extendTo(long newEndAddress) {
Expand All @@ -266,7 +282,7 @@ DebugImage toDebugImage() {
}
}

private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombstone) {
private DebugMeta createDebugMeta(@NonNull final Tombstone tombstone) {
final List<DebugImage> images = new ArrayList<>();

// Coalesce memory mappings into modules similar to how sentry-native does it.
Expand All @@ -277,27 +293,27 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
// combined with non-empty build_id as a proxy for this check.
ModuleAccumulator currentModule = null;

for (TombstoneProtos.MemoryMapping mapping : tombstone.getMemoryMappingsList()) {
for (MemoryMapping mapping : tombstone.memoryMappings) {
// Skip mappings that are not readable
if (!mapping.getRead()) {
if (!mapping.read) {
continue;
}

// Skip mappings with empty name or in /dev/
final String mappingName = mapping.getMappingName();
final String mappingName = mapping.mappingName;
if (mappingName.isEmpty() || mappingName.startsWith("/dev/")) {
continue;
}

final boolean hasBuildId = !mapping.getBuildId().isEmpty();
final boolean isFileStart = mapping.getOffset() == 0;
final boolean hasBuildId = !mapping.buildId.isEmpty();
final boolean isFileStart = mapping.offset == 0;

if (hasBuildId && isFileStart) {
// Check for duplicated mappings: On Android, the same ELF can have multiple
// mappings at offset 0 with different permissions (r--p, r-xp, r--p).
// If it's the same file as the current module, just extend it.
if (currentModule != null && mappingName.equals(currentModule.mappingName)) {
currentModule.extendTo(mapping.getEndAddress());
currentModule.extendTo(mapping.endAddress);
continue;
}

Expand All @@ -313,7 +329,7 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
currentModule = new ModuleAccumulator(mapping);
} else if (currentModule != null && mappingName.equals(currentModule.mappingName)) {
// Extend the current module with this mapping (same file, continuation)
currentModule.extendTo(mapping.getEndAddress());
currentModule.extendTo(mapping.endAddress);
Comment thread
cursor[bot] marked this conversation as resolved.
}
}

Expand All @@ -333,6 +349,8 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs

@Override
public void close() throws IOException {
tombstoneStream.close();
if (tombstoneStream != null) {
tombstoneStream.close();
}
}
}
Loading
Loading