Support Safari 6.1's new stack trace format in pkg/stack_trace.
R=rnystrom@google.com
BUG=14534
Review URL: https://codereview.chromium.org//48273005
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@29399 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
index 0b4f577..b15f651 100644
--- a/pkgs/stack_trace/lib/src/frame.dart
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -19,6 +19,10 @@
r'^\s*at (?:([^\s].*?)(?: \[as [^\]]+\])? '
r'\((.+):(\d+):(\d+)\)|(.+):(\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
@@ -169,11 +173,27 @@
return new Frame(uri, int.parse(match[4]), null, member);
}
- /// Parses a string representation of a Safari stack frame.
+ /// Parses a string representation of a Safari 6.0 stack frame.
///
- /// Safari 6+ frames look just like Firefox frames. Prior to Safari 6, stack
- /// traces can't be retrieved.
- factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame);
+ /// Safari 6.0 frames look just like Firefox frames. Prior to Safari 6.0,
+ /// stack traces can't be retrieved.
+ 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'.");
+ }
+
+ 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 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 95d41ad..c3db43e 100644
--- a/pkgs/stack_trace/lib/src/trace.dart
+++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -28,6 +28,19 @@
/// 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.
+///
+/// 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's trace frames start with the name of the function in which the
@@ -36,7 +49,8 @@
final _firefoxTrace = new RegExp(r"^([.0-9A-Za-z_$/<]|\(.*\))*@");
/// A RegExp to match this package's stack traces.
-final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?\s+[^\s]+($|\n)");
+final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?[ \t]+[^\s]+$",
+ multiLine: true);
/// A stack trace, comprised of a list of stack frames.
class Trace implements StackTrace {
@@ -89,9 +103,15 @@
try {
if (trace.isEmpty) return new Trace(<Frame>[]);
if (trace.contains(_v8Trace)) return new Trace.parseV8(trace);
- // Valid Safari traces are a superset of valid Firefox traces.
- if (trace.contains(_firefoxTrace)) return new Trace.parseSafari(trace);
- if (trace.contains(_friendlyTrace)) return new Trace.parseFriendly(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(_friendlyTrace)) {
+ return new Trace.parseFriendly(trace);
+ }
// Default to parsing the stack trace as a VM trace. This is also hit on
// IE and Safari, where the stack trace is just an empty string (issue
@@ -129,11 +149,25 @@
/// Parses a string representation of a Safari stack trace.
///
- /// Safari 6+ stack traces look just like Firefox traces, except that they
+ /// 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);
+ }
+
+ /// Parses a string representation of a Safari 6.1+ stack trace.
+ Trace.parseSafari6_1(String trace)
+ : this(trace.trim().split("\n")
+ .map((line) => new Frame.parseSafari6_1(line)));
+
+ /// 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, stack traces can't be retrieved.
- Trace.parseSafari(String trace)
+ /// Prior to Safari 6.0, stack traces can't be retrieved.
+ Trace.parseSafari6_0(String trace)
: this(trace.trim().split("\n")
.where((line) => line != '[native code]')
.map((line) => new Frame.parseFirefox(line)));
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
index ab7c401..97cf48a 100644
--- a/pkgs/stack_trace/test/frame_test.dart
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -226,6 +226,53 @@
});
});
+ group('.parseSafari6_1', () {
+ test('parses a simple stack frame correctly', () {
+ var frame = new Frame.parseSafari6_1(
+ "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));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('foo\$bar'));
+ });
+
+ test('parses an anonymous stack frame correctly', () {
+ var frame = new Frame.parseSafari6_1(
+ "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));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('<fn>'));
+ });
+
+ test('parses a stack frame with no line correctly', () {
+ var frame = new Frame.parseSafari6_1(
+ "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);
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('foo\$bar'));
+ });
+
+ test('parses a stack frame with no column correctly', () {
+ var frame = new Frame.parseSafari6_1(
+ "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));
+ expect(frame.column, isNull);
+ expect(frame.member, equals('foo\$bar'));
+ });
+
+ test('parses a stack frame with no line or column correctly', () {
+ var frame = new Frame.parseSafari6_1(
+ "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));
+ expect(frame.column, equals(11));
+ expect(frame.member, equals('foo\$bar'));
+ });
+ });
+
group('.parseFriendly', () {
test('parses a simple stack frame correctly', () {
var frame = new Frame.parseFriendly(
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart
index c3ce039..ad916f1 100644
--- a/pkgs/stack_trace/test/trace_test.dart
+++ b/pkgs/stack_trace/test/trace_test.dart
@@ -130,7 +130,7 @@
equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
});
- test('.parseSafari', () {
+ test('parses a Safari 6.0 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'
@@ -146,6 +146,20 @@
expect(trace.frames.length, equals(3));
});
+ test('parses a Safari 6.1 stack trace 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');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ 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 package:stack_trace stack trace correctly', () {
var trace = new Trace.parse(
'http://dartlang.org/foo/bar.dart 10:11 Foo.<fn>.bar\n'