| Index: mojo/public/dart/third_party/stack_trace/lib/src/chain.dart
|
| diff --git a/mojo/public/dart/third_party/stack_trace/lib/src/chain.dart b/mojo/public/dart/third_party/stack_trace/lib/src/chain.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..acd17e9d59ea516498fc50ed89b0ecea07290658
|
| --- /dev/null
|
| +++ b/mojo/public/dart/third_party/stack_trace/lib/src/chain.dart
|
| @@ -0,0 +1,196 @@
|
| +// Copyright (c) 2013, 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.
|
| +
|
| +library stack_trace.chain;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:collection';
|
| +import 'dart:math' as math;
|
| +
|
| +import 'frame.dart';
|
| +import 'stack_zone_specification.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);
|
| +
|
| +/// The line used in the string representation of stack chains to represent
|
| +/// the gap between traces.
|
| +const _gap = '===== asynchronous gap ===========================\n';
|
| +
|
| +/// A chain of stack traces.
|
| +///
|
| +/// A stack chain is a collection of one or more stack traces that collectively
|
| +/// represent the path from [main] through nested function calls to a particular
|
| +/// code location, usually where an error was thrown. Multiple stack traces are
|
| +/// necessary when using asynchronous functions, since the program's stack is
|
| +/// reset before each asynchronous callback is run.
|
| +///
|
| +/// Stack chains can be automatically tracked using [Chain.capture]. This sets
|
| +/// up a new [Zone] in which the current stack chain is tracked and can be
|
| +/// accessed using [new Chain.current]. Any errors that would be top-leveled in
|
| +/// the zone can be handled, along with their associated chains, with the
|
| +/// `onError` callback. For example:
|
| +///
|
| +/// Chain.capture(() {
|
| +/// // ...
|
| +/// }, onError: (error, stackChain) {
|
| +/// print("Caught error $error\n"
|
| +/// "$stackChain");
|
| +/// });
|
| +class Chain implements StackTrace {
|
| +
|
| + /// The stack traces that make up this chain.
|
| + ///
|
| + /// Like the frames in a stack trace, the traces are ordered from most local
|
| + /// to least local. The first one is the trace where the actual exception was
|
| + /// raised, the second one is where that callback was scheduled, and so on.
|
| + final List<Trace> traces;
|
| +
|
| + /// The [StackZoneSpecification] for the current zone.
|
| + static StackZoneSpecification get _currentSpec =>
|
| + Zone.current[#stack_trace.stack_zone.spec];
|
| +
|
| + /// Runs [callback] in a [Zone] in which the current stack chain is tracked
|
| + /// and automatically associated with (most) errors.
|
| + ///
|
| + /// If [onError] is passed, any error in the zone that would otherwise go
|
| + /// unhandled is passed to it, along with the [Chain] associated with that
|
| + /// error. Note that if [callback] produces multiple unhandled errors,
|
| + /// [onError] may be called more than once. If [onError] isn't passed, the
|
| + /// parent Zone's `unhandledErrorHandler` will be called with the error and
|
| + /// its chain.
|
| + ///
|
| + /// Note that even if [onError] isn't passed, this zone will still be an error
|
| + /// zone. This means that any errors that would cross the zone boundary are
|
| + /// considered unhandled.
|
| + ///
|
| + /// If [callback] returns a value, it will be returned by [capture] as well.
|
| + static capture(callback(), {ChainHandler onError}) {
|
| + var spec = new StackZoneSpecification(onError);
|
| + return runZoned(() {
|
| + try {
|
| + return callback();
|
| + } catch (error, stackTrace) {
|
| + // TODO(nweiz): Don't special-case this when issue 19566 is fixed.
|
| + return Zone.current.handleUncaughtError(error, stackTrace);
|
| + }
|
| + }, zoneSpecification: spec.toSpec(), zoneValues: {
|
| + #stack_trace.stack_zone.spec: spec
|
| + });
|
| + }
|
| +
|
| + /// Returns [futureOrStream] unmodified.
|
| + ///
|
| + /// Prior to Dart 1.7, this was necessary to ensure that stack traces for
|
| + /// exceptions reported with [Completer.completeError] and
|
| + /// [StreamController.addError] were tracked correctly.
|
| + @Deprecated("Chain.track is not necessary in Dart 1.7+.")
|
| + static track(futureOrStream) => futureOrStream;
|
| +
|
| + /// Returns the current stack chain.
|
| + ///
|
| + /// By default, the first frame of the first trace will be the line where
|
| + /// [Chain.current] is called. If [level] is passed, the first trace will
|
| + /// start that many frames up instead.
|
| + ///
|
| + /// If this is called outside of a [capture] zone, it just returns a
|
| + /// 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)]);
|
| + }
|
| +
|
| + /// Returns the stack chain associated with [trace].
|
| + ///
|
| + /// The first stack trace in the returned chain will always be [trace]
|
| + /// (converted to a [Trace] if necessary). If there is no chain associated
|
| + /// with [trace] or if this is called outside of a [capture] zone, this just
|
| + /// returns a single-trace chain containing [trace].
|
| + ///
|
| + /// 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);
|
| + }
|
| +
|
| + /// Parses a string representation of a stack chain.
|
| + ///
|
| + /// Specifically, this parses the output of [Chain.toString].
|
| + factory Chain.parse(String chain) {
|
| + if (chain.isEmpty) return new Chain([]);
|
| + return new Chain(
|
| + chain.split(_gap).map((trace) => new Trace.parseFriendly(trace)));
|
| + }
|
| +
|
| + /// Returns a new [Chain] comprised of [traces].
|
| + Chain(Iterable<Trace> traces)
|
| + : traces = new UnmodifiableListView<Trace>(traces.toList());
|
| +
|
| + /// Returns a terser version of [this].
|
| + ///
|
| + /// This calls [Trace.terse] on every trace in [traces], and discards any
|
| + /// trace that contain only internal frames.
|
| + Chain get terse => foldFrames((_) => false, terse: true);
|
| +
|
| + /// Returns a new [Chain] based on [this] where multiple stack frames matching
|
| + /// [predicate] are folded together.
|
| + ///
|
| + /// This means that whenever there are multiple frames in a row that match
|
| + /// [predicate], only the last one is kept. In addition, traces that are
|
| + /// composed entirely of frames matching [predicate] are omitted.
|
| + ///
|
| + /// This is useful for limiting the amount of library code that appears in a
|
| + /// stack trace by only showing user code and code that's called by user code.
|
| + ///
|
| + /// If [terse] is true, this will also fold together frames from the core
|
| + /// library or from this package, and simplify core library frames as in
|
| + /// [Trace.terse].
|
| + Chain foldFrames(bool predicate(Frame frame), {bool terse: false}) {
|
| + var foldedTraces = traces.map(
|
| + (trace) => trace.foldFrames(predicate, terse: terse));
|
| + var nonEmptyTraces = foldedTraces.where((trace) {
|
| + // Ignore traces that contain only folded frames.
|
| + if (trace.frames.length > 1) return true;
|
| +
|
| + // In terse mode, the trace may have removed an outer folded frame,
|
| + // leaving a single non-folded frame. We can detect a folded frame because
|
| + // it has no line information.
|
| + if (!terse) return false;
|
| + return trace.frames.single.line != null;
|
| + });
|
| +
|
| + // If all the traces contain only internal processing, preserve the last
|
| + // (top-most) one so that the chain isn't empty.
|
| + if (nonEmptyTraces.isEmpty && foldedTraces.isNotEmpty) {
|
| + return new Chain([foldedTraces.last]);
|
| + }
|
| +
|
| + return new Chain(nonEmptyTraces);
|
| + }
|
| +
|
| + /// Converts [this] to a [Trace].
|
| + ///
|
| + /// The trace version of a chain is just the concatenation of all the traces
|
| + /// in the chain.
|
| + Trace toTrace() => new Trace(flatten(traces.map((trace) => trace.frames)));
|
| +
|
| + String toString() {
|
| + // Figure out the longest path so we know how much to pad.
|
| + var longest = traces.map((trace) {
|
| + return trace.frames.map((frame) => frame.location.length)
|
| + .fold(0, math.max);
|
| + }).fold(0, math.max);
|
| +
|
| + // Don't call out to [Trace.toString] here because that doesn't ensure that
|
| + // padding is consistent across all traces.
|
| + return traces.map((trace) {
|
| + return trace.frames.map((frame) {
|
| + return '${padRight(frame.location, longest)} ${frame.member}\n';
|
| + }).join();
|
| + }).join(_gap);
|
| + }
|
| +}
|
|
|