2727import io .sentry .protocol .SentryStackTrace ;
2828import io .sentry .protocol .SentryThread ;
2929import java .math .BigInteger ;
30+ import java .nio .BufferUnderflowException ;
3031import java .nio .ByteBuffer ;
3132import java .nio .ByteOrder ;
32- import java .nio .BufferUnderflowException ;
3333import java .util .ArrayList ;
34- import java .util .Collection ;
3534import java .util .Collections ;
3635import java .util .HashMap ;
37- import java .util .LinkedHashMap ;
3836import java .util .List ;
3937import java .util .Map ;
4038import java .util .regex .Matcher ;
@@ -50,26 +48,38 @@ public class ThreadDumpParser {
5048 Pattern .compile ("\" (.*)\" (.*) ?sysTid=(\\ d+)" );
5149
5250 // For reference, see native_stack_dump.cc and tombstone_proto_to_text.cpp in Android sources
51+ // Groups
52+ // 0:entire regex
53+ // 1:index
54+ // 2:pc
55+ // 3:mapinfo
56+ // 4:filename
57+ // 5:mapoffset
58+ // 6:function
59+ // 7:fnoffset
60+ // 7:buildid
5361 private static final Pattern NATIVE_RE =
5462 Pattern .compile (
5563 // " native: #12 pc 0xabcd1234"
56- " *(?:native: )?#(?<index>\\ d+) \\ S+ (?<pc>[0-9a-fA-F]+)"
57- // The map info includes a filename and an optional offset into the file
58- + ("\\ s+(?<mapinfo>"
59- // "/path/to/file.ext",
60- + "(?<filename>.*?)"
61- // optional " (deleted)" suffix (deleted files) needed here to bias regex correctly
62- + "(?:\\ s+\\ (deleted\\ ))?"
63- // " (offset 0xabcd1234)", if the mapping is not into the beginning of the file
64- + "(?:\\ s+\\ (offset (?<mapoffset>.*?)\\ ))?"
65- + ")" )
66- // Optional function
67- + ("(?:\\ s+\\ ((?:"
68- + "\\ ?\\ ?\\ ?" // " (???) marks a missing function, so don't capture it in a group
69- + "|(?<function>.*?)(?:\\ +(?<fnoffset>\\ d+))?" // " (func+1234)", offset is optional
70- + ")\\ ))?" )
71- // Optional " (BuildId: abcd1234abcd1234abcd1234abcd1234abcd1234)"
72- + "(?:\\ s+\\ (BuildId: (?<buildid>.*?)\\ ))?" );
64+ " *(?:native: )?#(\\ d+) \\ S+ ([0-9a-fA-F]+)"
65+ // The map info includes a filename and an optional offset into the file
66+ + ("\\ s+("
67+ // "/path/to/file.ext",
68+ + "(.*?)"
69+ // optional " (deleted)" suffix (deleted files) needed here to bias regex
70+ // correctly
71+ + "(?:\\ s+\\ (deleted\\ ))?"
72+ // " (offset 0xabcd1234)", if the mapping is not into the beginning of the file
73+ + "(?:\\ s+\\ (offset (.*?)\\ ))?"
74+ + ")" )
75+ // Optional function
76+ + ("(?:\\ s+\\ ((?:"
77+ + "\\ ?\\ ?\\ ?" // " (???) marks a missing function, so don't capture it in a group
78+ + "|(.*?)(?:\\ +(\\ d+))?" // " (func+1234)", offset is
79+ // optional
80+ + ")\\ ))?" )
81+ // Optional " (BuildId: abcd1234abcd1234abcd1234abcd1234abcd1234)"
82+ + "(?:\\ s+\\ (BuildId: (.*?)\\ ))?" );
7383
7484 private static final Pattern JAVA_RE =
7585 Pattern .compile (" *at (?:(.+)\\ .)?([^.]+)\\ .([^.]+)\\ ((.*):([\\ d-]+)\\ )" );
@@ -100,16 +110,24 @@ public class ThreadDumpParser {
100110
101111 private final @ NotNull Map <String , DebugImage > debugImages ;
102112
113+ private final @ NotNull List <SentryThread > threads ;
114+
103115 public ThreadDumpParser (final @ NotNull SentryOptions options , final boolean isBackground ) {
104116 this .options = options ;
105117 this .isBackground = isBackground ;
106118 this .stackTraceFactory = new SentryStackTraceFactory (options );
107- this .debugImages = new HashMap <String , DebugImage >();
119+ this .debugImages = new HashMap <>();
120+ this .threads = new ArrayList <>();
121+ }
122+
123+ @ NotNull
124+ public List <DebugImage > getDebugImages () {
125+ return new ArrayList <>(debugImages .values ());
108126 }
109127
110128 @ NotNull
111- public Map < String , DebugImage > getDebugImages () {
112- return debugImages ;
129+ public List < SentryThread > getThreads () {
130+ return threads ;
113131 }
114132
115133 @ Nullable
@@ -118,21 +136,20 @@ private static String buildIdToDebugId(String buildId) {
118136 // Abuse BigInteger as a hex string parser. Extra byte needed to handle leading zeros.
119137 final ByteBuffer buf = ByteBuffer .wrap (new BigInteger ("10" + buildId , 16 ).toByteArray ());
120138 buf .get ();
121- return String .format ("%08x-%04x-%04x-%04x-%04x%08x" ,
122- buf .order (ByteOrder .LITTLE_ENDIAN ).getInt (),
123- buf .getShort (),
124- buf .getShort (),
125- buf .order (ByteOrder .BIG_ENDIAN ).getShort (),
126- buf .getShort (),
127- buf .getInt ());
139+ return String .format (
140+ "%08x-%04x-%04x-%04x-%04x%08x" ,
141+ buf .order (ByteOrder .LITTLE_ENDIAN ).getInt (),
142+ buf .getShort (),
143+ buf .getShort (),
144+ buf .order (ByteOrder .BIG_ENDIAN ).getShort (),
145+ buf .getShort (),
146+ buf .getInt ());
128147 } catch (NumberFormatException | BufferUnderflowException e ) {
129148 return null ;
130149 }
131150 }
132151
133- @ NotNull
134- public List <SentryThread > parse (final @ NotNull Lines lines ) {
135- final List <SentryThread > sentryThreads = new ArrayList <>();
152+ public void parse (final @ NotNull Lines lines ) {
136153
137154 final Matcher beginManagedThreadRe = BEGIN_MANAGED_THREAD_RE .matcher ("" );
138155 final Matcher beginUnmanagedNativeThreadRe = BEGIN_UNMANAGED_NATIVE_THREAD_RE .matcher ("" );
@@ -141,7 +158,7 @@ public List<SentryThread> parse(final @NotNull Lines lines) {
141158 final Line line = lines .next ();
142159 if (line == null ) {
143160 options .getLogger ().log (SentryLevel .WARNING , "Internal error while parsing thread dump." );
144- return sentryThreads ;
161+ return ;
145162 }
146163 final String text = line .text ;
147164 // we only handle managed threads, as unmanaged/not attached do not have the thread id and
@@ -151,11 +168,10 @@ public List<SentryThread> parse(final @NotNull Lines lines) {
151168
152169 final SentryThread thread = parseThread (lines );
153170 if (thread != null ) {
154- sentryThreads .add (thread );
171+ threads .add (thread );
155172 }
156173 }
157174 }
158- return sentryThreads ;
159175 }
160176
161177 private SentryThread parseThread (final @ NotNull Lines lines ) {
@@ -242,22 +258,34 @@ private SentryStackTrace parseStacktrace(
242258 break ;
243259 }
244260 final String text = line .text ;
245- if (matches (nativeRe , text )) {
261+ if (matches (javaRe , text )) {
262+ final SentryStackFrame frame = new SentryStackFrame ();
263+ final String packageName = javaRe .group (1 );
264+ final String className = javaRe .group (2 );
265+ final String module = String .format ("%s.%s" , packageName , className );
266+ frame .setModule (module );
267+ frame .setFunction (javaRe .group (3 ));
268+ frame .setFilename (javaRe .group (4 ));
269+ frame .setLineno (getUInteger (javaRe , 5 , null ));
270+ frame .setInApp (stackTraceFactory .isInApp (module ));
271+ frames .add (frame );
272+ lastJavaFrame = frame ;
273+ } else if (matches (nativeRe , text )) {
246274 final SentryStackFrame frame = new SentryStackFrame ();
247- frame .setPackage (nativeRe .group ("mapinfo" ));
248- frame .setFunction (nativeRe .group ("function" ));
249- frame .setLineno (getInteger (nativeRe , "fnoffset" , null ));
250- frame .setInstructionAddr ("0x" + nativeRe .group ("pc" ));
275+ frame .setPackage (nativeRe .group (3 ));
276+ frame .setFunction (nativeRe .group (6 ));
277+ frame .setLineno (getInteger (nativeRe , 7 , null ));
278+ frame .setInstructionAddr ("0x" + nativeRe .group (2 ));
251279 frame .setPlatform ("native" );
252280
253- final String buildId = nativeRe .group ("buildid" );
281+ final String buildId = nativeRe .group (8 );
254282 final String debugId = buildId == null ? null : buildIdToDebugId (buildId );
255283 if (debugId != null ) {
256284 if (!debugImages .containsKey (debugId )) {
257285 final DebugImage debugImage = new DebugImage ();
258286 debugImage .setDebugId (debugId );
259287 debugImage .setType ("elf" );
260- debugImage .setCodeFile (nativeRe .group ("filename" ));
288+ debugImage .setCodeFile (nativeRe .group (4 ));
261289 debugImage .setCodeId (buildId );
262290 debugImages .put (debugId , debugImage );
263291 }
@@ -267,18 +295,6 @@ private SentryStackTrace parseStacktrace(
267295
268296 frames .add (frame );
269297 lastJavaFrame = null ;
270- } else if (matches (javaRe , text )) {
271- final SentryStackFrame frame = new SentryStackFrame ();
272- final String packageName = javaRe .group (1 );
273- final String className = javaRe .group (2 );
274- final String module = String .format ("%s.%s" , packageName , className );
275- frame .setModule (module );
276- frame .setFunction (javaRe .group (3 ));
277- frame .setFilename (javaRe .group (4 ));
278- frame .setLineno (getUInteger (javaRe , 5 , null ));
279- frame .setInApp (stackTraceFactory .isInApp (module ));
280- frames .add (frame );
281- lastJavaFrame = frame ;
282298 } else if (matches (jniRe , text )) {
283299 final SentryStackFrame frame = new SentryStackFrame ();
284300 final String packageName = jniRe .group (1 );
@@ -395,8 +411,8 @@ private Long getLong(
395411
396412 @ Nullable
397413 private Integer getInteger (
398- final @ NotNull Matcher matcher , final String group , final @ Nullable Integer defaultValue ) {
399- final String str = matcher .group (group );
414+ final @ NotNull Matcher matcher , final int groupIndex , final @ Nullable Integer defaultValue ) {
415+ final String str = matcher .group (groupIndex );
400416 if (str == null || str .length () == 0 ) {
401417 return defaultValue ;
402418 } else {
0 commit comments