From 20761c4a1c5d4a7d35946c23491dcfd13b71328e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 16 Sep 2024 13:55:32 +0200 Subject: [PATCH 01/24] Handle V8 Wasm frames --- lib/src/frame.dart | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 88670a1..0c0acb3 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -12,17 +12,34 @@ import 'unparsed_frame.dart'; // #1 Foo._bar (file:///home/nweiz/code/stuff.dart) final _vmFrame = RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$'); +// JS examples: +// // at Object.stringify (native) // at VW.call$0 (https://example.com/stuff.dart.js:560:28) // at VW.call$0 (eval as fn // (https://example.com/stuff.dart.js:560:28), efn:3:28) // at https://example.com/stuff.dart.js:560:28 +// +// Wasm examples: +// +// at Error._throwWithCurrentStackTrace (wasm://wasm/0006d966:wasm-function[119]:0xbb13) +// at g (wasm://wasm/0006d966:wasm-function[796]:0x143b4) final _v8Frame = RegExp(r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$'); // https://example.com/stuff.dart.js:560:28 // https://example.com/stuff.dart.js:560 -final _v8UrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); +// +// Group 1: URI, required +// Group 2: line number, required +// Group 3: column number, optional +final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); + +// wasm://wasm/0006d966:wasm-function[796]:0x143b4 +// +// Captures only one group with the all of the URL as Wasm stack traces don't +// include line and column information, even with source maps. +final _v8WasmUrlLocation = RegExp(r'^wasm:\/\/.*$'); // eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28 // eval as function (https://example.com/stuff.dart.js:560:28) @@ -179,14 +196,22 @@ class Frame { return Frame(Uri.parse('native'), null, null, member); } - var urlMatch = _v8UrlLocation.firstMatch(location); - if (urlMatch == null) return UnparsedFrame(frame); + final v8UrlMatch = _v8JsUrlLocation.firstMatch(location); + if (v8UrlMatch != null) { + final uri = _uriOrPathToUri(v8UrlMatch[1]!); + final line = int.parse(v8UrlMatch[2]!); + final columnMatch = v8UrlMatch[3]; + final column = columnMatch != null ? int.parse(columnMatch) : null; + return Frame(uri, line, column, member); + } + + final v8WasmUrlMatch = _v8WasmUrlLocation.firstMatch(location); + if (v8WasmUrlMatch != null) { + final uri = _uriOrPathToUri(v8WasmUrlMatch[0]!); + return Frame(uri, null, null, member); + } - final uri = _uriOrPathToUri(urlMatch[1]!); - final line = int.parse(urlMatch[2]!); - final columnMatch = urlMatch[3]; - final column = columnMatch != null ? int.parse(columnMatch) : null; - return Frame(uri, line, column, member); + return UnparsedFrame(frame); } // V8 stack frames can be in two forms. From 546e3cbfe31eae0bc2e07cf759cb20782352dd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 10:43:15 +0200 Subject: [PATCH 02/24] Add Firefox and Safari frames --- lib/src/frame.dart | 95 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 0c0acb3..998f4bc 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -58,7 +58,7 @@ final _firefoxEvalLocation = // .VW.call$0/name<@https://example.com/stuff.dart.js:560 // .VW.call$0@https://example.com/stuff.dart.js:560:36 // https://example.com/stuff.dart.js:560 -final _firefoxSafariFrame = RegExp(r'^' +final _firefoxSafariJSFrame = RegExp(r'^' r'(?:' // Member description. Not present in some Safari frames. r'([^@(/]*)' // The actual name of the member. r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox. @@ -73,6 +73,38 @@ final _firefoxSafariFrame = RegExp(r'^' // empty in Safari if it's unknown. r'$'); +// With names: +// +// g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4 +// f@http://localhost:8080/test.wasm:wasm-function[795]:0x143a8 +// main@http://localhost:8080/test.wasm:wasm-function[792]:0x14390 +// +// Without names: +// +// @http://localhost:8080/test.wasm:wasm-function[796]:0x143b4 +// @http://localhost:8080/test.wasm:wasm-function[795]:0x143a8 +// @http://localhost:8080/test.wasm:wasm-function[792]:0x14390 +// +// Group 1: Function name, may be empty. +// Group 2: URI after the '@'. +// Group 3: Function index. +final _firefoxWasmFrame = RegExp(r'^(.*)@(.*\[(\d+)\]:.*)$'); + +// With names: +// +// .wasm-function[g]@[wasm code] +// .wasm-function[f]@[wasm code] +// .wasm-function[main]@[wasm code] +// +// Without names: +// +// .wasm-function[796]@[wasm code] +// .wasm-function[795]@[wasm code] +// .wasm-function[792]@[wasm code] +// +// Group 1: Function name or index. +final _safariWasmFrame = RegExp(r'^.*\[(.*)\]@\[wasm code\]$'); + // foo/bar.dart 10:11 Foo._bar // foo/bar.dart 10:11 (anonymous function).dart.fn // https://dart.dev/foo/bar.dart Foo._bar @@ -259,35 +291,52 @@ class Frame { return Frame(uri, line, null, member); }); - /// Parses a string representation of a Firefox stack frame. + /// Parses a string representation of a Firefox or Safari stack frame. factory Frame.parseFirefox(String frame) => _catchFormatException(frame, () { - var match = _firefoxSafariFrame.firstMatch(frame); - if (match == null) return UnparsedFrame(frame); + var match = _firefoxSafariJSFrame.firstMatch(frame); + if (match != null) { + if (match[3]!.contains(' line ')) { + return Frame._parseFirefoxEval(frame); + } - if (match[3]!.contains(' line ')) { - return Frame._parseFirefoxEval(frame); - } + // Normally this is a URI, but in a jsshell trace it can be a path. + var uri = _uriOrPathToUri(match[3]!); - // Normally this is a URI, but in a jsshell trace it can be a path. - var uri = _uriOrPathToUri(match[3]!); + var member = match[1]; + if (member != null) { + member += + List.filled('/'.allMatches(match[2]!).length, '.').join(); + if (member == '') member = ''; - var member = match[1]; - if (member != null) { - member += - List.filled('/'.allMatches(match[2]!).length, '.').join(); - if (member == '') member = ''; + // Some Firefox members have initial dots. We remove them for + // consistency with other platforms. + member = member.replaceFirst(_initialDot, ''); + } else { + member = ''; + } - // Some Firefox members have initial dots. We remove them for - // consistency with other platforms. - member = member.replaceFirst(_initialDot, ''); - } else { - member = ''; + var line = match[4] == '' ? null : int.parse(match[4]!); + var column = + match[5] == null || match[5] == '' ? null : int.parse(match[5]!); + return Frame(uri, line, column, member); } - var line = match[4] == '' ? null : int.parse(match[4]!); - var column = - match[5] == null || match[5] == '' ? null : int.parse(match[5]!); - return Frame(uri, line, column, member); + match = _firefoxWasmFrame.firstMatch(frame); + if (match != null) { + final member = match[1]!; + final uri = _uriOrPathToUri(match[2]!); + final functionIndex = match[3]!; + return Frame( + uri, null, null, member.isNotEmpty ? member : functionIndex); + } + + match = _safariWasmFrame.firstMatch(frame); + if (match != null) { + final member = match[1]!; + return Frame(Uri(path: 'wasm code'), null, null, member); + } + + return UnparsedFrame(frame); }); /// Parses a string representation of a Safari 6.0 stack frame. From fca651a546a507b35ee5fe239e3e3eb4014b859b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:24:43 +0200 Subject: [PATCH 03/24] Tweaks and tests --- lib/src/frame.dart | 118 +++++++++++++++++++++++++------------------ test/frame_test.dart | 20 ++++++++ 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 998f4bc..0cf1bfb 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -12,19 +12,12 @@ import 'unparsed_frame.dart'; // #1 Foo._bar (file:///home/nweiz/code/stuff.dart) final _vmFrame = RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$'); -// JS examples: -// // at Object.stringify (native) // at VW.call$0 (https://example.com/stuff.dart.js:560:28) // at VW.call$0 (eval as fn // (https://example.com/stuff.dart.js:560:28), efn:3:28) // at https://example.com/stuff.dart.js:560:28 -// -// Wasm examples: -// -// at Error._throwWithCurrentStackTrace (wasm://wasm/0006d966:wasm-function[119]:0xbb13) -// at g (wasm://wasm/0006d966:wasm-function[796]:0x143b4) -final _v8Frame = +final _v8JsFrame = RegExp(r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$'); // https://example.com/stuff.dart.js:560:28 @@ -35,6 +28,28 @@ final _v8Frame = // Group 3: column number, optional final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); +// With names: +// +// at Error.f (wasm://wasm/0006d966:wasm-function[119]:0xbb13) +// at g (wasm://wasm/0006d966:wasm-function[796]:0x143b4) +// +// Without names: +// +// at wasm://wasm/0005168a:wasm-function[119]:0xbb13 +// at wasm://wasm/0005168a:wasm-function[796]:0x143b4 +// +// Group 1: Function name, optional + +// When group 1 is available: +// Group 2: URI +// Group 3: Function index +// +// Otherwise: +// Group 4: URI +// Group 5: function index +final _v8WasmFrame = RegExp(r'^\s*at(?: (\S+))? ' + r'(?:\((wasm:\S+\[(\d+)\]\S+)\)|(wasm:\S+\[(\d+)\]\S+))$'); + // wasm://wasm/0006d966:wasm-function[796]:0x143b4 // // Captures only one group with the all of the URL as Wasm stack traces don't @@ -212,56 +227,59 @@ class Frame { /// Parses a string representation of a Chrome/V8 stack frame. factory Frame.parseV8(String frame) => _catchFormatException(frame, () { - var match = _v8Frame.firstMatch(frame); - if (match == null) return UnparsedFrame(frame); - - // v8 location strings can be arbitrarily-nested, since it adds a layer - // of nesting for each eval performed on that line. - Frame parseLocation(String location, String member) { - var evalMatch = _v8EvalLocation.firstMatch(location); - while (evalMatch != null) { - location = evalMatch[1]!; - evalMatch = _v8EvalLocation.firstMatch(location); - } - - if (location == 'native') { - return Frame(Uri.parse('native'), null, null, member); - } + // Try to match a Wasm frame first: the Wasm frame regex won't match a + // JS frame but the JS frame regex may match a Wasm frame. + var match = _v8WasmFrame.firstMatch(frame); + if (match != null) { + final member = match[1]; + final uri = _uriOrPathToUri(member != null ? match[2]! : match[4]!); + return Frame(uri, null, null, member ?? match[5]!); + } - final v8UrlMatch = _v8JsUrlLocation.firstMatch(location); - if (v8UrlMatch != null) { - final uri = _uriOrPathToUri(v8UrlMatch[1]!); - final line = int.parse(v8UrlMatch[2]!); - final columnMatch = v8UrlMatch[3]; + match = _v8JsFrame.firstMatch(frame); + if (match != null) { + // v8 location strings can be arbitrarily-nested, since it adds a layer + // of nesting for each eval performed on that line. + Frame parseJsLocation(String location, String member) { + var evalMatch = _v8EvalLocation.firstMatch(location); + while (evalMatch != null) { + location = evalMatch[1]!; + evalMatch = _v8EvalLocation.firstMatch(location); + } + + if (location == 'native') { + return Frame(Uri.parse('native'), null, null, member); + } + + var urlMatch = _v8JsUrlLocation.firstMatch(location); + if (urlMatch == null) return UnparsedFrame(frame); + + final uri = _uriOrPathToUri(urlMatch[1]!); + final line = int.parse(urlMatch[2]!); + final columnMatch = urlMatch[3]; final column = columnMatch != null ? int.parse(columnMatch) : null; return Frame(uri, line, column, member); } - final v8WasmUrlMatch = _v8WasmUrlLocation.firstMatch(location); - if (v8WasmUrlMatch != null) { - final uri = _uriOrPathToUri(v8WasmUrlMatch[0]!); - return Frame(uri, null, null, member); + // V8 stack frames can be in two forms. + if (match[2] != null) { + // The first form looks like " at FUNCTION (LOCATION)". V8 proper + // lists anonymous functions within eval as "", while IE10 + // lists them as "Anonymous function". + return parseJsLocation( + match[2]!, + match[1]! + .replaceAll('', '') + .replaceAll('Anonymous function', '') + .replaceAll('(anonymous function)', '')); + } else { + // The second form looks like " at LOCATION", and is used for + // anonymous functions. + return parseJsLocation(match[3]!, ''); } - - return UnparsedFrame(frame); } - // V8 stack frames can be in two forms. - if (match[2] != null) { - // The first form looks like " at FUNCTION (LOCATION)". V8 proper - // lists anonymous functions within eval as "", while IE10 - // lists them as "Anonymous function". - return parseLocation( - match[2]!, - match[1]! - .replaceAll('', '') - .replaceAll('Anonymous function', '') - .replaceAll('(anonymous function)', '')); - } else { - // The second form looks like " at LOCATION", and is used for - // anonymous functions. - return parseLocation(match[3]!, ''); - } + return UnparsedFrame(frame); }); /// Parses a string representation of a JavaScriptCore stack trace. diff --git a/test/frame_test.dart b/test/frame_test.dart index 6e8df72..5ffa143 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -632,6 +632,26 @@ baz@https://pub.dev/buz.js:56355:55 equals('$relative 5:10 in Foo')); }); }); + + test('parses a V8 Wasm frame with a name', () { + var frame = Frame.parseV8(' at Error._throwWithCurrentStackTrace ' + '(wasm://wasm/0006d966:wasm-function[119]:0xbb13)'); + expect( + frame.uri, Uri.parse('wasm://wasm/0006d966:wasm-function[119]:0xbb13')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, 'Error._throwWithCurrentStackTrace'); + }); + + test('parses a V8 Wasm frame without a name', () { + var frame = + Frame.parseV8(' at wasm://wasm/0006d966:wasm-function[119]:0xbb13'); + expect( + frame.uri, Uri.parse('wasm://wasm/0006d966:wasm-function[119]:0xbb13')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, '119'); + }); } void expectIsUnparsed(Frame Function(String) constructor, String text) { From 88f13f132ebe1eec2b9e61c0065eff9ddaafe6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:27:16 +0200 Subject: [PATCH 04/24] Test Firefox frames --- test/frame_test.dart | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/frame_test.dart b/test/frame_test.dart index 5ffa143..94ca45f 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -652,6 +652,30 @@ baz@https://pub.dev/buz.js:56355:55 expect(frame.column, null); expect(frame.member, '119'); }); + + test('parses a Firefox Wasm frame with a name', () { + var frame = Frame.parseFirefox( + 'g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); + expect( + frame.uri, + Uri.parse( + 'http://localhost:8080/test.wasm:wasm-function[796]:0x143b4')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, 'g'); + }); + + test('parses a Firefox Wasm frame without a name', () { + var frame = Frame.parseFirefox( + '@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); + expect( + frame.uri, + Uri.parse( + 'http://localhost:8080/test.wasm:wasm-function[796]:0x143b4')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, '796'); + }); } void expectIsUnparsed(Frame Function(String) constructor, String text) { From bfaa7f9054c4c123fbd8e9bb60d30910540ca1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:28:43 +0200 Subject: [PATCH 05/24] Add Safari tests --- test/frame_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/frame_test.dart b/test/frame_test.dart index 94ca45f..0acec02 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -676,6 +676,22 @@ baz@https://pub.dev/buz.js:56355:55 expect(frame.column, null); expect(frame.member, '796'); }); + + test('parses a Safari Wasm frame with a name', () { + var frame = Frame.parseSafari('.wasm-function[g]@[wasm code]'); + expect(frame.uri, Uri.parse('wasm code')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, 'g'); + }); + + test('parses a Safari Wasm frame without a name', () { + var frame = Frame.parseSafari('.wasm-function[796]@[wasm code]'); + expect(frame.uri, Uri.parse('wasm code')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, '796'); + }); } void expectIsUnparsed(Frame Function(String) constructor, String text) { From 9afe77d26991da3294b20c4fccce30613ff0df39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:29:19 +0200 Subject: [PATCH 06/24] Comments --- lib/src/frame.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 0cf1bfb..da8b565 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -39,7 +39,7 @@ final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); // at wasm://wasm/0005168a:wasm-function[796]:0x143b4 // // Group 1: Function name, optional - +// // When group 1 is available: // Group 2: URI // Group 3: Function index From c2eda030f00fe32a0547cc1f133ad8dc7697323e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:30:20 +0200 Subject: [PATCH 07/24] Remove unused regex --- lib/src/frame.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index da8b565..3c40d2d 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -50,12 +50,6 @@ final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); final _v8WasmFrame = RegExp(r'^\s*at(?: (\S+))? ' r'(?:\((wasm:\S+\[(\d+)\]\S+)\)|(wasm:\S+\[(\d+)\]\S+))$'); -// wasm://wasm/0006d966:wasm-function[796]:0x143b4 -// -// Captures only one group with the all of the URL as Wasm stack traces don't -// include line and column information, even with source maps. -final _v8WasmUrlLocation = RegExp(r'^wasm:\/\/.*$'); - // eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28 // eval as function (https://example.com/stuff.dart.js:560:28) // eval as function (eval as otherFunction From 61918974a17999dd0209dcac95549ff9fbbf9b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:38:34 +0200 Subject: [PATCH 08/24] Fix analysis issues introduced by this PR --- lib/src/frame.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 3c40d2d..c4f2f53 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -232,8 +232,8 @@ class Frame { match = _v8JsFrame.firstMatch(frame); if (match != null) { - // v8 location strings can be arbitrarily-nested, since it adds a layer - // of nesting for each eval performed on that line. + // v8 location strings can be arbitrarily-nested, since it adds a + // layer of nesting for each eval performed on that line. Frame parseJsLocation(String location, String member) { var evalMatch = _v8EvalLocation.firstMatch(location); while (evalMatch != null) { @@ -258,8 +258,8 @@ class Frame { // V8 stack frames can be in two forms. if (match[2] != null) { // The first form looks like " at FUNCTION (LOCATION)". V8 proper - // lists anonymous functions within eval as "", while IE10 - // lists them as "Anonymous function". + // lists anonymous functions within eval as "", while + // IE10 lists them as "Anonymous function". return parseJsLocation( match[2]!, match[1]! From e3feec41ae53f7ca5d89b27973362716af67d1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 11:44:51 +0200 Subject: [PATCH 09/24] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4c77c..029f155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 1.11.2-wip * Require Dart 3.1 or greater +* Added support for parsing Wasm frames of Chrome (V8), Firefox, Safari. ## 1.11.1 From a993d21c0e5c4466443808bcf9da946033d669c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 12:18:11 +0200 Subject: [PATCH 10/24] Update Chrome frame parsing and tests --- lib/src/frame.dart | 23 +++++++++++++++-------- test/frame_test.dart | 14 ++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index c4f2f53..edb8a2a 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -38,17 +38,20 @@ final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); // at wasm://wasm/0005168a:wasm-function[119]:0xbb13 // at wasm://wasm/0005168a:wasm-function[796]:0x143b4 // -// Group 1: Function name, optional +// Group 1: Function name, optional: `Error.f`. // // When group 1 is available: -// Group 2: URI -// Group 3: Function index +// Group 2: URI: `wasm://wasm/0006d966`. +// Group 3: Function index: `119`. +// Group 4: Function offset in hex: `bb13`. // // Otherwise: -// Group 4: URI -// Group 5: function index +// Group 5: URI, same as group 2. +// Group 6: Function index, same as group 3. +// Group 7: Function offset in hex, same as group 4. final _v8WasmFrame = RegExp(r'^\s*at(?: (\S+))? ' - r'(?:\((wasm:\S+\[(\d+)\]\S+)\)|(wasm:\S+\[(\d+)\]\S+))$'); + r'(?:\((?:(wasm:\S+):wasm-function\[(\d+)\]\:0x([0-9 a-f A-F]+))\)|' + r'(?:(wasm:\S+):wasm-function\[(\d+)\]\:0x([0-9 a-f A-F]+)))$'); // eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28 // eval as function (https://example.com/stuff.dart.js:560:28) @@ -226,8 +229,12 @@ class Frame { var match = _v8WasmFrame.firstMatch(frame); if (match != null) { final member = match[1]; - final uri = _uriOrPathToUri(member != null ? match[2]! : match[4]!); - return Frame(uri, null, null, member ?? match[5]!); + final uriGroupIndex = member == null ? 5 : 2; + final uri = _uriOrPathToUri(match[uriGroupIndex]!); + final functionIndex = match[uriGroupIndex + 1]!; + final functionOffset = + int.parse(match[uriGroupIndex + 2]!, radix: 16); + return Frame(uri, 0, functionOffset, member ?? functionIndex); } match = _v8JsFrame.firstMatch(frame); diff --git a/test/frame_test.dart b/test/frame_test.dart index 0acec02..72aec41 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -636,20 +636,18 @@ baz@https://pub.dev/buz.js:56355:55 test('parses a V8 Wasm frame with a name', () { var frame = Frame.parseV8(' at Error._throwWithCurrentStackTrace ' '(wasm://wasm/0006d966:wasm-function[119]:0xbb13)'); - expect( - frame.uri, Uri.parse('wasm://wasm/0006d966:wasm-function[119]:0xbb13')); - expect(frame.line, null); - expect(frame.column, null); + expect(frame.uri, Uri.parse('wasm://wasm/0006d966')); + expect(frame.line, 0); + expect(frame.column, 0xbb13); expect(frame.member, 'Error._throwWithCurrentStackTrace'); }); test('parses a V8 Wasm frame without a name', () { var frame = Frame.parseV8(' at wasm://wasm/0006d966:wasm-function[119]:0xbb13'); - expect( - frame.uri, Uri.parse('wasm://wasm/0006d966:wasm-function[119]:0xbb13')); - expect(frame.line, null); - expect(frame.column, null); + expect(frame.uri, Uri.parse('wasm://wasm/0006d966')); + expect(frame.line, 0); + expect(frame.column, 0xbb13); expect(frame.member, '119'); }); From 9e732a2d34c583259f31b13135fc39fd3b448108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 12:24:07 +0200 Subject: [PATCH 11/24] Update Firefox parsing and tests --- lib/src/frame.dart | 17 ++++++++++------- test/frame_test.dart | 18 ++++++------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index edb8a2a..377d46f 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -97,10 +97,12 @@ final _firefoxSafariJSFrame = RegExp(r'^' // @http://localhost:8080/test.wasm:wasm-function[795]:0x143a8 // @http://localhost:8080/test.wasm:wasm-function[792]:0x14390 // -// Group 1: Function name, may be empty. -// Group 2: URI after the '@'. -// Group 3: Function index. -final _firefoxWasmFrame = RegExp(r'^(.*)@(.*\[(\d+)\]:.*)$'); +// Group 1: Function name, may be empty: `g`. +// Group 2: URI: `http://localhost:8080/test.wasm`. +// Group 3: Function index: `796`. +// Group 4: Function offset in hex: `143b4`. +final _firefoxWasmFrame = + RegExp(r'^(.*)@(?:(.*):wasm-function\[(\d+)\]:0x([0-9 a-f A-F]+))$'); // With names: // @@ -114,7 +116,7 @@ final _firefoxWasmFrame = RegExp(r'^(.*)@(.*\[(\d+)\]:.*)$'); // .wasm-function[795]@[wasm code] // .wasm-function[792]@[wasm code] // -// Group 1: Function name or index. +// Group 1: Function name or index: `g` or `796`. final _safariWasmFrame = RegExp(r'^.*\[(.*)\]@\[wasm code\]$'); // foo/bar.dart 10:11 Foo._bar @@ -345,8 +347,9 @@ class Frame { final member = match[1]!; final uri = _uriOrPathToUri(match[2]!); final functionIndex = match[3]!; - return Frame( - uri, null, null, member.isNotEmpty ? member : functionIndex); + final functionOffset = int.parse(match[4]!, radix: 16); + return Frame(uri, 0, functionOffset, + member.isNotEmpty ? member : functionIndex); } match = _safariWasmFrame.firstMatch(frame); diff --git a/test/frame_test.dart b/test/frame_test.dart index 72aec41..32292a5 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -654,24 +654,18 @@ baz@https://pub.dev/buz.js:56355:55 test('parses a Firefox Wasm frame with a name', () { var frame = Frame.parseFirefox( 'g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); - expect( - frame.uri, - Uri.parse( - 'http://localhost:8080/test.wasm:wasm-function[796]:0x143b4')); - expect(frame.line, null); - expect(frame.column, null); + expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(frame.line, 0); + expect(frame.column, 0x143b4); expect(frame.member, 'g'); }); test('parses a Firefox Wasm frame without a name', () { var frame = Frame.parseFirefox( '@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); - expect( - frame.uri, - Uri.parse( - 'http://localhost:8080/test.wasm:wasm-function[796]:0x143b4')); - expect(frame.line, null); - expect(frame.column, null); + expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(frame.line, 0); + expect(frame.column, 0x143b4); expect(frame.member, '796'); }); From fb627fb470f377ffc49cbf045402eb6f7ade9bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 13:11:01 +0200 Subject: [PATCH 12/24] Update Chrome regex --- lib/src/frame.dart | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 377d46f..1dc282b 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -38,20 +38,15 @@ final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); // at wasm://wasm/0005168a:wasm-function[119]:0xbb13 // at wasm://wasm/0005168a:wasm-function[796]:0x143b4 // -// Group 1: Function name, optional: `Error.f`. +// Matches named groups: // -// When group 1 is available: -// Group 2: URI: `wasm://wasm/0006d966`. -// Group 3: Function index: `119`. -// Group 4: Function offset in hex: `bb13`. -// -// Otherwise: -// Group 5: URI, same as group 2. -// Group 6: Function index, same as group 3. -// Group 7: Function offset in hex, same as group 4. -final _v8WasmFrame = RegExp(r'^\s*at(?: (\S+))? ' - r'(?:\((?:(wasm:\S+):wasm-function\[(\d+)\]\:0x([0-9 a-f A-F]+))\)|' - r'(?:(wasm:\S+):wasm-function\[(\d+)\]\:0x([0-9 a-f A-F]+)))$'); +// - "member": optional, `Error.f` in the first example, NA in the second. +// - "uri": `wasm://wasm/0006d966`. +// - "index": `119`. +// - "offset": (hex number) `bb13`. +final _v8WasmFrame = RegExp(r'^\s*at (?:(?\S+) )?' + r'(?:\(?(?:(?wasm:\S+):wasm-function\[(?\d+)\]' + r'\:0x(?[0-9 a-f A-F]+))\)?)$'); // eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28 // eval as function (https://example.com/stuff.dart.js:560:28) @@ -230,12 +225,11 @@ class Frame { // JS frame but the JS frame regex may match a Wasm frame. var match = _v8WasmFrame.firstMatch(frame); if (match != null) { - final member = match[1]; - final uriGroupIndex = member == null ? 5 : 2; - final uri = _uriOrPathToUri(match[uriGroupIndex]!); - final functionIndex = match[uriGroupIndex + 1]!; + final member = match.namedGroup('member'); + final uri = _uriOrPathToUri(match.namedGroup('uri')!); + final functionIndex = match.namedGroup('index')!; final functionOffset = - int.parse(match[uriGroupIndex + 2]!, radix: 16); + int.parse(match.namedGroup('offset')!, radix: 16); return Frame(uri, 0, functionOffset, member ?? functionIndex); } From 4316832aa4b7accfdde8ba11293c4a5bbae830a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 13:14:03 +0200 Subject: [PATCH 13/24] Update Firefox regex --- lib/src/frame.dart | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 1dc282b..a26483c 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -92,12 +92,14 @@ final _firefoxSafariJSFrame = RegExp(r'^' // @http://localhost:8080/test.wasm:wasm-function[795]:0x143a8 // @http://localhost:8080/test.wasm:wasm-function[792]:0x14390 // -// Group 1: Function name, may be empty: `g`. -// Group 2: URI: `http://localhost:8080/test.wasm`. -// Group 3: Function index: `796`. -// Group 4: Function offset in hex: `143b4`. -final _firefoxWasmFrame = - RegExp(r'^(.*)@(?:(.*):wasm-function\[(\d+)\]:0x([0-9 a-f A-F]+))$'); +// Matches named groups: +// +// "member": Function name, may be empty: `g`. +// "uri": `http://localhost:8080/test.wasm`. +// "index": `796`. +// "offset": (in hex) `143b4`. +final _firefoxWasmFrame = RegExp(r'^(?.*)@(?:(?.*):wasm-function' + r'\[(?\d+)\]:0x(?[0-9 a-f A-F]+))$'); // With names: // @@ -338,10 +340,11 @@ class Frame { match = _firefoxWasmFrame.firstMatch(frame); if (match != null) { - final member = match[1]!; - final uri = _uriOrPathToUri(match[2]!); - final functionIndex = match[3]!; - final functionOffset = int.parse(match[4]!, radix: 16); + final member = match.namedGroup('member')!; + final uri = _uriOrPathToUri(match.namedGroup('uri')!); + final functionIndex = match.namedGroup('index')!; + final functionOffset = + int.parse(match.namedGroup('offset')!, radix: 16); return Frame(uri, 0, functionOffset, member.isNotEmpty ? member : functionIndex); } From 2dc3d3f415f94235676179b9e3df83743fb2686e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 13:14:53 +0200 Subject: [PATCH 14/24] Update Safari regex --- lib/src/frame.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index a26483c..e0db72f 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -113,8 +113,8 @@ final _firefoxWasmFrame = RegExp(r'^(?.*)@(?:(?.*):wasm-function' // .wasm-function[795]@[wasm code] // .wasm-function[792]@[wasm code] // -// Group 1: Function name or index: `g` or `796`. -final _safariWasmFrame = RegExp(r'^.*\[(.*)\]@\[wasm code\]$'); +// Matches named group "member": `g` or `796`. +final _safariWasmFrame = RegExp(r'^.*\[(?.*)\]@\[wasm code\]$'); // foo/bar.dart 10:11 Foo._bar // foo/bar.dart 10:11 (anonymous function).dart.fn @@ -351,7 +351,7 @@ class Frame { match = _safariWasmFrame.firstMatch(frame); if (match != null) { - final member = match[1]!; + final member = match.namedGroup('member')!; return Frame(Uri(path: 'wasm code'), null, null, member); } From 357e01135136ea530f8dc333ec9a082ff9fe70ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Tue, 17 Sep 2024 13:15:40 +0200 Subject: [PATCH 15/24] Update docsg --- lib/src/frame.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index e0db72f..deb86b9 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -94,10 +94,10 @@ final _firefoxSafariJSFrame = RegExp(r'^' // // Matches named groups: // -// "member": Function name, may be empty: `g`. -// "uri": `http://localhost:8080/test.wasm`. -// "index": `796`. -// "offset": (in hex) `143b4`. +// - "member": Function name, may be empty: `g`. +// - "uri": `http://localhost:8080/test.wasm`. +// - "index": `796`. +// - "offset": (in hex) `143b4`. final _firefoxWasmFrame = RegExp(r'^(?.*)@(?:(?.*):wasm-function' r'\[(?\d+)\]:0x(?[0-9 a-f A-F]+))$'); From 817b9661969e7e9fe39ba2c9dda52328f7477408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Wed, 18 Sep 2024 11:33:13 +0200 Subject: [PATCH 16/24] Handle whitespace in member names in V8 parser, add tests --- lib/src/frame.dart | 2 +- test/frame_test.dart | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index deb86b9..c8915c1 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -44,7 +44,7 @@ final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); // - "uri": `wasm://wasm/0006d966`. // - "index": `119`. // - "offset": (hex number) `bb13`. -final _v8WasmFrame = RegExp(r'^\s*at (?:(?\S+) )?' +final _v8WasmFrame = RegExp(r'^\s*at (?:(?.+) )?' r'(?:\(?(?:(?wasm:\S+):wasm-function\[(?\d+)\]' r'\:0x(?[0-9 a-f A-F]+))\)?)$'); diff --git a/test/frame_test.dart b/test/frame_test.dart index 32292a5..ea76954 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -642,6 +642,15 @@ baz@https://pub.dev/buz.js:56355:55 expect(frame.member, 'Error._throwWithCurrentStackTrace'); }); + test('parses a V8 Wasm frame with a name with spaces', () { + var frame = Frame.parseV8(' at main tear-off trampoline ' + '(wasm://wasm/0017fbea:wasm-function[863]:0x23cc8)'); + expect(frame.uri, Uri.parse('wasm://wasm/0017fbea')); + expect(frame.line, 0); + expect(frame.column, 0x23cc8); + expect(frame.member, 'main tear-off trampoline'); + }); + test('parses a V8 Wasm frame without a name', () { var frame = Frame.parseV8(' at wasm://wasm/0006d966:wasm-function[119]:0xbb13'); @@ -660,6 +669,15 @@ baz@https://pub.dev/buz.js:56355:55 expect(frame.member, 'g'); }); + test('parses a Firefox Wasm frame with a name with spaces', () { + var frame = Frame.parseFirefox( + 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387'); + expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(frame.line, 0); + expect(frame.column, 0x14387); + expect(frame.member, 'main tear-off trampoline'); + }); + test('parses a Firefox Wasm frame without a name', () { var frame = Frame.parseFirefox( '@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); @@ -677,6 +695,15 @@ baz@https://pub.dev/buz.js:56355:55 expect(frame.member, 'g'); }); + test('parses a Safari Wasm frame with a name', () { + var frame = Frame.parseSafari( + '.wasm-function[main tear-off trampoline]@[wasm code]'); + expect(frame.uri, Uri.parse('wasm code')); + expect(frame.line, null); + expect(frame.column, null); + expect(frame.member, 'main tear-off trampoline'); + }); + test('parses a Safari Wasm frame without a name', () { var frame = Frame.parseSafari('.wasm-function[796]@[wasm code]'); expect(frame.uri, Uri.parse('wasm code')); From 3115aad495c02cd456d5e4eac72ac0617b6b3f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Wed, 18 Sep 2024 11:42:37 +0200 Subject: [PATCH 17/24] Fix line numbers --- lib/src/frame.dart | 4 ++-- test/frame_test.dart | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 166ac4d..8ce9994 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -232,7 +232,7 @@ class Frame { final functionIndex = match.namedGroup('index')!; final functionOffset = int.parse(match.namedGroup('offset')!, radix: 16); - return Frame(uri, 0, functionOffset, member ?? functionIndex); + return Frame(uri, 1, functionOffset, member ?? functionIndex); } match = _v8JsFrame.firstMatch(frame); @@ -348,7 +348,7 @@ class Frame { final functionIndex = match.namedGroup('index')!; final functionOffset = int.parse(match.namedGroup('offset')!, radix: 16); - return Frame(uri, 0, functionOffset, + return Frame(uri, 1, functionOffset, member.isNotEmpty ? member : functionIndex); } diff --git a/test/frame_test.dart b/test/frame_test.dart index ea76954..07b6fba 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -637,7 +637,7 @@ baz@https://pub.dev/buz.js:56355:55 var frame = Frame.parseV8(' at Error._throwWithCurrentStackTrace ' '(wasm://wasm/0006d966:wasm-function[119]:0xbb13)'); expect(frame.uri, Uri.parse('wasm://wasm/0006d966')); - expect(frame.line, 0); + expect(frame.line, 1); expect(frame.column, 0xbb13); expect(frame.member, 'Error._throwWithCurrentStackTrace'); }); @@ -646,7 +646,7 @@ baz@https://pub.dev/buz.js:56355:55 var frame = Frame.parseV8(' at main tear-off trampoline ' '(wasm://wasm/0017fbea:wasm-function[863]:0x23cc8)'); expect(frame.uri, Uri.parse('wasm://wasm/0017fbea')); - expect(frame.line, 0); + expect(frame.line, 1); expect(frame.column, 0x23cc8); expect(frame.member, 'main tear-off trampoline'); }); @@ -655,7 +655,7 @@ baz@https://pub.dev/buz.js:56355:55 var frame = Frame.parseV8(' at wasm://wasm/0006d966:wasm-function[119]:0xbb13'); expect(frame.uri, Uri.parse('wasm://wasm/0006d966')); - expect(frame.line, 0); + expect(frame.line, 1); expect(frame.column, 0xbb13); expect(frame.member, '119'); }); @@ -664,7 +664,7 @@ baz@https://pub.dev/buz.js:56355:55 var frame = Frame.parseFirefox( 'g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); - expect(frame.line, 0); + expect(frame.line, 1); expect(frame.column, 0x143b4); expect(frame.member, 'g'); }); @@ -673,7 +673,7 @@ baz@https://pub.dev/buz.js:56355:55 var frame = Frame.parseFirefox( 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387'); expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); - expect(frame.line, 0); + expect(frame.line, 1); expect(frame.column, 0x14387); expect(frame.member, 'main tear-off trampoline'); }); @@ -682,7 +682,7 @@ baz@https://pub.dev/buz.js:56355:55 var frame = Frame.parseFirefox( '@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); - expect(frame.line, 0); + expect(frame.line, 1); expect(frame.column, 0x143b4); expect(frame.member, '796'); }); From 102c0252dcb245161bcdf6d972317e02901d9785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Wed, 18 Sep 2024 11:47:27 +0200 Subject: [PATCH 18/24] Fix column nums --- lib/src/frame.dart | 4 ++-- test/frame_test.dart | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 8ce9994..377f9a7 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -232,7 +232,7 @@ class Frame { final functionIndex = match.namedGroup('index')!; final functionOffset = int.parse(match.namedGroup('offset')!, radix: 16); - return Frame(uri, 1, functionOffset, member ?? functionIndex); + return Frame(uri, 1, functionOffset + 1, member ?? functionIndex); } match = _v8JsFrame.firstMatch(frame); @@ -348,7 +348,7 @@ class Frame { final functionIndex = match.namedGroup('index')!; final functionOffset = int.parse(match.namedGroup('offset')!, radix: 16); - return Frame(uri, 1, functionOffset, + return Frame(uri, 1, functionOffset + 1, member.isNotEmpty ? member : functionIndex); } diff --git a/test/frame_test.dart b/test/frame_test.dart index 07b6fba..e62e843 100644 --- a/test/frame_test.dart +++ b/test/frame_test.dart @@ -638,7 +638,7 @@ baz@https://pub.dev/buz.js:56355:55 '(wasm://wasm/0006d966:wasm-function[119]:0xbb13)'); expect(frame.uri, Uri.parse('wasm://wasm/0006d966')); expect(frame.line, 1); - expect(frame.column, 0xbb13); + expect(frame.column, 0xbb13 + 1); expect(frame.member, 'Error._throwWithCurrentStackTrace'); }); @@ -647,7 +647,7 @@ baz@https://pub.dev/buz.js:56355:55 '(wasm://wasm/0017fbea:wasm-function[863]:0x23cc8)'); expect(frame.uri, Uri.parse('wasm://wasm/0017fbea')); expect(frame.line, 1); - expect(frame.column, 0x23cc8); + expect(frame.column, 0x23cc8 + 1); expect(frame.member, 'main tear-off trampoline'); }); @@ -656,7 +656,7 @@ baz@https://pub.dev/buz.js:56355:55 Frame.parseV8(' at wasm://wasm/0006d966:wasm-function[119]:0xbb13'); expect(frame.uri, Uri.parse('wasm://wasm/0006d966')); expect(frame.line, 1); - expect(frame.column, 0xbb13); + expect(frame.column, 0xbb13 + 1); expect(frame.member, '119'); }); @@ -665,7 +665,7 @@ baz@https://pub.dev/buz.js:56355:55 'g@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); expect(frame.line, 1); - expect(frame.column, 0x143b4); + expect(frame.column, 0x143b4 + 1); expect(frame.member, 'g'); }); @@ -674,7 +674,7 @@ baz@https://pub.dev/buz.js:56355:55 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387'); expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); expect(frame.line, 1); - expect(frame.column, 0x14387); + expect(frame.column, 0x14387 + 1); expect(frame.member, 'main tear-off trampoline'); }); @@ -683,7 +683,7 @@ baz@https://pub.dev/buz.js:56355:55 '@http://localhost:8080/test.wasm:wasm-function[796]:0x143b4'); expect(frame.uri, Uri.parse('http://localhost:8080/test.wasm')); expect(frame.line, 1); - expect(frame.column, 0x143b4); + expect(frame.column, 0x143b4 + 1); expect(frame.member, '796'); }); From 95abc380aa3b04ee4ed3a49961a4aedc1828bd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 19 Sep 2024 09:36:59 +0200 Subject: [PATCH 19/24] Address some of the comments --- lib/src/frame.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index 377f9a7..e571040 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -44,9 +44,12 @@ final _v8JsUrlLocation = RegExp(r'^(.*?):(\d+)(?::(\d+))?$|native$'); // - "uri": `wasm://wasm/0006d966`. // - "index": `119`. // - "offset": (hex number) `bb13`. +// +// To avoid having multiple groups for the same part of the frame, this regex +// matches unmatched parentheses after the member name. final _v8WasmFrame = RegExp(r'^\s*at (?:(?.+) )?' r'(?:\(?(?:(?wasm:\S+):wasm-function\[(?\d+)\]' - r'\:0x(?[0-9 a-f A-F]+))\)?)$'); + r'\:0x(?[0-9a-fA-F]+))\)?)$'); // eval as function (https://example.com/stuff.dart.js:560:28), efn:3:28 // eval as function (https://example.com/stuff.dart.js:560:28) @@ -98,8 +101,8 @@ final _firefoxSafariJSFrame = RegExp(r'^' // - "uri": `http://localhost:8080/test.wasm`. // - "index": `796`. // - "offset": (in hex) `143b4`. -final _firefoxWasmFrame = RegExp(r'^(?.*)@(?:(?.*):wasm-function' - r'\[(?\d+)\]:0x(?[0-9 a-f A-F]+))$'); +final _firefoxWasmFrame = RegExp(r'^(?.*?)@(?:(?.*?):wasm-function' + r'\[(?\d+)\]:0x(?[0-9a-fA-F]+))$'); // With names: // @@ -114,7 +117,8 @@ final _firefoxWasmFrame = RegExp(r'^(?.*)@(?:(?.*):wasm-function' // .wasm-function[792]@[wasm code] // // Matches named group "member": `g` or `796`. -final _safariWasmFrame = RegExp(r'^.*\[(?.*)\]@\[wasm code\]$'); +final _safariWasmFrame = + RegExp(r'^.*?wasm-function\[(?.*)\]@\[wasm code\]$'); // foo/bar.dart 10:11 Foo._bar // foo/bar.dart 10:11 (anonymous function).dart.fn From 33f6e8ef1400215d3c3bd7b1d1dff954f7d28c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 19 Sep 2024 09:45:03 +0200 Subject: [PATCH 20/24] Clarify syntax --- lib/src/frame.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index e571040..ba5cba0 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -106,6 +106,9 @@ final _firefoxWasmFrame = RegExp(r'^(?.*?)@(?:(?.*?):wasm-function' // With names: // +// (Note: Lines below are literal text, e.g. is not a placeholder, it's a +// part of the stack frame.) +// // .wasm-function[g]@[wasm code] // .wasm-function[f]@[wasm code] // .wasm-function[main]@[wasm code] From fc3a86d31d5940b83db976f37369da0ccf41d9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 19 Sep 2024 10:26:25 +0200 Subject: [PATCH 21/24] More tests --- test/trace_test.dart | 132 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/test/trace_test.dart b/test/trace_test.dart index d4bce76..a37c5a9 100644 --- a/test/trace_test.dart +++ b/test/trace_test.dart @@ -296,6 +296,138 @@ void main() { expect(trace.frames[0].uri, equals(Uri.parse('file:///pull.dart'))); expect(trace.frames[1].uri, equals(Uri.parse('dart:the/future.dart'))); }); + + test('parses a V8 stack frace with Wasm frames correctly', () { + var trace = Trace.parse( + '\tat Error._throwWithCurrentStackTrace (wasm://wasm/0006d892:wasm-function[119]:0xbaf8)\n' + '\tat main (wasm://wasm/0006d892:wasm-function[792]:0x14378)\n' + '\tat main tear-off trampoline (wasm://wasm/0006d892:wasm-function[794]:0x14387)\n' + '\tat _invokeMain (wasm://wasm/0006d892:wasm-function[70]:0xa56c)\n' + '\tat InstantiatedApp.invokeMain (/home/user/test.mjs:361:37)\n' + '\tat main (/home/user/run_wasm.js:416:21)\n' + '\tat async action (/home/user/run_wasm.js:353:38)\n' + '\tat async eventLoop (/home/user/run_wasm.js:329:9)'); + + expect(trace.frames.length, 8); + + expect(trace.frames[0].uri, Uri.parse('wasm://wasm/0006d892')); + expect(trace.frames[0].line, 1); + expect(trace.frames[0].column, 0xbaf8 + 1); + expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); + + expect(trace.frames[1].uri, Uri.parse('wasm://wasm/0006d892')); + expect(trace.frames[1].line, 1); + expect(trace.frames[1].column, 0x14378 + 1); + expect(trace.frames[1].member, 'main'); + + expect(trace.frames[2].uri, Uri.parse('wasm://wasm/0006d892')); + expect(trace.frames[2].line, 1); + expect(trace.frames[2].column, 0x14387 + 1); + expect(trace.frames[2].member, 'main tear-off trampoline'); + + expect(trace.frames[3].uri, Uri.parse('wasm://wasm/0006d892')); + expect(trace.frames[3].line, 1); + expect(trace.frames[3].column, 0xa56c + 1); + expect(trace.frames[3].member, '_invokeMain'); + + expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); + expect(trace.frames[4].line, 361); + expect(trace.frames[4].column, 37); + expect(trace.frames[4].member, 'InstantiatedApp.invokeMain'); + + expect(trace.frames[5].uri, Uri.parse('file:///home/user/run_wasm.js')); + expect(trace.frames[5].line, 416); + expect(trace.frames[5].column, 21); + expect(trace.frames[5].member, 'main'); + + expect(trace.frames[6].uri, Uri.parse('file:///home/user/run_wasm.js')); + expect(trace.frames[6].line, 353); + expect(trace.frames[6].column, 38); + expect(trace.frames[6].member, 'async action'); + + expect(trace.frames[7].uri, Uri.parse('file:///home/user/run_wasm.js')); + expect(trace.frames[7].line, 329); + expect(trace.frames[7].column, 9); + expect(trace.frames[7].member, 'async eventLoop'); + }); + + test('parses Firefox stack frace with Wasm frames correctly', () { + var trace = Trace.parse( + 'Error._throwWithCurrentStackTrace@http://localhost:8080/test.wasm:wasm-function[119]:0xbaf8\n' + 'main@http://localhost:8080/test.wasm:wasm-function[792]:0x14378\n' + 'main tear-off trampoline@http://localhost:8080/test.wasm:wasm-function[794]:0x14387\n' + '_invokeMain@http://localhost:8080/test.wasm:wasm-function[70]:0xa56c\n' + 'invoke@http://localhost:8080/test.mjs:48:26'); + + expect(trace.frames.length, 5); + + expect(trace.frames[0].uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(trace.frames[0].line, 1); + expect(trace.frames[0].column, 0xbaf8 + 1); + expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); + + expect(trace.frames[1].uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(trace.frames[1].line, 1); + expect(trace.frames[1].column, 0x14378 + 1); + expect(trace.frames[1].member, 'main'); + + expect(trace.frames[2].uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(trace.frames[2].line, 1); + expect(trace.frames[2].column, 0x14387 + 1); + expect(trace.frames[2].member, 'main tear-off trampoline'); + + expect(trace.frames[3].uri, Uri.parse('http://localhost:8080/test.wasm')); + expect(trace.frames[3].line, 1); + expect(trace.frames[3].column, 0xa56c + 1); + expect(trace.frames[3].member, '_invokeMain'); + + expect(trace.frames[4].uri, Uri.parse('http://localhost:8080/test.mjs')); + expect(trace.frames[4].line, 48); + expect(trace.frames[4].column, 26); + expect(trace.frames[4].member, 'invoke'); + }); + + test('parses Safari stack frace with Wasm frames correctly', () { + var trace = Trace.parse( + '.wasm-function[Error._throwWithCurrentStackTrace]@[wasm code]\n' + '.wasm-function[main]@[wasm code]\n' + '.wasm-function[main tear-off trampoline]@[wasm code]\n' + '.wasm-function[_invokeMain]@[wasm code]\n' + 'invokeMain@/home/user/test.mjs:361:48\n' + '@/home/user/run_wasm.js:416:31'); + + expect(trace.frames.length, 6); + + expect(trace.frames[0].uri, Uri.parse('wasm code')); + expect(trace.frames[0].line, null); + expect(trace.frames[0].column, null); + expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); + + expect(trace.frames[1].uri, Uri.parse('wasm code')); + expect(trace.frames[1].line, null); + expect(trace.frames[1].column, null); + expect(trace.frames[1].member, 'main'); + + expect(trace.frames[2].uri, Uri.parse('wasm code')); + expect(trace.frames[2].line, null); + expect(trace.frames[2].column, null); + expect(trace.frames[2].member, 'main tear-off trampoline'); + + expect(trace.frames[3].uri, Uri.parse('wasm code')); + expect(trace.frames[3].line, null); + expect(trace.frames[3].column, null); + expect(trace.frames[3].member, '_invokeMain'); + + expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); + expect(trace.frames[4].line, 361); + expect(trace.frames[4].column, 48); + expect(trace.frames[4].member, 'invokeMain'); + + expect(trace.frames[5].uri, Uri.parse('file:///home/user/run_wasm.js')); + expect(trace.frames[5].line, 416); + expect(trace.frames[5].column, 31); + expect(trace.frames[5].member, ''); + }); }); test('.toString() nicely formats the stack trace', () { From 2f885b2343fcc8bd1a129382708800e292268ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 19 Sep 2024 10:36:57 +0200 Subject: [PATCH 22/24] Handle JSShell syntax, update tests --- lib/src/frame.dart | 16 ++++++++++++++-- test/trace_test.dart | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/src/frame.dart b/lib/src/frame.dart index ba5cba0..bd0a582 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -95,14 +95,26 @@ final _firefoxSafariJSFrame = RegExp(r'^' // @http://localhost:8080/test.wasm:wasm-function[795]:0x143a8 // @http://localhost:8080/test.wasm:wasm-function[792]:0x14390 // +// JSShell in the command line uses a different format, which this regex also +// parses. +// +// With names: +// +// main@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378 +// +// Without names: +// +// @/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378 +// // Matches named groups: // // - "member": Function name, may be empty: `g`. // - "uri": `http://localhost:8080/test.wasm`. // - "index": `796`. // - "offset": (in hex) `143b4`. -final _firefoxWasmFrame = RegExp(r'^(?.*?)@(?:(?.*?):wasm-function' - r'\[(?\d+)\]:0x(?[0-9a-fA-F]+))$'); +final _firefoxWasmFrame = + RegExp(r'^(?.*?)@(?:(?\S+).*?:wasm-function' + r'\[(?\d+)\]:0x(?[0-9a-fA-F]+))$'); // With names: // diff --git a/test/trace_test.dart b/test/trace_test.dart index a37c5a9..c7b0c8c 100644 --- a/test/trace_test.dart +++ b/test/trace_test.dart @@ -387,6 +387,37 @@ void main() { expect(trace.frames[4].member, 'invoke'); }); + test('parses JSShell stack frace with Wasm frames correctly', () { + var trace = Trace.parse( + 'Error._throwWithCurrentStackTrace@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[119]:0xbaf8\n' + 'main@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[792]:0x14378\n' + 'main tear-off trampoline@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[794]:0x14387\n' + '_invokeMain@/home/user/test.mjs line 29 > WebAssembly.compile:wasm-function[70]:0xa56c\n' + 'invokeMain@/home/user/test.mjs:361:37\n' + 'main@/home/user/run_wasm.js:416:21\n' + 'async*action@/home/user/run_wasm.js:353:44\n' + 'eventLoop@/home/user/run_wasm.js:329:15\n' + 'self.dartMainRunner@/home/user/run_wasm.js:354:14\n' + '@/home/user/run_wasm.js:419:15'); + + expect(trace.frames.length, 10); + + expect(trace.frames[0].uri, Uri.parse('file:///home/user/test.mjs')); + expect(trace.frames[0].line, 1); + expect(trace.frames[0].column, 0xbaf8 + 1); + expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); + + expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); + expect(trace.frames[4].line, 361); + expect(trace.frames[4].column, 37); + expect(trace.frames[4].member, 'invokeMain'); + + expect(trace.frames[9].uri, Uri.parse('file:///home/user/run_wasm.js')); + expect(trace.frames[9].line, 419); + expect(trace.frames[9].column, 15); + expect(trace.frames[9].member, ''); + }); + test('parses Safari stack frace with Wasm frames correctly', () { var trace = Trace.parse( '.wasm-function[Error._throwWithCurrentStackTrace]@[wasm code]\n' From 360f906771ec0404573f264e172ed5dd032ef149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 19 Sep 2024 10:38:18 +0200 Subject: [PATCH 23/24] Simplify tests --- test/trace_test.dart | 52 ++++++++++---------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/test/trace_test.dart b/test/trace_test.dart index c7b0c8c..f51a365 100644 --- a/test/trace_test.dart +++ b/test/trace_test.dart @@ -310,26 +310,15 @@ void main() { expect(trace.frames.length, 8); + for (final frame in trace.frames) { + expect(frame is UnparsedFrame, false); + } + expect(trace.frames[0].uri, Uri.parse('wasm://wasm/0006d892')); expect(trace.frames[0].line, 1); expect(trace.frames[0].column, 0xbaf8 + 1); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); - expect(trace.frames[1].uri, Uri.parse('wasm://wasm/0006d892')); - expect(trace.frames[1].line, 1); - expect(trace.frames[1].column, 0x14378 + 1); - expect(trace.frames[1].member, 'main'); - - expect(trace.frames[2].uri, Uri.parse('wasm://wasm/0006d892')); - expect(trace.frames[2].line, 1); - expect(trace.frames[2].column, 0x14387 + 1); - expect(trace.frames[2].member, 'main tear-off trampoline'); - - expect(trace.frames[3].uri, Uri.parse('wasm://wasm/0006d892')); - expect(trace.frames[3].line, 1); - expect(trace.frames[3].column, 0xa56c + 1); - expect(trace.frames[3].member, '_invokeMain'); - expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[4].line, 361); expect(trace.frames[4].column, 37); @@ -339,16 +328,6 @@ void main() { expect(trace.frames[5].line, 416); expect(trace.frames[5].column, 21); expect(trace.frames[5].member, 'main'); - - expect(trace.frames[6].uri, Uri.parse('file:///home/user/run_wasm.js')); - expect(trace.frames[6].line, 353); - expect(trace.frames[6].column, 38); - expect(trace.frames[6].member, 'async action'); - - expect(trace.frames[7].uri, Uri.parse('file:///home/user/run_wasm.js')); - expect(trace.frames[7].line, 329); - expect(trace.frames[7].column, 9); - expect(trace.frames[7].member, 'async eventLoop'); }); test('parses Firefox stack frace with Wasm frames correctly', () { @@ -361,26 +340,15 @@ void main() { expect(trace.frames.length, 5); + for (final frame in trace.frames) { + expect(frame is UnparsedFrame, false); + } + expect(trace.frames[0].uri, Uri.parse('http://localhost:8080/test.wasm')); expect(trace.frames[0].line, 1); expect(trace.frames[0].column, 0xbaf8 + 1); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); - expect(trace.frames[1].uri, Uri.parse('http://localhost:8080/test.wasm')); - expect(trace.frames[1].line, 1); - expect(trace.frames[1].column, 0x14378 + 1); - expect(trace.frames[1].member, 'main'); - - expect(trace.frames[2].uri, Uri.parse('http://localhost:8080/test.wasm')); - expect(trace.frames[2].line, 1); - expect(trace.frames[2].column, 0x14387 + 1); - expect(trace.frames[2].member, 'main tear-off trampoline'); - - expect(trace.frames[3].uri, Uri.parse('http://localhost:8080/test.wasm')); - expect(trace.frames[3].line, 1); - expect(trace.frames[3].column, 0xa56c + 1); - expect(trace.frames[3].member, '_invokeMain'); - expect(trace.frames[4].uri, Uri.parse('http://localhost:8080/test.mjs')); expect(trace.frames[4].line, 48); expect(trace.frames[4].column, 26); @@ -402,6 +370,10 @@ void main() { expect(trace.frames.length, 10); + for (final frame in trace.frames) { + expect(frame is UnparsedFrame, false); + } + expect(trace.frames[0].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[0].line, 1); expect(trace.frames[0].column, 0xbaf8 + 1); From db25e33f487322ef47cfb4ebf15fdf28babbbb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 19 Sep 2024 10:39:51 +0200 Subject: [PATCH 24/24] Simplify more tests --- test/trace_test.dart | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/test/trace_test.dart b/test/trace_test.dart index f51a365..e09de95 100644 --- a/test/trace_test.dart +++ b/test/trace_test.dart @@ -401,26 +401,15 @@ void main() { expect(trace.frames.length, 6); + for (final frame in trace.frames) { + expect(frame is UnparsedFrame, false); + } + expect(trace.frames[0].uri, Uri.parse('wasm code')); expect(trace.frames[0].line, null); expect(trace.frames[0].column, null); expect(trace.frames[0].member, 'Error._throwWithCurrentStackTrace'); - expect(trace.frames[1].uri, Uri.parse('wasm code')); - expect(trace.frames[1].line, null); - expect(trace.frames[1].column, null); - expect(trace.frames[1].member, 'main'); - - expect(trace.frames[2].uri, Uri.parse('wasm code')); - expect(trace.frames[2].line, null); - expect(trace.frames[2].column, null); - expect(trace.frames[2].member, 'main tear-off trampoline'); - - expect(trace.frames[3].uri, Uri.parse('wasm code')); - expect(trace.frames[3].line, null); - expect(trace.frames[3].column, null); - expect(trace.frames[3].member, '_invokeMain'); - expect(trace.frames[4].uri, Uri.parse('file:///home/user/test.mjs')); expect(trace.frames[4].line, 361); expect(trace.frames[4].column, 48);