Merge pull request #35 from dart-lang/trim-vm-chains
Improve handling of VM stack chains
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19fb82b..b0c972a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+## 1.8.3
+
+* `Chain.forTrace()` now returns a full stack chain for *all* `StackTrace`s
+ within `Chain.capture()`, even those that haven't been processed by
+ `dart:async` yet.
+
+* `Chain.forTrace()` now uses the Dart VM's stack chain information when called
+ synchronously within `Chain.capture()`. This matches the existing behavior
+ outside `Chain.capture()`.
+
+* `Chain.forTrace()` now trims the VM's stack chains for the innermost stack
+ trace within `Chain.capture()` (unless it's called synchronously, as above).
+ This avoids duplicated frames and makes the format of the innermost traces
+ consistent with the other traces in the chain.
+
## 1.8.2
* Update to use strong-mode clean Zone API.
diff --git a/lib/src/chain.dart b/lib/src/chain.dart
index 01bf06f..56c5333 100644
--- a/lib/src/chain.dart
+++ b/lib/src/chain.dart
@@ -153,6 +153,7 @@
factory Chain.forTrace(StackTrace trace) {
if (trace is Chain) return trace;
if (_currentSpec != null) return _currentSpec.chainFor(trace);
+ if (trace is Trace) return new Chain([trace]);
return new LazyChain(() => new Chain.parse(trace.toString()));
}
diff --git a/lib/src/stack_zone_specification.dart b/lib/src/stack_zone_specification.dart
index 6749d56..cd5d068 100644
--- a/lib/src/stack_zone_specification.dart
+++ b/lib/src/stack_zone_specification.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'chain.dart';
+import 'lazy_chain.dart';
import 'lazy_trace.dart';
import 'trace.dart';
import 'utils.dart';
@@ -86,8 +87,23 @@
/// with [trace], this just returns a single-trace chain containing [trace].
Chain chainFor(StackTrace trace) {
if (trace is Chain) return trace;
- var previous = trace == null ? null : _chains[trace];
- return new _Node(trace, previous).toChain();
+ trace ??= StackTrace.current;
+
+ var previous = _chains[trace] ?? _currentNode;
+ if (previous == null) {
+ // If there's no [_currentNode], we're running synchronously beneath
+ // [Chain.capture] and we should fall back to the VM's stack chaining. We
+ // can't use [Chain.from] here because it'll just call [chainFor] again.
+ if (trace is Trace) return new Chain([trace]);
+ return new LazyChain(() => new Chain.parse(trace.toString()));
+ } else {
+ if (trace is! Trace) {
+ var original = trace;
+ trace = new LazyTrace(() => new Trace.parse(_trimVMChain(original)));
+ }
+
+ return new _Node(trace, previous).toChain();
+ }
}
/// Tracks the current stack chain so it can be set to [_currentChain] when
@@ -194,6 +210,29 @@
_currentNode = previousNode;
}
}
+
+ /// 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(() {
+ var text = _trimVMChain(stackTrace);
+ 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)),
+ original: text);
+ });
+ }
+
+ /// Removes the VM's stack chains from the native [trace], since we're
+ /// generating our own and we don't want duplicate frames.
+ String _trimVMChain(StackTrace trace) {
+ var text = trace.toString();
+ var index = text.indexOf(vmChainGap);
+ return index == -1 ? text : text.substring(0, index);
+ }
}
/// A linked list node representing a single entry in a stack chain.
@@ -204,8 +243,7 @@
/// The previous node in the chain.
final _Node previous;
- _Node(StackTrace trace, [this.previous])
- : trace = trace == null ? _currentTrace() : new Trace.from(trace);
+ _Node(StackTrace trace, [this.previous]) : trace = new Trace.from(trace);
/// Converts this to a [Chain].
Chain toChain() {
@@ -218,22 +256,3 @@
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)), original: text);
- });
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index a6e5b92..65d3ef6 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.8.2
+version: 1.8.3
author: "Dart Team <misc@dartlang.org>"
homepage: https://github.com/dart-lang/stack_trace
description: A package for manipulating stack traces and printing them readably.
diff --git a/test/chain/vm_test.dart b/test/chain/vm_test.dart
index daf7f8f..a47cbf7 100644
--- a/test/chain/vm_test.dart
+++ b/test/chain/vm_test.dart
@@ -8,20 +8,31 @@
import 'dart:async';
-import 'package:stack_trace/stack_trace.dart';
import 'package:test/test.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:stack_trace/src/utils.dart';
+
import '../utils.dart';
import 'utils.dart';
void main() {
group('capture() with onError catches exceptions', () {
- test('thrown synchronously', () {
- return captureFuture(() => throw 'error').then((chain) {
- expect(chain.traces, hasLength(1));
- expect(
- chain.traces.single.frames.first, frameMember(startsWith('main')));
+ test('thrown synchronously', () async {
+ StackTrace vmTrace;
+ var chain = await captureFuture(() {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ vmTrace = stackTrace;
+ rethrow;
+ }
});
+
+ // Because there's no chain context for a synchronous error, we fall back
+ // on the VM's stack chain tracking.
+ expect(chain.toString(),
+ equals(new Chain.parse(vmTrace.toString()).toString()));
});
test('thrown in a microtask', () {
@@ -444,11 +455,10 @@
});
});
- test(
- 'called for an unregistered stack trace returns a chain wrapping that '
- 'trace', () {
+ test('called for an unregistered stack trace uses the current chain',
+ () async {
var trace;
- var chain = Chain.capture(() {
+ var chain = await Chain.capture(() async {
try {
throw 'error';
} catch (_, stackTrace) {
@@ -457,27 +467,36 @@
}
});
- expect(chain.traces, hasLength(1));
+ expect(chain.traces, hasLength(2));
+
+ // Assert that we've trimmed the VM's stack chains here to avoid
+ // duplication.
expect(chain.traces.first.toString(),
- equals(new Trace.from(trace).toString()));
+ equals(new Chain.parse(trace.toString()).traces.first.toString()));
+ expect(
+ chain.traces.last.frames, contains(frameMember(startsWith('main'))));
});
});
test(
- 'forTrace() outside of capture() returns a chain wrapping the given '
- 'trace', () {
- var trace;
- var chain = Chain.capture(() {
- try {
- throw 'error';
- } catch (_, stackTrace) {
- trace = stackTrace;
- return new Chain.forTrace(stackTrace);
- }
- });
+ 'forTrace() outside of capture() returns a chain describing the VM stack '
+ 'chain', () {
+ // Disable the test package's chain-tracking.
+ return Chain.disable(() async {
+ var trace;
+ await Chain.capture(() async {
+ try {
+ throw 'error';
+ } catch (_, stackTrace) {
+ trace = stackTrace;
+ }
+ });
- expect(chain.traces, hasLength(1));
- expect(chain.traces.first.toString(),
- equals(new Trace.from(trace).toString()));
+ var chain = new Chain.forTrace(trace);
+ expect(chain.traces,
+ hasLength(vmChainGap.allMatches(trace.toString()).length + 1));
+ expect(
+ chain.traces.first.frames, contains(frameMember(startsWith('main'))));
+ });
});
}