Properly parse V8 lines involving eval in pkg/stack_trace.

R=sigmund@google.com

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@31644 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
index 74aafcd..8544c78 100644
--- a/pkgs/stack_trace/lib/src/frame.dart
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -14,13 +14,24 @@
     r'^#\d+\s+([^\s].*) \((.+?):(\d+)(?::(\d+))?\)$');
 
 //     at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28)
+//     at VW.call$0 (eval as fn
+//         (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28)
 //     at http://pub.dartlang.org/stuff.dart.js:560:28
 final _v8Frame = new RegExp(
-    r'^\s*at (?:([^\s].*?)(?: \[as [^\]]+\])? '
-    r'\((.+):(\d+):(\d+)\)|(.+):(\d+):(\d+))$');
+    r'^\s*at (?:([^\s].*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$');
 
-/// foo$bar$0@http://pub.dartlang.org/stuff.dart.js:560:28
-/// http://pub.dartlang.org/stuff.dart.js:560:28
+// http://pub.dartlang.org/stuff.dart.js:560:28
+final _v8UrlLocation = new RegExp(r'^(.*):(\d+):(\d+)$');
+
+// eval as function (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28
+// eval as function (http://pub.dartlang.org/stuff.dart.js:560:28)
+// eval as function (eval as otherFunction
+//     (http://pub.dartlang.org/stuff.dart.js:560:28))
+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
@@ -139,20 +150,35 @@
       throw new FormatException("Couldn't parse V8 stack trace line '$frame'.");
     }
 
+    // v8 location strings can be arbitrarily-nested, since it adds a layer of
+    // nesting for each eval performed on that line.
+    parseLocation(location, member) {
+      var evalMatch = _v8EvalLocation.firstMatch(location);
+      if (evalMatch != null) return parseLocation(evalMatch[1], member);
+      
+
+      var urlMatch = _v8UrlLocation.firstMatch(location);
+      if (urlMatch == null) {
+        throw new FormatException(
+            "Couldn't parse V8 stack trace line '$frame'.");
+      }
+
+      return new Frame(
+          _uriOrPathToUri(urlMatch[1]),
+          int.parse(urlMatch[2]),
+          int.parse(urlMatch[3]),
+          member);
+    }
+
     // V8 stack frames can be in two forms.
     if (match[2] != null) {
-      // 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);
+      // The first form looks like " at FUNCTION (LOCATION)".
+      return parseLocation(
+          match[2], match[1].replaceAll("<anonymous>", "<fn>"));
     } else {
-      // 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>");
+      // The second form looks like " at LOCATION", and is used for anonymous
+      // functions.
+      return parseLocation(match[3], "<fn>");
     }
   }
 
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
index 9722d38..482794c 100644
--- a/pkgs/stack_trace/test/frame_test.dart
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -149,6 +149,36 @@
       expect(frame.member, equals('VW.call\$0'));
     });
 
+    test('parses a basic eval stack frame correctly', () {
+      var frame = new Frame.parseV8("    at eval (eval at <anonymous> "
+          "(http://pub.dartlang.org/stuff.dart.js:560:28))");
+      expect(frame.uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('eval'));
+    });
+
+    test('parses an eval stack frame with inner position info correctly', () {
+      var frame = new Frame.parseV8("    at eval (eval at <anonymous> "
+          "(http://pub.dartlang.org/stuff.dart.js:560:28), <anonymous>:3:28)");
+      expect(frame.uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('eval'));
+    });
+
+    test('parses a nested eval stack frame correctly', () {
+      var frame = new Frame.parseV8("    at eval (eval at <anonymous> "
+          "(eval at sub (http://pub.dartlang.org/stuff.dart.js:560:28)))");
+      expect(frame.uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, equals(28));
+      expect(frame.member, equals('eval'));
+    });
+
     test('converts "<anonymous>" to "<fn>"', () {
       String parsedMember(String member) =>
           new Frame.parseV8('    at $member (foo:0:0)').member;