Support the VM's new causal async stack traces. (dart-lang/stack_trace#19)
diff --git a/pkgs/stack_trace/CHANGELOG.md b/pkgs/stack_trace/CHANGELOG.md index 5d25eac..293538f 100644 --- a/pkgs/stack_trace/CHANGELOG.md +++ b/pkgs/stack_trace/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/pkgs/stack_trace/lib/src/chain.dart b/pkgs/stack_trace/lib/src/chain.dart index 7045e19..4543eff 100644 --- a/pkgs/stack_trace/lib/src/chain.dart +++ b/pkgs/stack_trace/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/pkgs/stack_trace/lib/src/lazy_chain.dart b/pkgs/stack_trace/lib/src/lazy_chain.dart new file mode 100644 index 0000000..55b9897 --- /dev/null +++ b/pkgs/stack_trace/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/pkgs/stack_trace/lib/src/stack_zone_specification.dart b/pkgs/stack_trace/lib/src/stack_zone_specification.dart index 7125249..948ef30 100644 --- a/pkgs/stack_trace/lib/src/stack_zone_specification.dart +++ b/pkgs/stack_trace/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/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart index ad55fc7..eb8ecd8 100644 --- a/pkgs/stack_trace/lib/src/trace.dart +++ b/pkgs/stack_trace/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/pkgs/stack_trace/lib/src/utils.dart b/pkgs/stack_trace/lib/src/utils.dart index 5189760..838a093 100644 --- a/pkgs/stack_trace/lib/src/utils.dart +++ b/pkgs/stack_trace/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/pkgs/stack_trace/pubspec.yaml b/pkgs/stack_trace/pubspec.yaml index 6984445..4289a6e 100644 --- a/pkgs/stack_trace/pubspec.yaml +++ b/pkgs/stack_trace/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: >