@@ -17,12 +17,39 @@ final _vmFrame = RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$');
17
17
// at VW.call$0 (eval as fn
18
18
// (https://example.com/stuff.dart.js:560:28), efn:3:28)
19
19
// at https://example.com/stuff.dart.js:560:28
20
- final _v8Frame =
20
+ final _v8JsFrame =
21
21
RegExp (r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$' );
22
22
23
23
// https://example.com/stuff.dart.js:560:28
24
24
// https://example.com/stuff.dart.js:560
25
- final _v8UrlLocation = RegExp (r'^(.*?):(\d+)(?::(\d+))?$|native$' );
25
+ //
26
+ // Group 1: URI, required
27
+ // Group 2: line number, required
28
+ // Group 3: column number, optional
29
+ final _v8JsUrlLocation = RegExp (r'^(.*?):(\d+)(?::(\d+))?$|native$' );
30
+
31
+ // With names:
32
+ //
33
+ // at Error.f (wasm://wasm/0006d966:wasm-function[119]:0xbb13)
34
+ // at g (wasm://wasm/0006d966:wasm-function[796]:0x143b4)
35
+ //
36
+ // Without names:
37
+ //
38
+ // at wasm://wasm/0005168a:wasm-function[119]:0xbb13
39
+ // at wasm://wasm/0005168a:wasm-function[796]:0x143b4
40
+ //
41
+ // Matches named groups:
42
+ //
43
+ // - "member": optional, `Error.f` in the first example, NA in the second.
44
+ // - "uri": `wasm://wasm/0006d966`.
45
+ // - "index": `119`.
46
+ // - "offset": (hex number) `bb13`.
47
+ //
48
+ // To avoid having multiple groups for the same part of the frame, this regex
49
+ // matches unmatched parentheses after the member name.
50
+ final _v8WasmFrame = RegExp (r'^\s*at (?:(?<member>.+) )?'
51
+ r'(?:\(?(?:(?<uri>wasm:\S+):wasm-function\[(?<index>\d+)\]'
52
+ r'\:0x(?<offset>[0-9a-fA-F]+))\)?)$' );
26
53
27
54
// eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28
28
55
// eval as function (https://example.com/stuff.dart.js:560:28)
@@ -41,7 +68,7 @@ final _firefoxEvalLocation =
41
68
// .VW.call$0/name<@https://example.com/stuff.dart.js:560
42
69
// .VW.call$0@https://example.com/stuff.dart.js:560:36
43
70
// https://example.com/stuff.dart.js:560
44
- final _firefoxSafariFrame = RegExp (r'^'
71
+ final _firefoxSafariJSFrame = RegExp (r'^'
45
72
r'(?:' // Member description. Not present in some Safari frames.
46
73
r'([^@(/]*)' // The actual name of the member.
47
74
r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox.
@@ -56,6 +83,58 @@ final _firefoxSafariFrame = RegExp(r'^'
56
83
// empty in Safari if it's unknown.
57
84
r'$' );
58
85
86
+ // With names:
87
+ //
88
+ // g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4
89
+ // f@http://localhost:8080/test.wasm:wasm-function[795]:0x143a8
90
+ // main@http://localhost:8080/test.wasm:wasm-function[792]:0x14390
91
+ //
92
+ // Without names:
93
+ //
94
+ // @http://localhost:8080/test.wasm:wasm-function[796]:0x143b4
95
+ // @http://localhost:8080/test.wasm:wasm-function[795]:0x143a8
96
+ // @http://localhost:8080/test.wasm:wasm-function[792]:0x14390
97
+ //
98
+ // JSShell in the command line uses a different format, which this regex also
99
+ // parses.
100
+ //
101
+ // With names:
102
+ //
103
+ // main@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378
104
+ //
105
+ // Without names:
106
+ //
107
+ // @/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378
108
+ //
109
+ // Matches named groups:
110
+ //
111
+ // - "member": Function name, may be empty: `g`.
112
+ // - "uri": `http://localhost:8080/test.wasm`.
113
+ // - "index": `796`.
114
+ // - "offset": (in hex) `143b4`.
115
+ final _firefoxWasmFrame =
116
+ RegExp (r'^(?<member>.*?)@(?:(?<uri>\S+).*?:wasm-function'
117
+ r'\[(?<index>\d+)\]:0x(?<offset>[0-9a-fA-F]+))$' );
118
+
119
+ // With names:
120
+ //
121
+ // (Note: Lines below are literal text, e.g. <?> is not a placeholder, it's a
122
+ // part of the stack frame.)
123
+ //
124
+ // <?>.wasm-function[g]@[wasm code]
125
+ // <?>.wasm-function[f]@[wasm code]
126
+ // <?>.wasm-function[main]@[wasm code]
127
+ //
128
+ // Without names:
129
+ //
130
+ // <?>.wasm-function[796]@[wasm code]
131
+ // <?>.wasm-function[795]@[wasm code]
132
+ // <?>.wasm-function[792]@[wasm code]
133
+ //
134
+ // Matches named group "member": `g` or `796`.
135
+ final _safariWasmFrame =
136
+ RegExp (r'^.*?wasm-function\[(?<member>.*)\]@\[wasm code\]$' );
137
+
59
138
// foo/bar.dart 10:11 Foo._bar
60
139
// foo/bar.dart 10:11 (anonymous function).dart.fn
61
140
// https://dart.dev/foo/bar.dart Foo._bar
@@ -163,48 +242,62 @@ class Frame {
163
242
164
243
/// Parses a string representation of a Chrome/V8 stack frame.
165
244
factory Frame .parseV8 (String frame) => _catchFormatException (frame, () {
166
- var match = _v8Frame.firstMatch (frame);
167
- if (match == null ) return UnparsedFrame (frame);
245
+ // Try to match a Wasm frame first: the Wasm frame regex won't match a
246
+ // JS frame but the JS frame regex may match a Wasm frame.
247
+ var match = _v8WasmFrame.firstMatch (frame);
248
+ if (match != null ) {
249
+ final member = match.namedGroup ('member' );
250
+ final uri = _uriOrPathToUri (match.namedGroup ('uri' )! );
251
+ final functionIndex = match.namedGroup ('index' )! ;
252
+ final functionOffset =
253
+ int .parse (match.namedGroup ('offset' )! , radix: 16 );
254
+ return Frame (uri, 1 , functionOffset + 1 , member ?? functionIndex);
255
+ }
168
256
169
- // v8 location strings can be arbitrarily-nested, since it adds a layer
170
- // of nesting for each eval performed on that line.
171
- Frame parseLocation (String location, String member) {
172
- var evalMatch = _v8EvalLocation.firstMatch (location);
173
- while (evalMatch != null ) {
174
- location = evalMatch[1 ]! ;
175
- evalMatch = _v8EvalLocation.firstMatch (location);
257
+ match = _v8JsFrame.firstMatch (frame);
258
+ if (match != null ) {
259
+ // v8 location strings can be arbitrarily-nested, since it adds a
260
+ // layer of nesting for each eval performed on that line.
261
+ Frame parseJsLocation (String location, String member) {
262
+ var evalMatch = _v8EvalLocation.firstMatch (location);
263
+ while (evalMatch != null ) {
264
+ location = evalMatch[1 ]! ;
265
+ evalMatch = _v8EvalLocation.firstMatch (location);
266
+ }
267
+
268
+ if (location == 'native' ) {
269
+ return Frame (Uri .parse ('native' ), null , null , member);
270
+ }
271
+
272
+ var urlMatch = _v8JsUrlLocation.firstMatch (location);
273
+ if (urlMatch == null ) return UnparsedFrame (frame);
274
+
275
+ final uri = _uriOrPathToUri (urlMatch[1 ]! );
276
+ final line = int .parse (urlMatch[2 ]! );
277
+ final columnMatch = urlMatch[3 ];
278
+ final column = columnMatch != null ? int .parse (columnMatch) : null ;
279
+ return Frame (uri, line, column, member);
176
280
}
177
281
178
- if (location == 'native' ) {
179
- return Frame (Uri .parse ('native' ), null , null , member);
282
+ // V8 stack frames can be in two forms.
283
+ if (match[2 ] != null ) {
284
+ // The first form looks like " at FUNCTION (LOCATION)". V8 proper
285
+ // lists anonymous functions within eval as "<anonymous>", while
286
+ // IE10 lists them as "Anonymous function".
287
+ return parseJsLocation (
288
+ match[2 ]! ,
289
+ match[1 ]!
290
+ .replaceAll ('<anonymous>' , '<fn>' )
291
+ .replaceAll ('Anonymous function' , '<fn>' )
292
+ .replaceAll ('(anonymous function)' , '<fn>' ));
293
+ } else {
294
+ // The second form looks like " at LOCATION", and is used for
295
+ // anonymous functions.
296
+ return parseJsLocation (match[3 ]! , '<fn>' );
180
297
}
181
-
182
- var urlMatch = _v8UrlLocation.firstMatch (location);
183
- if (urlMatch == null ) return UnparsedFrame (frame);
184
-
185
- final uri = _uriOrPathToUri (urlMatch[1 ]! );
186
- final line = int .parse (urlMatch[2 ]! );
187
- final columnMatch = urlMatch[3 ];
188
- final column = columnMatch != null ? int .parse (columnMatch) : null ;
189
- return Frame (uri, line, column, member);
190
298
}
191
299
192
- // V8 stack frames can be in two forms.
193
- if (match[2 ] != null ) {
194
- // The first form looks like " at FUNCTION (LOCATION)". V8 proper
195
- // lists anonymous functions within eval as "<anonymous>", while IE10
196
- // lists them as "Anonymous function".
197
- return parseLocation (
198
- match[2 ]! ,
199
- match[1 ]!
200
- .replaceAll ('<anonymous>' , '<fn>' )
201
- .replaceAll ('Anonymous function' , '<fn>' )
202
- .replaceAll ('(anonymous function)' , '<fn>' ));
203
- } else {
204
- // The second form looks like " at LOCATION", and is used for
205
- // anonymous functions.
206
- return parseLocation (match[3 ]! , '<fn>' );
207
- }
300
+ return UnparsedFrame (frame);
208
301
});
209
302
210
303
/// Parses a string representation of a JavaScriptCore stack trace.
@@ -237,35 +330,54 @@ class Frame {
237
330
return Frame (uri, line, null , member);
238
331
});
239
332
240
- /// Parses a string representation of a Firefox stack frame.
333
+ /// Parses a string representation of a Firefox or Safari stack frame.
241
334
factory Frame .parseFirefox (String frame) => _catchFormatException (frame, () {
242
- var match = _firefoxSafariFrame.firstMatch (frame);
243
- if (match == null ) return UnparsedFrame (frame);
335
+ var match = _firefoxSafariJSFrame.firstMatch (frame);
336
+ if (match != null ) {
337
+ if (match[3 ]! .contains (' line ' )) {
338
+ return Frame ._parseFirefoxEval (frame);
339
+ }
244
340
245
- if (match[3 ]! .contains (' line ' )) {
246
- return Frame ._parseFirefoxEval (frame);
247
- }
341
+ // Normally this is a URI, but in a jsshell trace it can be a path.
342
+ var uri = _uriOrPathToUri (match[3 ]! );
248
343
249
- // Normally this is a URI, but in a jsshell trace it can be a path.
250
- var uri = _uriOrPathToUri (match[3 ]! );
344
+ var member = match[1 ];
345
+ if (member != null ) {
346
+ member +=
347
+ List .filled ('/' .allMatches (match[2 ]! ).length, '.<fn>' ).join ();
348
+ if (member == '' ) member = '<fn>' ;
251
349
252
- var member = match[1 ];
253
- if (member != null ) {
254
- member +=
255
- List .filled ('/' .allMatches (match[2 ]! ).length, '.<fn>' ).join ();
256
- if (member == '' ) member = '<fn>' ;
350
+ // Some Firefox members have initial dots. We remove them for
351
+ // consistency with other platforms.
352
+ member = member.replaceFirst (_initialDot, '' );
353
+ } else {
354
+ member = '<fn>' ;
355
+ }
257
356
258
- // Some Firefox members have initial dots. We remove them for
259
- // consistency with other platforms.
260
- member = member.replaceFirst (_initialDot, '' );
261
- } else {
262
- member = '<fn>' ;
357
+ var line = match[4 ] == '' ? null : int .parse (match[4 ]! );
358
+ var column =
359
+ match[5 ] == null || match[5 ] == '' ? null : int .parse (match[5 ]! );
360
+ return Frame (uri, line, column, member);
263
361
}
264
362
265
- var line = match[4 ] == '' ? null : int .parse (match[4 ]! );
266
- var column =
267
- match[5 ] == null || match[5 ] == '' ? null : int .parse (match[5 ]! );
268
- return Frame (uri, line, column, member);
363
+ match = _firefoxWasmFrame.firstMatch (frame);
364
+ if (match != null ) {
365
+ final member = match.namedGroup ('member' )! ;
366
+ final uri = _uriOrPathToUri (match.namedGroup ('uri' )! );
367
+ final functionIndex = match.namedGroup ('index' )! ;
368
+ final functionOffset =
369
+ int .parse (match.namedGroup ('offset' )! , radix: 16 );
370
+ return Frame (uri, 1 , functionOffset + 1 ,
371
+ member.isNotEmpty ? member : functionIndex);
372
+ }
373
+
374
+ match = _safariWasmFrame.firstMatch (frame);
375
+ if (match != null ) {
376
+ final member = match.namedGroup ('member' )! ;
377
+ return Frame (Uri (path: 'wasm code' ), null , null , member);
378
+ }
379
+
380
+ return UnparsedFrame (frame);
269
381
});
270
382
271
383
/// Parses a string representation of a Safari 6.0 stack frame.
0 commit comments