Skip to content

Commit e6514d7

Browse files
authored
Support the VM's new causal async stack traces. (flutter#19)
1 parent d32a28a commit e6514d7

File tree

7 files changed

+89
-9
lines changed

7 files changed

+89
-9
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 1.7.1
2+
3+
* Make `Trace.parse()`, `Chain.parse()`, treat the VM's new causal asynchronous
4+
stack traces as chains. Outside of a `Chain.capture()` block, `new
5+
Chain.current()` will return a stack chain constructed from the asynchronous
6+
stack traces.
7+
18
## 1.7.0
29

310
* Add a `Chain.disable()` function that disables stack-chain tracking.

lib/src/chain.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'dart:math' as math;
77

88
import 'frame.dart';
9+
import 'lazy_chain.dart';
910
import 'stack_zone_specification.dart';
1011
import 'trace.dart';
1112
import 'utils.dart';
@@ -134,7 +135,15 @@ class Chain implements StackTrace {
134135
/// single-trace chain.
135136
factory Chain.current([int level=0]) {
136137
if (_currentSpec != null) return _currentSpec.currentChain(level + 1);
137-
return new Chain([new Trace.current(level + 1)]);
138+
139+
var chain = new Chain.forTrace(StackTrace.current);
140+
return new LazyChain(() {
141+
// JS includes a frame for the call to StackTrace.current, but the VM
142+
// doesn't, so we skip an extra frame in a JS context.
143+
var first = new Trace(
144+
chain.traces.first.frames.skip(level + (inJS ? 2 : 1)));
145+
return new Chain([first]..addAll(chain.traces.skip(1)));
146+
});
138147
}
139148

140149
/// Returns the stack chain associated with [trace].
@@ -147,8 +156,8 @@ class Chain implements StackTrace {
147156
/// If [trace] is already a [Chain], it will be returned as-is.
148157
factory Chain.forTrace(StackTrace trace) {
149158
if (trace is Chain) return trace;
150-
if (_currentSpec == null) return new Chain([new Trace.from(trace)]);
151-
return _currentSpec.chainFor(trace);
159+
if (_currentSpec != null) return _currentSpec.chainFor(trace);
160+
return new LazyChain(() => new Chain.parse(trace.toString()));
152161
}
153162

154163
/// Parses a string representation of a stack chain.
@@ -158,6 +167,10 @@ class Chain implements StackTrace {
158167
/// and returned as a single-trace chain.
159168
factory Chain.parse(String chain) {
160169
if (chain.isEmpty) return new Chain([]);
170+
if (chain.contains(vmChainGap)) {
171+
return new Chain(
172+
chain.split(vmChainGap).map((trace) => new Trace.parseVM(trace)));
173+
}
161174
if (!chain.contains(chainGap)) return new Chain([new Trace.parse(chain)]);
162175

163176
return new Chain(

lib/src/lazy_chain.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'chain.dart';
6+
import 'frame.dart';
7+
import 'lazy_trace.dart';
8+
import 'trace.dart';
9+
10+
/// A thunk for lazily constructing a [Chain].
11+
typedef Chain ChainThunk();
12+
13+
/// A wrapper around a [ChainThunk]. This works around issue 9579 by avoiding
14+
/// the conversion of native [StackTrace]s to strings until it's absolutely
15+
/// necessary.
16+
class LazyChain implements Chain {
17+
final ChainThunk _thunk;
18+
Chain _inner;
19+
20+
LazyChain(this._thunk);
21+
22+
Chain get _chain {
23+
if (_inner == null) _inner = _thunk();
24+
return _inner;
25+
}
26+
27+
List<Trace> get traces => _chain.traces;
28+
Chain get terse => _chain.terse;
29+
Chain foldFrames(bool predicate(Frame frame), {bool terse: false}) =>
30+
new LazyChain(() => _chain.foldFrames(predicate, terse: terse));
31+
Trace toTrace() => new LazyTrace(() => _chain.toTrace());
32+
String toString() => _chain.toString();
33+
}

lib/src/stack_zone_specification.dart

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import 'dart:async';
66

7-
import 'trace.dart';
87
import 'chain.dart';
8+
import 'lazy_trace.dart';
9+
import 'trace.dart';
10+
import 'utils.dart';
911

1012
/// A function that handles errors in the zone wrapped by [Chain.capture].
1113
typedef void _ChainHandler(error, Chain chain);
@@ -170,7 +172,7 @@ class StackZoneSpecification {
170172
/// [_createNode] is called. If [level] is passed, the first trace will start
171173
/// that many frames up instead.
172174
_Node _createNode([int level=0]) =>
173-
new _Node(new Trace.current(level + 1), _currentNode);
175+
new _Node(_currentTrace(level + 1), _currentNode);
174176

175177
// TODO(nweiz): use a more robust way of detecting and tracking errors when
176178
// issue 15105 is fixed.
@@ -201,7 +203,7 @@ class _Node {
201203
final _Node previous;
202204

203205
_Node(StackTrace trace, [this.previous])
204-
: trace = trace == null ? new Trace.current() : new Trace.from(trace);
206+
: trace = trace == null ? _currentTrace() : new Trace.from(trace);
205207

206208
/// Converts this to a [Chain].
207209
Chain toChain() {
@@ -214,3 +216,22 @@ class _Node {
214216
return new Chain(nodes);
215217
}
216218
}
219+
220+
/// Like [new Trace.current], but if the current stack trace has VM chaining
221+
/// enabled, this only returns the innermost sub-trace.
222+
Trace _currentTrace([int level]) {
223+
level ??= 0;
224+
var stackTrace = StackTrace.current;
225+
return new LazyTrace(() {
226+
// Ignore the VM's stack chains when we generate our own. Otherwise we'll
227+
// end up with duplicate frames all over the place.
228+
var text = stackTrace.toString();
229+
var index = text.indexOf(vmChainGap);
230+
if (index != -1) text = text.substring(0, index);
231+
232+
var trace = new Trace.parse(text);
233+
// JS includes a frame for the call to StackTrace.current, but the VM
234+
// doesn't, so we skip an extra frame in a JS context.
235+
return new Trace(trace.frames.skip(level + (inJS ? 2 : 1)));
236+
});
237+
}

lib/src/trace.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ final _firefoxSafariTrace = new RegExp(
5151
r"$", multiLine: true);
5252

5353
/// A RegExp to match this package's stack traces.
54-
final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?[ \t]+[^\s]+$",
54+
final _friendlyTrace = new RegExp(r"^[^\s<][^\s]*( \d+(:\d+)?)?[ \t]+[^\s]+$",
5555
multiLine: true);
5656

5757
/// A stack trace, comprised of a list of stack frames.
@@ -137,7 +137,9 @@ class Trace implements StackTrace {
137137
: this(_parseVM(trace));
138138

139139
static List<Frame> _parseVM(String trace) {
140-
var lines = trace.trim().split("\n");
140+
// Ignore [vmChainGap]. This matches the behavior of
141+
// `Chain.parse().toTrace()`.
142+
var lines = trace.trim().replaceAll(vmChainGap, '').split("\n");
141143
var frames = lines.take(lines.length - 1)
142144
.map((line) => new Frame.parseVM(line))
143145
.toList();

lib/src/utils.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
/// the gap between traces.
77
const chainGap = '===== asynchronous gap ===========================\n';
88

9+
/// The line used in the string representation of VM stack chains to represent
10+
/// the gap between traces.
11+
const vmChainGap = '<asynchronous suspension>\n';
12+
913
// TODO(nweiz): When cross-platform imports work, use them to set this.
1014
/// Whether we're running in a JS context.
1115
final bool inJS = 0.0 is int;

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name: stack_trace
77
#
88
# When the major version is upgraded, you *must* update that version constraint
99
# in pub to stay in sync with this.
10-
version: 1.7.0
10+
version: 1.7.1
1111
author: "Dart Team <[email protected]>"
1212
homepage: https://github.com/dart-lang/stack_trace
1313
description: >

0 commit comments

Comments
 (0)