11package io .sentry .android .core .internal .tombstone ;
22
33import androidx .annotation .NonNull ;
4+ import com .abovevacant .epitaph .core .BacktraceFrame ;
5+ import com .abovevacant .epitaph .core .MemoryMapping ;
6+ import com .abovevacant .epitaph .core .Register ;
7+ import com .abovevacant .epitaph .core .Signal ;
8+ import com .abovevacant .epitaph .core .Tombstone ;
9+ import com .abovevacant .epitaph .core .TombstoneThread ;
10+ import com .abovevacant .epitaph .wire .TombstoneDecoder ;
411import io .sentry .SentryEvent ;
512import io .sentry .SentryLevel ;
613import io .sentry .SentryStackTraceFactory ;
2734
2835public class TombstoneParser implements Closeable {
2936
30- private final InputStream tombstoneStream ;
37+ @ Nullable private final InputStream tombstoneStream ;
3138 @ NotNull private final List <String > inAppIncludes ;
3239 @ NotNull private final List <String > inAppExcludes ;
3340 @ Nullable private final String nativeLibraryDir ;
@@ -38,7 +45,14 @@ private static String formatHex(long value) {
3845 }
3946
4047 public TombstoneParser (
41- @ NonNull final InputStream tombstoneStream ,
48+ @ NotNull List <String > inAppIncludes ,
49+ @ NotNull List <String > inAppExcludes ,
50+ @ Nullable String nativeLibraryDir ) {
51+ this (null , inAppIncludes , inAppExcludes , nativeLibraryDir );
52+ }
53+
54+ public TombstoneParser (
55+ @ Nullable final InputStream tombstoneStream ,
4256 @ NotNull List <String > inAppIncludes ,
4357 @ NotNull List <String > inAppExcludes ,
4458 @ Nullable String nativeLibraryDir ) {
@@ -58,10 +72,14 @@ public TombstoneParser(
5872
5973 @ NonNull
6074 public SentryEvent parse () throws IOException {
61- @ NonNull
62- final TombstoneProtos .Tombstone tombstone =
63- TombstoneProtos .Tombstone .parseFrom (tombstoneStream );
75+ if (tombstoneStream == null ) {
76+ throw new IOException ("No InputStream provided; use parse(Tombstone) instead." );
77+ }
78+ return parse (TombstoneDecoder .decode (tombstoneStream ));
79+ }
6480
81+ @ NonNull
82+ public SentryEvent parse (@ NonNull final Tombstone tombstone ) {
6583 final SentryEvent event = new SentryEvent ();
6684 event .setLevel (SentryLevel .FATAL );
6785
@@ -79,19 +97,18 @@ public SentryEvent parse() throws IOException {
7997
8098 @ NonNull
8199 private List <SentryThread > createThreads (
82- @ NonNull final TombstoneProtos . Tombstone tombstone , @ NonNull final SentryException exc ) {
100+ @ NonNull final Tombstone tombstone , @ NonNull final SentryException exc ) {
83101 final List <SentryThread > threads = new ArrayList <>();
84- for (Map .Entry <Integer , TombstoneProtos .Thread > threadEntry :
85- tombstone .getThreadsMap ().entrySet ()) {
86- final TombstoneProtos .Thread threadEntryValue = threadEntry .getValue ();
102+ for (Map .Entry <Integer , TombstoneThread > threadEntry : tombstone .threads .entrySet ()) {
103+ final TombstoneThread threadEntryValue = threadEntry .getValue ();
87104
88105 final SentryThread thread = new SentryThread ();
89106 thread .setId (Long .valueOf (threadEntry .getKey ()));
90- thread .setName (threadEntryValue .getName () );
107+ thread .setName (threadEntryValue .name );
91108
92109 final SentryStackTrace stacktrace = createStackTrace (threadEntryValue );
93110 thread .setStacktrace (stacktrace );
94- if (tombstone .getTid () == threadEntryValue .getId () ) {
111+ if (tombstone .tid == threadEntryValue .id ) {
95112 thread .setCrashed (true );
96113 // even though we refer to the thread_id from the exception,
97114 // the backend currently requires a stack-trace in exception
@@ -104,38 +121,38 @@ private List<SentryThread> createThreads(
104121 }
105122
106123 @ NonNull
107- private SentryStackTrace createStackTrace (@ NonNull final TombstoneProtos . Thread thread ) {
124+ private SentryStackTrace createStackTrace (@ NonNull final TombstoneThread thread ) {
108125 final List <SentryStackFrame > frames = new ArrayList <>();
109126
110- for (TombstoneProtos . BacktraceFrame frame : thread .getCurrentBacktraceList () ) {
111- if (frame .getFileName () .endsWith ("libart.so" )) {
127+ for (BacktraceFrame frame : thread .backtrace ) {
128+ if (frame .fileName .endsWith ("libart.so" )) {
112129 // We ignore all ART frames for time being because they aren't actionable for app developers
113130 continue ;
114131 }
115- if (frame .getFileName () .startsWith ("<anonymous" ) && frame .getFunctionName () .isEmpty ()) {
132+ if (frame .fileName .startsWith ("<anonymous" ) && frame .functionName .isEmpty ()) {
116133 // Code in anonymous VMAs that does not resolve to a function name, cannot be symbolicated
117134 // in the backend either, and thus has no value in the UI.
118135 continue ;
119136 }
120137 final SentryStackFrame stackFrame = new SentryStackFrame ();
121- stackFrame .setPackage (frame .getFileName () );
122- stackFrame .setFunction (frame .getFunctionName () );
123- stackFrame .setInstructionAddr (formatHex (frame .getPc () ));
138+ stackFrame .setPackage (frame .fileName );
139+ stackFrame .setFunction (frame .functionName );
140+ stackFrame .setInstructionAddr (formatHex (frame .pc ));
124141
125142 // inAppIncludes/inAppExcludes filter by Java/Kotlin package names, which don't overlap
126143 // with native C/C++ function names (e.g., "crash", "__libc_init"). For native frames,
127144 // isInApp() returns null, making nativeLibraryDir the effective in-app check.
128- // Protobuf returns "" for unset function names, which would incorrectly return true
145+ // epitaph returns "" for unset function names, which would incorrectly return true
129146 // from isInApp(), so we treat empty as false to let nativeLibraryDir decide.
130- final String functionName = frame .getFunctionName () ;
147+ final String functionName = frame .functionName ;
131148 @ Nullable
132149 Boolean inApp =
133150 functionName .isEmpty ()
134151 ? Boolean .FALSE
135152 : SentryStackTraceFactory .isInApp (functionName , inAppIncludes , inAppExcludes );
136153
137154 final boolean isInNativeLibraryDir =
138- nativeLibraryDir != null && frame .getFileName () .startsWith (nativeLibraryDir );
155+ nativeLibraryDir != null && frame .fileName .startsWith (nativeLibraryDir );
139156 inApp = (inApp != null && inApp ) || isInNativeLibraryDir ;
140157
141158 stackFrame .setInApp (inApp );
@@ -151,74 +168,73 @@ private SentryStackTrace createStackTrace(@NonNull final TombstoneProtos.Thread
151168 stacktrace .setInstructionAddressAdjustment (SentryStackTrace .InstructionAddressAdjustment .NONE );
152169
153170 final Map <String , String > registers = new HashMap <>();
154- for (TombstoneProtos . Register register : thread .getRegistersList () ) {
155- registers .put (register .getName () , formatHex (register .getU64 () ));
171+ for (Register register : thread .registers ) {
172+ registers .put (register .name , formatHex (register .value ));
156173 }
157174 stacktrace .setRegisters (registers );
158175
159176 return stacktrace ;
160177 }
161178
162179 @ NonNull
163- private List <SentryException > createException (@ NonNull TombstoneProtos . Tombstone tombstone ) {
180+ private List <SentryException > createException (@ NonNull Tombstone tombstone ) {
164181 final SentryException exception = new SentryException ();
165182
166- if (tombstone .hasSignalInfo ()) {
167- final TombstoneProtos . Signal signalInfo = tombstone .getSignalInfo () ;
168- exception .setType (signalInfo .getName () );
169- exception .setValue (excTypeValueMap .get (signalInfo .getName () ));
183+ if (tombstone .hasSignal ()) {
184+ final Signal signalInfo = tombstone .signal ;
185+ exception .setType (signalInfo .name );
186+ exception .setValue (excTypeValueMap .get (signalInfo .name ));
170187 exception .setMechanism (createMechanismFromSignalInfo (signalInfo ));
171188 }
172189
173- exception .setThreadId ((long ) tombstone .getTid () );
190+ exception .setThreadId ((long ) tombstone .tid );
174191 final List <SentryException > exceptions = new ArrayList <>(1 );
175192 exceptions .add (exception );
176193
177194 return exceptions ;
178195 }
179196
180197 @ NonNull
181- private static Mechanism createMechanismFromSignalInfo (
182- @ NonNull final TombstoneProtos .Signal signalInfo ) {
198+ private static Mechanism createMechanismFromSignalInfo (@ NonNull final Signal signalInfo ) {
183199
184200 final Mechanism mechanism = new Mechanism ();
185201 mechanism .setType (NativeExceptionMechanism .TOMBSTONE .getValue ());
186202 mechanism .setHandled (false );
187203 mechanism .setSynthetic (true );
188204
189205 final Map <String , Object > meta = new HashMap <>();
190- meta .put ("number" , signalInfo .getNumber () );
191- meta .put ("name" , signalInfo .getName () );
192- meta .put ("code" , signalInfo .getCode () );
193- meta .put ("code_name" , signalInfo .getCodeName () );
206+ meta .put ("number" , signalInfo .number );
207+ meta .put ("name" , signalInfo .name );
208+ meta .put ("code" , signalInfo .code );
209+ meta .put ("code_name" , signalInfo .codeName );
194210 mechanism .setMeta (meta );
195211
196212 return mechanism ;
197213 }
198214
199215 @ NonNull
200- private Message constructMessage (@ NonNull final TombstoneProtos . Tombstone tombstone ) {
216+ private Message constructMessage (@ NonNull final Tombstone tombstone ) {
201217 final Message message = new Message ();
202- final TombstoneProtos . Signal signalInfo = tombstone .getSignalInfo () ;
218+ final Signal signalInfo = tombstone .signal ;
203219
204220 // reproduce the message `debuggerd` would use to dump the stack trace in logcat
205- String command = String .join (" " , tombstone .getCommandLineList () );
206- if (tombstone .hasSignalInfo ()) {
207- String abortMessage = tombstone .getAbortMessage () ;
221+ String command = String .join (" " , tombstone .commandLine );
222+ if (tombstone .hasSignal ()) {
223+ String abortMessage = tombstone .abortMessage ;
208224 message .setFormatted (
209225 String .format (
210226 Locale .ROOT ,
211227 "%sFatal signal %s (%d), %s (%d), pid = %d (%s)" ,
212228 !abortMessage .isEmpty () ? abortMessage + ": " : "" ,
213- signalInfo .getName () ,
214- signalInfo .getNumber () ,
215- signalInfo .getCodeName () ,
216- signalInfo .getCode () ,
217- tombstone .getPid () ,
229+ signalInfo .name ,
230+ signalInfo .number ,
231+ signalInfo .codeName ,
232+ signalInfo .code ,
233+ tombstone .pid ,
218234 command ));
219235 } else {
220236 message .setFormatted (
221- String .format (Locale .ROOT , "Fatal exit pid = %d (%s)" , tombstone .getPid () , command ));
237+ String .format (Locale .ROOT , "Fatal exit pid = %d (%s)" , tombstone .pid , command ));
222238 }
223239
224240 return message ;
@@ -236,11 +252,11 @@ private static class ModuleAccumulator {
236252 long beginAddress ;
237253 long endAddress ;
238254
239- ModuleAccumulator (TombstoneProtos . MemoryMapping mapping ) {
240- this .mappingName = mapping .getMappingName () ;
241- this .buildId = mapping .getBuildId () ;
242- this .beginAddress = mapping .getBeginAddress () ;
243- this .endAddress = mapping .getEndAddress () ;
255+ ModuleAccumulator (MemoryMapping mapping ) {
256+ this .mappingName = mapping .mappingName ;
257+ this .buildId = mapping .buildId ;
258+ this .beginAddress = mapping .beginAddress ;
259+ this .endAddress = mapping .endAddress ;
244260 }
245261
246262 void extendTo (long newEndAddress ) {
@@ -266,7 +282,7 @@ DebugImage toDebugImage() {
266282 }
267283 }
268284
269- private DebugMeta createDebugMeta (@ NonNull final TombstoneProtos . Tombstone tombstone ) {
285+ private DebugMeta createDebugMeta (@ NonNull final Tombstone tombstone ) {
270286 final List <DebugImage > images = new ArrayList <>();
271287
272288 // Coalesce memory mappings into modules similar to how sentry-native does it.
@@ -277,27 +293,27 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
277293 // combined with non-empty build_id as a proxy for this check.
278294 ModuleAccumulator currentModule = null ;
279295
280- for (TombstoneProtos . MemoryMapping mapping : tombstone .getMemoryMappingsList () ) {
296+ for (MemoryMapping mapping : tombstone .memoryMappings ) {
281297 // Skip mappings that are not readable
282- if (!mapping .getRead () ) {
298+ if (!mapping .read ) {
283299 continue ;
284300 }
285301
286302 // Skip mappings with empty name or in /dev/
287- final String mappingName = mapping .getMappingName () ;
303+ final String mappingName = mapping .mappingName ;
288304 if (mappingName .isEmpty () || mappingName .startsWith ("/dev/" )) {
289305 continue ;
290306 }
291307
292- final boolean hasBuildId = !mapping .getBuildId () .isEmpty ();
293- final boolean isFileStart = mapping .getOffset () == 0 ;
308+ final boolean hasBuildId = !mapping .buildId .isEmpty ();
309+ final boolean isFileStart = mapping .offset == 0 ;
294310
295311 if (hasBuildId && isFileStart ) {
296312 // Check for duplicated mappings: On Android, the same ELF can have multiple
297313 // mappings at offset 0 with different permissions (r--p, r-xp, r--p).
298314 // If it's the same file as the current module, just extend it.
299315 if (currentModule != null && mappingName .equals (currentModule .mappingName )) {
300- currentModule .extendTo (mapping .getEndAddress () );
316+ currentModule .extendTo (mapping .endAddress );
301317 continue ;
302318 }
303319
@@ -313,7 +329,7 @@ private DebugMeta createDebugMeta(@NonNull final TombstoneProtos.Tombstone tombs
313329 currentModule = new ModuleAccumulator (mapping );
314330 } else if (currentModule != null && mappingName .equals (currentModule .mappingName )) {
315331 // Extend the current module with this mapping (same file, continuation)
316- currentModule .extendTo (mapping .getEndAddress () );
332+ currentModule .extendTo (mapping .endAddress );
317333 }
318334 }
319335
0 commit comments