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: >