Fix friendly frame parsing bugs.

Fix bug parsing a friendly frame with spaces in the member name.
Fix bug parsing a friendly frame where the location is a data url.

BUG=
R=nweiz@google.com

Review-Url: https://codereview.chromium.org//2739643004 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 293538f..53cdd37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.7.2
+* Fix bug parsing a friendly frame with spaces in the member name.
+* Fix bug parsing a friendly frame where the location is a data url.
+
 ## 1.7.1
 
 * Make `Trace.parse()`, `Chain.parse()`, treat the VM's new causal asynchronous
diff --git a/lib/src/frame.dart b/lib/src/frame.dart
index 8dde788..8599245 100644
--- a/lib/src/frame.dart
+++ b/lib/src/frame.dart
@@ -52,9 +52,11 @@
     r'$');
 
 // foo/bar.dart 10:11 Foo._bar
+// foo/bar.dart 10:11 (anonymous function).dart.fn
 // http://dartlang.org/foo/bar.dart Foo._bar
+// data:... 10:11 Foo._bar
 final _friendlyFrame = new RegExp(
-    r'^(\S+)(?: (\d+)(?::(\d+))?)?\s+([^\d]\S*)$');
+    r'^(\S+)(?: (\d+)(?::(\d+))?)?\s+([^\d].*)$');
 
 /// A regular expression that matches asynchronous member names generated by the
 /// VM.
@@ -186,7 +188,8 @@
       // as "Anonymous function".
       return parseLocation(match[2],
           match[1].replaceAll("<anonymous>", "<fn>")
-                  .replaceAll("Anonymous function", "<fn>"));
+                  .replaceAll("Anonymous function", "<fn>")
+                  .replaceAll("(anonymous function)", "<fn>"));
     } else {
       // The second form looks like " at LOCATION", and is used for anonymous
       // functions.
@@ -249,8 +252,11 @@
       throw new FormatException(
           "Couldn't parse package:stack_trace stack trace line '$frame'.");
     }
-
-    var uri = Uri.parse(match[1]);
+    // Fake truncated data urls generated by the friendly stack trace format
+    // cause Uri.parse to throw an exception so we have to special case them.
+    var uri = match[1] == 'data:...'
+        ? new Uri.dataFromString('')
+        : Uri.parse(match[1]);
     // If there's no scheme, this is a relative URI. We should interpret it as
     // relative to the current working directory.
     if (uri.scheme == '') {
diff --git a/pubspec.yaml b/pubspec.yaml
index 4289a6e..dd081ea 100644
--- a/pubspec.yaml
+++ b/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.7.1
+version: 1.7.2
 author: "Dart Team <misc@dartlang.org>"
 homepage: https://github.com/dart-lang/stack_trace
 description: >
diff --git a/test/frame_test.dart b/test/frame_test.dart
index 080c8b8..a5f8b23 100644
--- a/test/frame_test.dart
+++ b/test/frame_test.dart
@@ -448,7 +448,7 @@
       expect(frame.member, equals('Foo.<fn>.bar'));
     });
 
-    test('parses a stack frame with no line correctly', () {
+    test('parses a stack frame with no column correctly', () {
       var frame = new Frame.parseFriendly(
           "http://dartlang.org/foo/bar.dart 10  Foo.<fn>.bar");
       expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart")));
@@ -472,6 +472,45 @@
       expectIsUnparsed((text) => new Frame.parseFriendly(text),
           'foo/bar.dart 10:11');
     });
+
+    test('parses a data url stack frame with no line or column correctly', () {
+      var frame = new Frame.parseFriendly(
+          "data:...  main");
+      expect(frame.uri.scheme, equals('data'));
+      expect(frame.line, isNull);
+      expect(frame.column, isNull);
+      expect(frame.member, equals('main'));
+    });
+
+    test('parses a data url stack frame correctly', () {
+      var frame = new Frame.parseFriendly(
+          "data:... 10:11    main");
+      expect(frame.uri.scheme, equals('data'));
+      expect(frame.line, equals(10));
+      expect(frame.column, equals(11));
+      expect(frame.member, equals('main'));
+    });
+
+    test('parses a stack frame with spaces in the member name correctly', () {
+      var frame = new Frame.parseFriendly(
+          "foo/bar.dart 10:11    (anonymous function).dart.fn");
+      expect(frame.uri,
+          equals(path.toUri(path.absolute(path.join('foo', 'bar.dart')))));
+      expect(frame.line, equals(10));
+      expect(frame.column, equals(11));
+      expect(frame.member, equals('(anonymous function).dart.fn'));
+    });
+
+    test('parses a stack frame with spaces in the member name and no line or '
+         'column correctly', () {
+      var frame = new Frame.parseFriendly(
+          "http://dartlang.org/foo/bar.dart  (anonymous function).dart.fn");
+      expect(
+          frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart")));
+      expect(frame.line, isNull);
+      expect(frame.column, isNull);
+      expect(frame.member, equals('(anonymous function).dart.fn'));
+    });
   });
 
   test('only considers dart URIs to be core', () {
diff --git a/test/trace_test.dart b/test/trace_test.dart
index 077f1ba..327eff0 100644
--- a/test/trace_test.dart
+++ b/test/trace_test.dart
@@ -72,6 +72,23 @@
           equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
       expect(trace.frames[2].uri,
           equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+
+      trace = new Trace.parse(
+          'Exception: foo\n'
+          '    bar\n'
+          '    at Foo._bar (http://pub.dartlang.org/stuff.js:42:21)\n'
+          '    at http://pub.dartlang.org/stuff.js:0:2\n'
+          '    at (anonymous function).zip.zap '
+              '(http://pub.dartlang.org/thing.js:1:100)');
+
+      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[1].member, equals("<fn>"));
+      expect(trace.frames[2].uri,
+          equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+      expect(trace.frames[2].member, equals("<fn>.zip.zap"));
     });
 
     // JavaScriptCore traces are just like V8, except that it doesn't have a