LCOV - code coverage report
Current view: top level - stack_trace-1.8.2/lib/src - chain.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 5 51 9.8 %
Date: 2017-10-10 20:17:03 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2013, 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 'dart:async';
       6             : import 'dart:math' as math;
       7             : 
       8             : import 'frame.dart';
       9             : import 'lazy_chain.dart';
      10             : import 'stack_zone_specification.dart';
      11             : import 'trace.dart';
      12             : import 'utils.dart';
      13             : 
      14             : /// A function that handles errors in the zone wrapped by [Chain.capture].
      15             : @Deprecated("Will be removed in stack_trace 2.0.0.")
      16             : typedef void ChainHandler(error, Chain chain);
      17             : 
      18             : /// An opaque key used to track the current [StackZoneSpecification].
      19             : final _specKey = new Object();
      20             : 
      21             : /// A chain of stack traces.
      22             : ///
      23             : /// A stack chain is a collection of one or more stack traces that collectively
      24             : /// represent the path from [main] through nested function calls to a particular
      25             : /// code location, usually where an error was thrown. Multiple stack traces are
      26             : /// necessary when using asynchronous functions, since the program's stack is
      27             : /// reset before each asynchronous callback is run.
      28             : ///
      29             : /// Stack chains can be automatically tracked using [Chain.capture]. This sets
      30             : /// up a new [Zone] in which the current stack chain is tracked and can be
      31             : /// accessed using [new Chain.current]. Any errors that would be top-leveled in
      32             : /// the zone can be handled, along with their associated chains, with the
      33             : /// `onError` callback. For example:
      34             : ///
      35             : ///     Chain.capture(() {
      36             : ///       // ...
      37             : ///     }, onError: (error, stackChain) {
      38             : ///       print("Caught error $error\n"
      39             : ///             "$stackChain");
      40             : ///     });
      41             : class Chain implements StackTrace {
      42             :   /// The stack traces that make up this chain.
      43             :   ///
      44             :   /// Like the frames in a stack trace, the traces are ordered from most local
      45             :   /// to least local. The first one is the trace where the actual exception was
      46             :   /// raised, the second one is where that callback was scheduled, and so on.
      47             :   final List<Trace> traces;
      48             : 
      49             :   /// The [StackZoneSpecification] for the current zone.
      50           0 :   static StackZoneSpecification get _currentSpec => Zone.current[_specKey];
      51             : 
      52             :   /// If [when] is `true`, runs [callback] in a [Zone] in which the current
      53             :   /// stack chain is tracked and automatically associated with (most) errors.
      54             :   ///
      55             :   /// If [when] is `false`, this does not track stack chains. Instead, it's
      56             :   /// identical to [runZoned], except that it wraps any errors in [new
      57             :   /// Chain.forTrace]—which will only wrap the trace unless there's a different
      58             :   /// [Chain.capture] active. This makes it easy for the caller to only capture
      59             :   /// stack chains in debug mode or during development.
      60             :   ///
      61             :   /// If [onError] is passed, any error in the zone that would otherwise go
      62             :   /// unhandled is passed to it, along with the [Chain] associated with that
      63             :   /// error. Note that if [callback] produces multiple unhandled errors,
      64             :   /// [onError] may be called more than once. If [onError] isn't passed, the
      65             :   /// parent Zone's `unhandledErrorHandler` will be called with the error and
      66             :   /// its chain.
      67             :   ///
      68             :   /// Note that even if [onError] isn't passed, this zone will still be an error
      69             :   /// zone. This means that any errors that would cross the zone boundary are
      70             :   /// considered unhandled.
      71             :   ///
      72             :   /// If [callback] returns a value, it will be returned by [capture] as well.
      73             :   static T capture<T>(T callback(),
      74             :       {void onError(error, Chain chain), bool when: true}) {
      75             :     if (!when) {
      76             :       var newOnError;
      77             :       if (onError != null) {
      78             :         newOnError = (error, stackTrace) {
      79           0 :           onError(
      80             :               error,
      81             :               stackTrace == null
      82           0 :                   ? new Chain.current()
      83           0 :                   : new Chain.forTrace(stackTrace));
      84             :         };
      85             :       }
      86             : 
      87           0 :       return runZoned(callback, onError: newOnError);
      88             :     }
      89             : 
      90           5 :     var spec = new StackZoneSpecification(onError);
      91           5 :     return runZoned(() {
      92             :       try {
      93           5 :         return callback();
      94             :       } catch (error, stackTrace) {
      95             :         // TODO(nweiz): Don't special-case this when issue 19566 is fixed.
      96           0 :         Zone.current.handleUncaughtError(error, stackTrace);
      97             :         return null;
      98             :       }
      99             :     },
     100           5 :         zoneSpecification: spec.toSpec(),
     101          15 :         zoneValues: {_specKey: spec, StackZoneSpecification.disableKey: false});
     102             :   }
     103             : 
     104             :   /// If [when] is `true` and this is called within a [Chain.capture] zone, runs
     105             :   /// [callback] in a [Zone] in which chain capturing is disabled.
     106             :   ///
     107             :   /// If [callback] returns a value, it will be returned by [disable] as well.
     108             :   static T disable<T>(T callback(), {bool when: true}) {
     109             :     var zoneValues =
     110           0 :         when ? {_specKey: null, StackZoneSpecification.disableKey: true} : null;
     111             : 
     112           0 :     return runZoned(callback, zoneValues: zoneValues);
     113             :   }
     114             : 
     115             :   /// Returns [futureOrStream] unmodified.
     116             :   ///
     117             :   /// Prior to Dart 1.7, this was necessary to ensure that stack traces for
     118             :   /// exceptions reported with [Completer.completeError] and
     119             :   /// [StreamController.addError] were tracked correctly.
     120             :   @Deprecated("Chain.track is not necessary in Dart 1.7+.")
     121             :   static track(futureOrStream) => futureOrStream;
     122             : 
     123             :   /// Returns the current stack chain.
     124             :   ///
     125             :   /// By default, the first frame of the first trace will be the line where
     126             :   /// [Chain.current] is called. If [level] is passed, the first trace will
     127             :   /// start that many frames up instead.
     128             :   ///
     129             :   /// If this is called outside of a [capture] zone, it just returns a
     130             :   /// single-trace chain.
     131             :   factory Chain.current([int level = 0]) {
     132           0 :     if (_currentSpec != null) return _currentSpec.currentChain(level + 1);
     133             : 
     134           0 :     var chain = new Chain.forTrace(StackTrace.current);
     135           0 :     return new LazyChain(() {
     136             :       // JS includes a frame for the call to StackTrace.current, but the VM
     137             :       // doesn't, so we skip an extra frame in a JS context.
     138           0 :       var first = new Trace(
     139           0 :           chain.traces.first.frames.skip(level + (inJS ? 2 : 1)),
     140           0 :           original: chain.traces.first.original.toString());
     141           0 :       return new Chain([first]..addAll(chain.traces.skip(1)));
     142             :     });
     143             :   }
     144             : 
     145             :   /// Returns the stack chain associated with [trace].
     146             :   ///
     147             :   /// The first stack trace in the returned chain will always be [trace]
     148             :   /// (converted to a [Trace] if necessary). If there is no chain associated
     149             :   /// with [trace] or if this is called outside of a [capture] zone, this just
     150             :   /// returns a single-trace chain containing [trace].
     151             :   ///
     152             :   /// If [trace] is already a [Chain], it will be returned as-is.
     153             :   factory Chain.forTrace(StackTrace trace) {
     154           0 :     if (trace is Chain) return trace;
     155           0 :     if (_currentSpec != null) return _currentSpec.chainFor(trace);
     156           0 :     return new LazyChain(() => new Chain.parse(trace.toString()));
     157             :   }
     158             : 
     159             :   /// Parses a string representation of a stack chain.
     160             :   ///
     161             :   /// If [chain] is the output of a call to [Chain.toString], it will be parsed
     162             :   /// as a full stack chain. Otherwise, it will be parsed as in [Trace.parse]
     163             :   /// and returned as a single-trace chain.
     164             :   factory Chain.parse(String chain) {
     165           0 :     if (chain.isEmpty) return new Chain([]);
     166           0 :     if (chain.contains(vmChainGap)) {
     167           0 :       return new Chain(
     168           0 :           chain.split(vmChainGap).map((trace) => new Trace.parseVM(trace)));
     169             :     }
     170           0 :     if (!chain.contains(chainGap)) return new Chain([new Trace.parse(chain)]);
     171             : 
     172           0 :     return new Chain(
     173           0 :         chain.split(chainGap).map((trace) => new Trace.parseFriendly(trace)));
     174             :   }
     175             : 
     176             :   /// Returns a new [Chain] comprised of [traces].
     177           0 :   Chain(Iterable<Trace> traces) : traces = new List<Trace>.unmodifiable(traces);
     178             : 
     179             :   /// Returns a terser version of [this].
     180             :   ///
     181             :   /// This calls [Trace.terse] on every trace in [traces], and discards any
     182             :   /// trace that contain only internal frames.
     183             :   ///
     184             :   /// This won't do anything with a raw JavaScript trace, since there's no way
     185             :   /// to determine which frames come from which Dart libraries. However, the
     186             :   /// [`source_map_stack_trace`][source_map_stack_trace] package can be used to
     187             :   /// convert JavaScript traces into Dart-style traces.
     188             :   ///
     189             :   /// [source_map_stack_trace]: https://pub.dartlang.org/packages/source_map_stack_trace
     190           0 :   Chain get terse => foldFrames((_) => false, terse: true);
     191             : 
     192             :   /// Returns a new [Chain] based on [this] where multiple stack frames matching
     193             :   /// [predicate] are folded together.
     194             :   ///
     195             :   /// This means that whenever there are multiple frames in a row that match
     196             :   /// [predicate], only the last one is kept. In addition, traces that are
     197             :   /// composed entirely of frames matching [predicate] are omitted.
     198             :   ///
     199             :   /// This is useful for limiting the amount of library code that appears in a
     200             :   /// stack trace by only showing user code and code that's called by user code.
     201             :   ///
     202             :   /// If [terse] is true, this will also fold together frames from the core
     203             :   /// library or from this package, and simplify core library frames as in
     204             :   /// [Trace.terse].
     205             :   Chain foldFrames(bool predicate(Frame frame), {bool terse: false}) {
     206             :     var foldedTraces =
     207           0 :         traces.map((trace) => trace.foldFrames(predicate, terse: terse));
     208           0 :     var nonEmptyTraces = foldedTraces.where((trace) {
     209             :       // Ignore traces that contain only folded frames.
     210           0 :       if (trace.frames.length > 1) return true;
     211           0 :       if (trace.frames.isEmpty) return false;
     212             : 
     213             :       // In terse mode, the trace may have removed an outer folded frame,
     214             :       // leaving a single non-folded frame. We can detect a folded frame because
     215             :       // it has no line information.
     216             :       if (!terse) return false;
     217           0 :       return trace.frames.single.line != null;
     218             :     });
     219             : 
     220             :     // If all the traces contain only internal processing, preserve the last
     221             :     // (top-most) one so that the chain isn't empty.
     222           0 :     if (nonEmptyTraces.isEmpty && foldedTraces.isNotEmpty) {
     223           0 :       return new Chain([foldedTraces.last]);
     224             :     }
     225             : 
     226           0 :     return new Chain(nonEmptyTraces);
     227             :   }
     228             : 
     229             :   /// Converts [this] to a [Trace].
     230             :   ///
     231             :   /// The trace version of a chain is just the concatenation of all the traces
     232             :   /// in the chain.
     233           0 :   Trace toTrace() => new Trace(traces.expand((trace) => trace.frames));
     234             : 
     235             :   String toString() {
     236             :     // Figure out the longest path so we know how much to pad.
     237           0 :     var longest = traces.map((trace) {
     238           0 :       return trace.frames
     239           0 :           .map((frame) => frame.location.length)
     240           0 :           .fold(0, math.max);
     241           0 :     }).fold(0, math.max);
     242             : 
     243             :     // Don't call out to [Trace.toString] here because that doesn't ensure that
     244             :     // padding is consistent across all traces.
     245           0 :     return traces.map((trace) {
     246           0 :       return trace.frames.map((frame) {
     247           0 :         return '${frame.location.padRight(longest)}  ${frame.member}\n';
     248           0 :       }).join();
     249           0 :     }).join(chainGap);
     250             :   }
     251             : }

Generated by: LCOV version 1.13