Support the VM's new causal async stack traces. (#19)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d25eac..293538f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.7.1
+
+* Make `Trace.parse()`, `Chain.parse()`, treat the VM's new causal asynchronous
+  stack traces as chains. Outside of a `Chain.capture()` block, `new
+  Chain.current()` will return a stack chain constructed from the asynchronous
+  stack traces.
+
 ## 1.7.0
 
 * Add a `Chain.disable()` function that disables stack-chain tracking.
diff --git a/lib/src/chain.dart b/lib/src/chain.dart
index 7045e19..4543eff 100644
--- a/lib/src/chain.dart
+++ b/lib/src/chain.dart
@@ -6,6 +6,7 @@
 import 'dart:math' as math;
 
 import 'frame.dart';
+import 'lazy_chain.dart';
 import 'stack_zone_specification.dart';
 import 'trace.dart';
 import 'utils.dart';
@@ -134,7 +135,15 @@
   /// single-trace chain.
   factory Chain.current([int level=0]) {
     if (_currentSpec != null) return _currentSpec.currentChain(level + 1);
-    return new Chain([new Trace.current(level + 1)]);
+
+    var chain = new Chain.forTrace(StackTrace.current);
+    return new LazyChain(() {
+      // JS includes a frame for the call to StackTrace.current, but the VM
+      // doesn't, so we skip an extra frame in a JS context.
+      var first = new Trace(
+          chain.traces.first.frames.skip(level + (inJS ? 2 : 1)));
+      return new Chain([first]..addAll(chain.traces.skip(1)));
+    });
   }
 
   /// Returns the stack chain associated with [trace].
@@ -147,8 +156,8 @@
   /// If [trace] is already a [Chain], it will be returned as-is.
   factory Chain.forTrace(StackTrace trace) {
     if (trace is Chain) return trace;
-    if (_currentSpec == null) return new Chain([new Trace.from(trace)]);
-    return _currentSpec.chainFor(trace);
+    if (_currentSpec != null) return _currentSpec.chainFor(trace);
+    return new LazyChain(() => new Chain.parse(trace.toString()));
   }
 
   /// Parses a string representation of a stack chain.
@@ -158,6 +167,10 @@
   /// and returned as a single-trace chain.
   factory Chain.parse(String chain) {
     if (chain.isEmpty) return new Chain([]);
+    if (chain.contains(vmChainGap)) {
+      return new Chain(
+          chain.split(vmChainGap).map((trace) => new Trace.parseVM(trace)));
+    }
     if (!chain.contains(chainGap)) return new Chain([new Trace.parse(chain)]);
 
     return new Chain(
diff --git a/lib/src/lazy_chain.dart b/lib/src/lazy_chain.dart
new file mode 100644
index 0000000..55b9897
--- /dev/null
+++ b/lib/src/lazy_chain.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'chain.dart';
+import 'frame.dart';
+import 'lazy_trace.dart';
+import 'trace.dart';
+
+/// A thunk for lazily constructing a [Chain].
+typedef Chain ChainThunk();
+
+/// A wrapper around a [ChainThunk]. This works around issue 9579 by avoiding
+/// the conversion of native [StackTrace]s to strings until it's absolutely
+/// necessary.
+class LazyChain implements Chain {
+  final ChainThunk _thunk;
+  Chain _inner;
+
+  LazyChain(this._thunk);
+
+  Chain get _chain {
+    if (_inner == null) _inner = _thunk();
+    return _inner;
+  }
+
+  List<Trace> get traces => _chain.traces;
+  Chain get terse => _chain.terse;
+  Chain foldFrames(bool predicate(Frame frame), {bool terse: false}) =>
+      new LazyChain(() => _chain.foldFrames(predicate, terse: terse));
+  Trace toTrace() => new LazyTrace(() => _chain.toTrace());
+  String toString() => _chain.toString();
+}
diff --git a/lib/src/stack_zone_specification.dart b/lib/src/stack_zone_specification.dart
index 7125249..948ef30 100644
--- a/lib/src/stack_zone_specification.dart
+++ b/lib/src/stack_zone_specification.dart
@@ -4,8 +4,10 @@
 
 import 'dart:async';
 
-import 'trace.dart';
 import 'chain.dart';
+import 'lazy_trace.dart';
+import 'trace.dart';
+import 'utils.dart';
 
 /// A function that handles errors in the zone wrapped by [Chain.capture].
 typedef void _ChainHandler(error, Chain chain);
@@ -170,7 +172,7 @@
   /// [_createNode] is called. If [level] is passed, the first trace will start
   /// that many frames up instead.
   _Node _createNode([int level=0]) =>
-    new _Node(new Trace.current(level + 1), _currentNode);
+    new _Node(_currentTrace(level + 1), _currentNode);
 
   // TODO(nweiz): use a more robust way of detecting and tracking errors when
   // issue 15105 is fixed.
@@ -201,7 +203,7 @@
   final _Node previous;
 
   _Node(StackTrace trace, [this.previous])
-      : trace = trace == null ? new Trace.current() : new Trace.from(trace);
+      : trace = trace == null ? _currentTrace() : new Trace.from(trace);
 
   /// Converts this to a [Chain].
   Chain toChain() {
@@ -214,3 +216,22 @@
     return new Chain(nodes);
   }
 }
+
+/// Like [new Trace.current], but if the current stack trace has VM chaining
+/// enabled, this only returns the innermost sub-trace.
+Trace _currentTrace([int level]) {
+  level ??= 0;
+  var stackTrace = StackTrace.current;
+  return new LazyTrace(() {
+    // Ignore the VM's stack chains when we generate our own. Otherwise we'll
+    // end up with duplicate frames all over the place.
+    var text = stackTrace.toString();
+    var index = text.indexOf(vmChainGap);
+    if (index != -1) text = text.substring(0, index);
+
+    var trace = new Trace.parse(text);
+    // JS includes a frame for the call to StackTrace.current, but the VM
+    // doesn't, so we skip an extra frame in a JS context.
+    return new Trace(trace.frames.skip(level + (inJS ? 2 : 1)));
+  });
+}
diff --git a/lib/src/trace.dart b/lib/src/trace.dart
index ad55fc7..eb8ecd8 100644
--- a/lib/src/trace.dart
+++ b/lib/src/trace.dart
@@ -51,7 +51,7 @@
     r"$", multiLine: true);
 
 /// A RegExp to match this package's stack traces.
-final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?[ \t]+[^\s]+$",
+final _friendlyTrace = new RegExp(r"^[^\s<][^\s]*( \d+(:\d+)?)?[ \t]+[^\s]+$",
     multiLine: true);
 
 /// A stack trace, comprised of a list of stack frames.
@@ -137,7 +137,9 @@
       : this(_parseVM(trace));
 
   static List<Frame> _parseVM(String trace) {
-    var lines = trace.trim().split("\n");
+    // Ignore [vmChainGap]. This matches the behavior of
+    // `Chain.parse().toTrace()`.
+    var lines = trace.trim().replaceAll(vmChainGap, '').split("\n");
     var frames = lines.take(lines.length - 1)
         .map((line) => new Frame.parseVM(line))
         .toList();
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 5189760..838a093 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -6,6 +6,10 @@
 /// the gap between traces.
 const chainGap = '===== asynchronous gap ===========================\n';
 
+/// The line used in the string representation of VM stack chains to represent
+/// the gap between traces.
+const vmChainGap = '<asynchronous suspension>\n';
+
 // TODO(nweiz): When cross-platform imports work, use them to set this.
 /// Whether we're running in a JS context.
 final bool inJS = 0.0 is int;
diff --git a/pubspec.yaml b/pubspec.yaml
index 6984445..4289a6e 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.0
+version: 1.7.1
 author: "Dart Team <misc@dartlang.org>"
 homepage: https://github.com/dart-lang/stack_trace
 description: >