Properly parse jsshell and d8 stack frames.

It turns out jsshell and d8 can use native paths where Firefox and
Chrome usually use URLs. This CL accommodates that.

R=rnystrom@google.com
BUG=14897

Review URL: https://codereview.chromium.org//63603005

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@30079 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
index b15f651..3bead03 100644
--- a/pkgs/stack_trace/lib/src/frame.dart
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -136,14 +136,17 @@
 
     // V8 stack frames can be in two forms.
     if (match[2] != null) {
-      // The first form looks like "  at FUNCTION (URI:LINE:COL)"
-      var uri = Uri.parse(match[2]);
+      // The first form looks like " at FUNCTION (PATH:LINE:COL)". PATH is
+      // usually an absolute URL, but it can be a path if the stack frame came
+      // from d8.
+      var uri = _uriOrPathToUri(match[2]);
       var member = match[1].replaceAll("<anonymous>", "<fn>");
       return new Frame(uri, int.parse(match[3]), int.parse(match[4]), member);
     } else {
-      // The second form looks like " at URI:LINE:COL", and is used for
-      // anonymous functions.
-      var uri = Uri.parse(match[5]);
+      // The second form looks like " at PATH:LINE:COL", and is used for
+      // anonymous functions. PATH is usually an absolute URL, but it can be a
+      // path if the stack frame came from d8.
+      var uri = _uriOrPathToUri(match[5]);
       return new Frame(uri, int.parse(match[6]), int.parse(match[7]), "<fn>");
     }
   }
@@ -162,7 +165,8 @@
           "Couldn't parse Firefox stack trace line '$frame'.");
     }
 
-    var uri = Uri.parse(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];
     member += new List.filled('/'.allMatches(match[2]).length, ".<fn>").join();
     if (member == '') member = '<fn>';
@@ -215,6 +219,30 @@
     return new Frame(uri, line, column, match[4]);
   }
 
+  /// A regular expression matching an absolute URI.
+  static final _uriRegExp = new RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://');
+
+  /// A regular expression matching a Windows path.
+  static final _windowsRegExp = new RegExp(r'^([a-zA-Z]:[\\/]|\\\\)');
+
+  /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path,
+  /// to a URI (absolute if possible).
+  static Uri _uriOrPathToUri(String uriOrPath) {
+    if (uriOrPath.contains(_uriRegExp)) {
+      return Uri.parse(uriOrPath);
+    } else if (uriOrPath.contains(_windowsRegExp)) {
+      return new Uri.file(uriOrPath, windows: true);
+    } else if (uriOrPath.startsWith('/')) {
+      return new Uri.file(uriOrPath, windows: false);
+    }
+
+    // As far as I've seen, Firefox and V8 both always report absolute paths in
+    // their stack frames. However, if we do get a relative path, we should
+    // handle it gracefully.
+    if (uriOrPath.contains('\\')) return path.windows.toUri(uriOrPath);
+    return Uri.parse(uriOrPath);
+  }
+
   Frame(this.uri, this.line, this.column, this.member);
 
   String toString() => '$location in $member';
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
index 97cf48a..87a8c4d 100644
--- a/pkgs/stack_trace/test/frame_test.dart
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -72,6 +72,52 @@
       expect(frame.member, equals('VW.call\$0'));
     });
 
+    test('parses a stack frame with an absolute POSIX path correctly', () {
+      var frame = new Frame.parseV8("    at VW.call\$0 "
+          "(/path/to/stuff.dart.js:560:28)");
+      expect(frame.uri, equals(Uri.parse("file:///path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with an absolute Windows path correctly', () {
+      var frame = new Frame.parseV8("    at VW.call\$0 "
+          r"(C:\path\to\stuff.dart.js:560:28)");
+      expect(frame.uri, equals(Uri.parse("file:///C:/path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with a Windows UNC path correctly', () {
+      var frame = new Frame.parseV8("    at VW.call\$0 "
+          r"(\\mount\path\to\stuff.dart.js:560:28)");
+      expect(frame.uri,
+          equals(Uri.parse("file://mount/path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with a relative POSIX path correctly', () {
+      var frame = new Frame.parseV8("    at VW.call\$0 "
+          "(path/to/stuff.dart.js:560:28)");
+      expect(frame.uri, equals(Uri.parse("path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with a relative Windows path correctly', () {
+      var frame = new Frame.parseV8("    at VW.call\$0 "
+          r"(path\to\stuff.dart.js:560:28)");
+      expect(frame.uri, equals(Uri.parse("path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
     test('parses an anonymous stack frame correctly', () {
       var frame = new Frame.parseV8(
           "    at http://pub.dartlang.org/stuff.dart.js:560:28");
@@ -135,6 +181,52 @@
       expect(frame.member, equals('VW.call\$0'));
     });
 
+    test('parses a stack frame with an absolute POSIX path correctly', () {
+      var frame = new Frame.parseFirefox(
+          ".VW.call\$0@/path/to/stuff.dart.js:560");
+      expect(frame.uri, equals(Uri.parse("file:///path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, isNull);
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with an absolute Windows path correctly', () {
+      var frame = new Frame.parseFirefox(
+          r".VW.call$0@C:\path\to\stuff.dart.js:560");
+      expect(frame.uri, equals(Uri.parse("file:///C:/path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, isNull);
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with a Windows UNC path correctly', () {
+      var frame = new Frame.parseFirefox(
+          r".VW.call$0@\\mount\path\to\stuff.dart.js:560");
+      expect(frame.uri,
+          equals(Uri.parse("file://mount/path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, isNull);
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with a relative POSIX path correctly', () {
+      var frame = new Frame.parseFirefox(
+          ".VW.call\$0@path/to/stuff.dart.js:560");
+      expect(frame.uri, equals(Uri.parse("path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, isNull);
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
+    test('parses a stack frame with a relative Windows path correctly', () {
+      var frame = new Frame.parseFirefox(
+          r".VW.call$0@path\to\stuff.dart.js:560");
+      expect(frame.uri, equals(Uri.parse("path/to/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, isNull);
+      expect(frame.member, equals('VW.call\$0'));
+    });
+
     test('parses a simple anonymous stack frame correctly', () {
       var frame = new Frame.parseFirefox(
           "@http://pub.dartlang.org/stuff.dart.js:560");