Unify parsing of Firefox and Safari stack traces. This releases stack_trace 1.1.0. R=rnystrom@google.com Review URL: https://codereview.chromium.org//578993002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@40470 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/CHANGELOG.md b/pkgs/stack_trace/CHANGELOG.md index 56c614a..bbb7eb5 100644 --- a/pkgs/stack_trace/CHANGELOG.md +++ b/pkgs/stack_trace/CHANGELOG.md
@@ -1,3 +1,13 @@ +## 1.1.0 + +* Unify the parsing of Safari and Firefox stack traces. This fixes an error in + Firefox trace parsing. + +* Deprecate `Trace.parseSafari6_0`, `Trace.parseSafari6_1`, + `Frame.parseSafari6_0`, and `Frame.parseSafari6_1`. + +* Add `Frame.parseSafari`. + ## 1.0.3 * Use `Zone.errorCallback` to attach stack chains to all errors without the need
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart index b1db230..4f5b16c 100644 --- a/pkgs/stack_trace/lib/src/frame.dart +++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -30,15 +30,26 @@ final _v8EvalLocation = new RegExp( r'^eval at (?:\S.*?) \((.*)\)(?:, .*?:\d+:\d+)?$'); -// foo$bar$0@http://pub.dartlang.org/stuff.dart.js:560:28 -// http://pub.dartlang.org/stuff.dart.js:560:28 -final _safariFrame = new RegExp(r"^(?:([0-9A-Za-z_$]*)@)?(.*):(\d*):(\d*)$"); - // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560 // .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560 // .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560 -final _firefoxFrame = new RegExp( - r'^([^@(/]*)(?:\(.*\))?((?:/[^/]*)*)(?:\(.*\))?@(.*):(\d+)$'); +// .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560:36 +// http://pub.dartlang.org/stuff.dart.js:560 +final _firefoxSafariFrame = new 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. + r'((?:/[^/]*)*)' // Extra characters indicating a nested closure. + r'(?:\(.*\))?' // Arguments to the closure. + r'@' + r')?' + r'(.*?)' // The frame's URL. + r':' + r'(\d*)' // The line number. Empty in Safari if it's unknown. + r'(?::(\d*))?' // The column number. Not present in older browsers and + // empty in Safari if it's unknown. + r'$'); // foo/bar.dart 10:11 in Foo._bar // http://dartlang.org/foo/bar.dart in Foo._bar @@ -187,45 +198,45 @@ /// Parses a string representation of a Firefox stack frame. factory Frame.parseFirefox(String frame) { - var match = _firefoxFrame.firstMatch(frame); + var match = _firefoxSafariFrame.firstMatch(frame); if (match == null) { throw new FormatException( - "Couldn't parse Firefox stack trace line '$frame'."); + "Couldn't parse Firefox/Safari stack trace line '$frame'."); } // Normally this is a URI, but in a jsshell trace it can be a path. var uri = _uriOrPathToUri(match[3]); - var member = match[1]; - member += new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); - if (member == '') member = '<fn>'; - // Some Firefox members have initial dots. We remove them for consistency - // with other platforms. - member = member.replaceFirst(_initialDot, ''); - return new Frame(uri, int.parse(match[4]), null, member); + var member; + if (match[1] != null) { + member = match[1]; + member += + new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); + if (member == '') member = '<fn>'; + + // Some Firefox members have initial dots. We remove them for consistency + // with other platforms. + member = member.replaceFirst(_initialDot, ''); + } else { + member = '<fn>'; + } + + var line = match[4] == '' ? null : int.parse(match[4]); + var column = match[5] == null || match[5] == '' ? + null : int.parse(match[5]); + return new Frame(uri, line, column, member); } /// Parses a string representation of a Safari 6.0 stack frame. - /// - /// Safari 6.0 frames look just like Firefox frames. Prior to Safari 6.0, - /// stack traces can't be retrieved. + @Deprecated("Use Frame.parseSafari instead.") factory Frame.parseSafari6_0(String frame) => new Frame.parseFirefox(frame); /// Parses a string representation of a Safari 6.1+ stack frame. - factory Frame.parseSafari6_1(String frame) { - var match = _safariFrame.firstMatch(frame); - if (match == null) { - throw new FormatException( - "Couldn't parse Safari stack trace line '$frame'."); - } + @Deprecated("Use Frame.parseSafari instead.") + factory Frame.parseSafari6_1(String frame) => new Frame.parseFirefox(frame); - var uri = Uri.parse(match[2]); - var member = match[1]; - if (member == null) member = '<fn>'; - var line = match[3] == '' ? null : int.parse(match[3]); - var column = match[4] == '' ? null : int.parse(match[4]); - return new Frame(uri, line, column, member); - } + /// Parses a string representation of a Safari stack frame. + factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame); /// Parses this package's string representation of a stack frame. factory Frame.parseFriendly(String frame) {
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart index c540d78..2690ece 100644 --- a/pkgs/stack_trace/lib/src/trace.dart +++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -29,25 +29,28 @@ /// though it is possible for the message to match this as well. final _v8TraceLine = new RegExp(r" ?at "); -/// A RegExp to match Safari's stack traces. +/// A RegExp to match Firefox and Safari's stack traces. /// -/// Prior to version 6, Safari's stack traces were uncapturable. In v6 they were -/// almost identical to Firefox traces, and so are handled by the Firefox code. -/// In v6.1+, they have their own format that's similar to Firefox but distinct -/// enough to warrant handling separately. -/// -/// Most notably, Safari traces occasionally don't include the initial method -/// name followed by "@", and they always have both the line and column number -/// (or just a trailing colon if no column number is available). -final _safariTrace = new RegExp(r"^([0-9A-Za-z_$]*@)?.*:\d*:\d*$", - multiLine: true); - -/// A RegExp to match Firefox's stack traces. +/// Firefox and Safari have very similar stack trace formats, so we use the same +/// logic for parsing them. /// /// Firefox's trace frames start with the name of the function in which the /// error occurred, possibly including its parameters inside `()`. For example, /// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`. -final _firefoxTrace = new RegExp(r"^([.0-9A-Za-z_$/<]|\(.*\))*@"); +/// +/// Safari traces occasionally don't include the initial method name followed by +/// "@", and they always have both the line and column number (or just a +/// trailing colon if no column number is available). They can also contain +/// empty lines or lines consisting only of `[native code]`. +final _firefoxSafariTrace = new RegExp( + r"^" + r"(" // Member description. Not present in some Safari frames. + r"([.0-9A-Za-z_$/<]|\(.*\))*" // Member name and arguments. + r"@" + r")?" + r"[^\s]*" // Frame URL. + r":\d*" // Line or column number. Some older frames only have a line number. + r"$", multiLine: true); /// A RegExp to match this package's stack traces. final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?[ \t]+[^\s]+$", @@ -112,12 +115,9 @@ try { if (trace.isEmpty) return new Trace(<Frame>[]); if (trace.contains(_v8Trace)) return new Trace.parseV8(trace); - // Safari 6.1+ traces could be misinterpreted as Firefox traces, so we - // check for them first. - if (trace.contains(_safariTrace)) return new Trace.parseSafari6_1(trace); - // Safari 6.0 traces are a superset of Firefox traces, so we parse those - // two together. - if (trace.contains(_firefoxTrace)) return new Trace.parseSafari6_0(trace); + if (trace.contains(_firefoxSafariTrace)) { + return new Trace.parseFirefox(trace); + } if (trace.contains(_friendlyTrace)) { return new Trace.parseFriendly(trace); } @@ -157,29 +157,20 @@ /// Parses a string representation of a Firefox stack trace. Trace.parseFirefox(String trace) : this(trace.trim().split("\n") + .where((line) => line.isNotEmpty && line != '[native code]') .map((line) => new Frame.parseFirefox(line))); /// Parses a string representation of a Safari stack trace. - /// - /// This will automatically decide between [parseSafari6_0] and - /// [parseSafari6_1] based on the contents of [trace]. - factory Trace.parseSafari(String trace) { - if (trace.contains(_safariTrace)) return new Trace.parseSafari6_1(trace); - return new Trace.parseSafari6_0(trace); - } + Trace.parseSafari(String trace) + : this.parseFirefox(trace); /// Parses a string representation of a Safari 6.1+ stack trace. + @Deprecated("Use Trace.parseSafari instead.") Trace.parseSafari6_1(String trace) - : this(trace.trim().split("\n") - .where((line) => line.isNotEmpty) - .map((line) => new Frame.parseSafari6_1(line))); + : this.parseSafari(trace); /// Parses a string representation of a Safari 6.0 stack trace. - /// - /// Safari 6.0 stack traces look just like Firefox traces, except that they - /// sometimes (e.g. in isolates) have a "[native code]" frame. We just ignore - /// this frame to make the stack format more consistent between browsers. - /// Prior to Safari 6.0, stack traces can't be retrieved. + @Deprecated("Use Trace.parseSafari instead.") Trace.parseSafari6_0(String trace) : this(trace.trim().split("\n") .where((line) => line != '[native code]')
diff --git a/pkgs/stack_trace/pubspec.yaml b/pkgs/stack_trace/pubspec.yaml index bf28770..cd798bf 100644 --- a/pkgs/stack_trace/pubspec.yaml +++ b/pkgs/stack_trace/pubspec.yaml
@@ -7,7 +7,7 @@ # # When the major version is upgraded, you *must* update that version constraint # in pub to stay in sync with this. -version: 1.0.3 +version: 1.1.0 author: "Dart Team <misc@dartlang.org>" homepage: http://www.dartlang.org description: >
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart index 1205d74..b132368 100644 --- a/pkgs/stack_trace/test/frame_test.dart +++ b/pkgs/stack_trace/test/frame_test.dart
@@ -210,7 +210,7 @@ }); }); - group('.parseFirefox', () { + group('.parseFirefox/.parseSafari', () { test('parses a simple stack frame correctly', () { var frame = new Frame.parseFirefox( ".VW.call\$0@http://pub.dartlang.org/stuff.dart.js:560"); @@ -356,11 +356,9 @@ expect(() => new Frame.parseFirefox('@dart:async/future.dart'), throwsFormatException); }); - }); - group('.parseSafari6_1', () { test('parses a simple stack frame correctly', () { - var frame = new Frame.parseSafari6_1( + var frame = new Frame.parseFirefox( "foo\$bar@http://dartlang.org/foo/bar.dart:10:11"); expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); expect(frame.line, equals(10)); @@ -369,7 +367,7 @@ }); test('parses an anonymous stack frame correctly', () { - var frame = new Frame.parseSafari6_1( + var frame = new Frame.parseFirefox( "http://dartlang.org/foo/bar.dart:10:11"); expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); expect(frame.line, equals(10)); @@ -378,7 +376,7 @@ }); test('parses a stack frame with no line correctly', () { - var frame = new Frame.parseSafari6_1( + var frame = new Frame.parseFirefox( "foo\$bar@http://dartlang.org/foo/bar.dart::11"); expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); expect(frame.line, isNull); @@ -387,7 +385,7 @@ }); test('parses a stack frame with no column correctly', () { - var frame = new Frame.parseSafari6_1( + var frame = new Frame.parseFirefox( "foo\$bar@http://dartlang.org/foo/bar.dart:10:"); expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); expect(frame.line, equals(10)); @@ -396,7 +394,7 @@ }); test('parses a stack frame with no line or column correctly', () { - var frame = new Frame.parseSafari6_1( + var frame = new Frame.parseFirefox( "foo\$bar@http://dartlang.org/foo/bar.dart:10:11"); expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart"))); expect(frame.line, equals(10));
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart index bc2db6b..17f9c22 100644 --- a/pkgs/stack_trace/test/trace_test.dart +++ b/pkgs/stack_trace/test/trace_test.dart
@@ -92,7 +92,7 @@ equals(Uri.parse("http://pub.dartlang.org/thing.js"))); }); - test('parses a Firefox stack trace correctly', () { + test('parses a Firefox/Safari stack trace correctly', () { var trace = new Trace.parse( 'Foo._bar@http://pub.dartlang.org/stuff.js:42\n' 'zip/<@http://pub.dartlang.org/stuff.js:0\n' @@ -130,7 +130,8 @@ equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); }); - test('parses a Safari 6.0 stack trace correctly', () { + test('parses a Firefox/Safari stack trace containing native code correctly', + () { var trace = new Trace.parse( 'Foo._bar@http://pub.dartlang.org/stuff.js:42\n' 'zip/<@http://pub.dartlang.org/stuff.js:0\n' @@ -146,11 +147,29 @@ expect(trace.frames.length, equals(3)); }); - test('parses a Safari 6.1 stack trace correctly', () { + test('parses a Firefox/Safari stack trace without a method name correctly', + () { var trace = new Trace.parse( - 'http://pub.dartlang.org/stuff.js:42:43\n' - 'zip@http://pub.dartlang.org/stuff.js:0:1\n' - 'zip\$zap@http://pub.dartlang.org/thing.js:1:2'); + 'http://pub.dartlang.org/stuff.js:42\n' + 'zip/<@http://pub.dartlang.org/stuff.js:0\n' + 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1'); + + expect(trace.frames[0].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[0].member, equals('<fn>')); + expect(trace.frames[1].uri, + equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[2].uri, + equals(Uri.parse("http://pub.dartlang.org/thing.js"))); + }); + + test('parses a Firefox/Safari stack trace with an empty line correctly', + () { + var trace = new Trace.parse( + 'Foo._bar@http://pub.dartlang.org/stuff.js:42\n' + '\n' + 'zip/<@http://pub.dartlang.org/stuff.js:0\n' + 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); @@ -160,15 +179,17 @@ equals(Uri.parse("http://pub.dartlang.org/thing.js"))); }); - test('parses a Safari 6.1 stack trace with an empty line correctly', () { + test('parses a Firefox/Safari stack trace with a column number correctly', + () { var trace = new Trace.parse( - 'http://pub.dartlang.org/stuff.js:42:43\n' - '\n' - 'zip@http://pub.dartlang.org/stuff.js:0:1\n' - 'zip\$zap@http://pub.dartlang.org/thing.js:1:2'); + 'Foo._bar@http://pub.dartlang.org/stuff.js:42:2\n' + 'zip/<@http://pub.dartlang.org/stuff.js:0\n' + 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1'); expect(trace.frames[0].uri, equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); + expect(trace.frames[0].line, equals(42)); + expect(trace.frames[0].column, equals(2)); expect(trace.frames[1].uri, equals(Uri.parse("http://pub.dartlang.org/stuff.js"))); expect(trace.frames[2].uri,