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,