@@ -17,12 +17,39 @@ final _vmFrame = RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$');
1717// at VW.call$0 (eval as fn
1818// (https://example.com/stuff.dart.js:560:28), efn:3:28)
1919// at https://example.com/stuff.dart.js:560:28
20- final _v8Frame =
20+ final _v8JsFrame =
2121 RegExp (r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$' );
2222
2323// https://example.com/stuff.dart.js:560:28
2424// 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]+))\)?)$' );
2653
2754// eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28
2855// eval as function (https://example.com/stuff.dart.js:560:28)
@@ -41,7 +68,7 @@ final _firefoxEvalLocation =
4168// .VW.call$0/name<@https://example.com/stuff.dart.js:560
4269// .VW.call$0@https://example.com/stuff.dart.js:560:36
4370// https://example.com/stuff.dart.js:560
44- final _firefoxSafariFrame = RegExp (r'^'
71+ final _firefoxSafariJSFrame = RegExp (r'^'
4572 r'(?:' // Member description. Not present in some Safari frames.
4673 r'([^@(/]*)' // The actual name of the member.
4774 r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox.
@@ -56,6 +83,58 @@ final _firefoxSafariFrame = RegExp(r'^'
5683 // empty in Safari if it's unknown.
5784 r'$' );
5885
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+
59138// foo/bar.dart 10:11 Foo._bar
60139// foo/bar.dart 10:11 (anonymous function).dart.fn
61140// https://dart.dev/foo/bar.dart Foo._bar
@@ -163,48 +242,62 @@ class Frame {
163242
164243 /// Parses a string representation of a Chrome/V8 stack frame.
165244 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+ }
168256
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);
176280 }
177281
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>' );
180297 }
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);
190298 }
191299
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);
208301 });
209302
210303 /// Parses a string representation of a JavaScriptCore stack trace.
@@ -237,35 +330,54 @@ class Frame {
237330 return Frame (uri, line, null , member);
238331 });
239332
240- /// Parses a string representation of a Firefox stack frame.
333+ /// Parses a string representation of a Firefox or Safari stack frame.
241334 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+ }
244340
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 ]! );
248343
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>' ;
251349
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+ }
257356
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);
263361 }
264362
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);
269381 });
270382
271383 /// Parses a string representation of a Safari 6.0 stack frame.
0 commit comments