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'