LCOV - code coverage report
Current view: top level - stack_trace-1.10.0/lib/src - chain.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 29 62 46.8 %
Date: 2021-11-28 14:37:50 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 ChainHandler = void Function(dynamic error, Chain chain);
      17             : 
      18             : /// An opaque key used to track the current [StackZoneSpecification].
      19          33 : final _specKey = 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          11 :   static StackZoneSpecification? get _currentSpec =>
      51          33 :       Zone.current[_specKey] as StackZoneSpecification?;
      52             : 
      53             :   /// If [when] is `true`, runs [callback] in a [Zone] in which the current
      54             :   /// stack chain is tracked and automatically associated with (most) errors.
      55             :   ///
      56             :   /// If [when] is `false`, this does not track stack chains. Instead, it's
      57             :   /// identical to [runZoned], except that it wraps any errors in [new
      58             :   /// Chain.forTrace]—which will only wrap the trace unless there's a different
      59             :   /// [Chain.capture] active. This makes it easy for the caller to only capture
      60             :   /// stack chains in debug mode or during development.
      61             :   ///
      62             :   /// If [onError] is passed, any error in the zone that would otherwise go
      63             :   /// unhandled is passed to it, along with the [Chain] associated with that
      64             :   /// error. Note that if [callback] produces multiple unhandled errors,
      65             :   /// [onError] may be called more than once. If [onError] isn't passed, the
      66             :   /// parent Zone's `unhandledErrorHandler` will be called with the error and
      67             :   /// its chain.
      68             :   ///
      69             :   /// The zone this creates will be an error zone if either [onError] is
      70             :   /// not `null` and [when] is false,
      71             :   /// or if both [when] and [errorZone] are `true`.
      72             :   ///  If [errorZone] is `false`, [onError] must be `null`.
      73             :   ///
      74             :   /// If [callback] returns a value, it will be returned by [capture] as well.
      75          11 :   static T capture<T>(T Function() callback,
      76             :       {void Function(Object error, Chain)? onError,
      77             :       bool when = true,
      78             :       bool errorZone = true}) {
      79             :     if (!errorZone && onError != null) {
      80           0 :       throw ArgumentError.value(
      81             :           onError, 'onError', 'must be null if errorZone is false');
      82             :     }
      83             : 
      84             :     if (!when) {
      85          11 :       if (onError == null) return runZoned(callback);
      86           0 :       return runZonedGuarded(callback, (error, stackTrace) {
      87           0 :         onError(error, Chain.forTrace(stackTrace));
      88             :       }) as T;
      89             :     }
      90             : 
      91           0 :     var spec = StackZoneSpecification(onError, errorZone: errorZone);
      92           0 :     return runZoned(() {
      93             :       try {
      94             :         return callback();
      95             :       } on Object catch (error, stackTrace) {
      96             :         // TODO(nweiz): Don't special-case this when issue 19566 is fixed.
      97           0 :         Zone.current.handleUncaughtError(error, stackTrace);
      98             : 
      99             :         // If the expected return type of capture() is not nullable, this will
     100             :         // throw a cast exception. But the only other alternative is to throw
     101             :         // some other exception. Casting null to T at least lets existing uses
     102             :         // where T is a nullable type continue to work.
     103             :         return null as T;
     104             :       }
     105             :     },
     106           0 :         zoneSpecification: spec.toSpec(),
     107           0 :         zoneValues: {_specKey: spec, StackZoneSpecification.disableKey: false});
     108             :   }
     109             : 
     110             :   /// If [when] is `true` and this is called within a [Chain.capture] zone, runs
     111             :   /// [callback] in a [Zone] in which chain capturing is disabled.
     112             :   ///
     113             :   /// If [callback] returns a value, it will be returned by [disable] as well.
     114           0 :   static T disable<T>(T Function() callback, {bool when = true}) {
     115             :     var zoneValues =
     116           0 :         when ? {_specKey: null, StackZoneSpecification.disableKey: true} : null;
     117             : 
     118           0 :     return runZoned(callback, zoneValues: zoneValues);
     119             :   }
     120             : 
     121             :   /// Returns [futureOrStream] unmodified.
     122             :   ///
     123             :   /// Prior to Dart 1.7, this was necessary to ensure that stack traces for
     124             :   /// exceptions reported with [Completer.completeError] and
     125             :   /// [StreamController.addError] were tracked correctly.
     126           0 :   @Deprecated('Chain.track is not necessary in Dart 1.7+.')
     127             :   static dynamic track(futureOrStream) => futureOrStream;
     128             : 
     129             :   /// Returns the current stack chain.
     130             :   ///
     131             :   /// By default, the first frame of the first trace will be the line where
     132             :   /// [Chain.current] is called. If [level] is passed, the first trace will
     133             :   /// start that many frames up instead.
     134             :   ///
     135             :   /// If this is called outside of a [capture] zone, it just returns a
     136             :   /// single-trace chain.
     137           0 :   factory Chain.current([int level = 0]) {
     138           0 :     if (_currentSpec != null) return _currentSpec!.currentChain(level + 1);
     139             : 
     140           0 :     var chain = Chain.forTrace(StackTrace.current);
     141           0 :     return LazyChain(() {
     142             :       // JS includes a frame for the call to StackTrace.current, but the VM
     143             :       // doesn't, so we skip an extra frame in a JS context.
     144           0 :       var first = Trace(chain.traces.first.frames.skip(level + (inJS ? 2 : 1)),
     145           0 :           original: chain.traces.first.original.toString());
     146           0 :       return Chain([first, ...chain.traces.skip(1)]);
     147             :     });
     148             :   }
     149             : 
     150             :   /// Returns the stack chain associated with [trace].
     151             :   ///
     152             :   /// The first stack trace in the returned chain will always be [trace]
     153             :   /// (converted to a [Trace] if necessary). If there is no chain associated
     154             :   /// with [trace] or if this is called outside of a [capture] zone, this just
     155             :   /// returns a single-trace chain containing [trace].
     156             :   ///
     157             :   /// If [trace] is already a [Chain], it will be returned as-is.
     158          11 :   factory Chain.forTrace(StackTrace trace) {
     159          11 :     if (trace is Chain) return trace;
     160          11 :     if (_currentSpec != null) return _currentSpec!.chainFor(trace);
     161          33 :     if (trace is Trace) return Chain([trace]);
     162           0 :     return LazyChain(() => Chain.parse(trace.toString()));
     163             :   }
     164             : 
     165             :   /// Parses a string representation of a stack chain.
     166             :   ///
     167             :   /// If [chain] is the output of a call to [Chain.toString], it will be parsed
     168             :   /// as a full stack chain. Otherwise, it will be parsed as in [Trace.parse]
     169             :   /// and returned as a single-trace chain.
     170           0 :   factory Chain.parse(String chain) {
     171           0 :     if (chain.isEmpty) return Chain([]);
     172           0 :     if (chain.contains(vmChainGap)) {
     173           0 :       return Chain(chain
     174           0 :           .split(vmChainGap)
     175           0 :           .where((line) => line.isNotEmpty)
     176           0 :           .map((trace) => Trace.parseVM(trace)));
     177             :     }
     178           0 :     if (!chain.contains(chainGap)) return Chain([Trace.parse(chain)]);
     179             : 
     180           0 :     return Chain(
     181           0 :         chain.split(chainGap).map((trace) => Trace.parseFriendly(trace)));
     182             :   }
     183             : 
     184             :   /// Returns a new [Chain] comprised of [traces].
     185          22 :   Chain(Iterable<Trace> traces) : traces = List<Trace>.unmodifiable(traces);
     186             : 
     187             :   /// Returns a terser version of [this].
     188             :   ///
     189             :   /// This calls [Trace.terse] on every trace in [traces], and discards any
     190             :   /// trace that contain only internal frames.
     191             :   ///
     192             :   /// This won't do anything with a raw JavaScript trace, since there's no way
     193             :   /// to determine which frames come from which Dart libraries. However, the
     194             :   /// [`source_map_stack_trace`][source_map_stack_trace] package can be used to
     195             :   /// convert JavaScript traces into Dart-style traces.
     196             :   ///
     197             :   /// [source_map_stack_trace]: https://pub.dev/packages/source_map_stack_trace
     198           0 :   Chain get terse => foldFrames((_) => false, terse: true);
     199             : 
     200             :   /// Returns a new [Chain] based on [this] where multiple stack frames matching
     201             :   /// [predicate] are folded together.
     202             :   ///
     203             :   /// This means that whenever there are multiple frames in a row that match
     204             :   /// [predicate], only the last one is kept. In addition, traces that are
     205             :   /// composed entirely of frames matching [predicate] are omitted.
     206             :   ///
     207             :   /// This is useful for limiting the amount of library code that appears in a
     208             :   /// stack trace by only showing user code and code that's called by user code.
     209             :   ///
     210             :   /// If [terse] is true, this will also fold together frames from the core
     211             :   /// library or from this package, and simplify core library frames as in
     212             :   /// [Trace.terse].
     213          11 :   Chain foldFrames(bool Function(Frame) predicate, {bool terse = false}) {
     214             :     var foldedTraces =
     215          44 :         traces.map((trace) => trace.foldFrames(predicate, terse: terse));
     216          22 :     var nonEmptyTraces = foldedTraces.where((trace) {
     217             :       // Ignore traces that contain only folded frames.
     218          33 :       if (trace.frames.length > 1) return true;
     219          22 :       if (trace.frames.isEmpty) return false;
     220             : 
     221             :       // In terse mode, the trace may have removed an outer folded frame,
     222             :       // leaving a single non-folded frame. We can detect a folded frame because
     223             :       // it has no line information.
     224             :       if (!terse) return false;
     225          33 :       return trace.frames.single.line != null;
     226             :     });
     227             : 
     228             :     // If all the traces contain only internal processing, preserve the last
     229             :     // (top-most) one so that the chain isn't empty.
     230          11 :     if (nonEmptyTraces.isEmpty && foldedTraces.isNotEmpty) {
     231           0 :       return Chain([foldedTraces.last]);
     232             :     }
     233             : 
     234          11 :     return Chain(nonEmptyTraces);
     235             :   }
     236             : 
     237             :   /// Converts [this] to a [Trace].
     238             :   ///
     239             :   /// The trace version of a chain is just the concatenation of all the traces
     240             :   /// in the chain.
     241           0 :   Trace toTrace() => Trace(traces.expand((trace) => trace.frames));
     242             : 
     243          11 :   @override
     244             :   String toString() {
     245             :     // Figure out the longest path so we know how much to pad.
     246          33 :     var longest = traces.map((trace) {
     247          11 :       return trace.frames
     248          44 :           .map((frame) => frame.location.length)
     249          11 :           .fold(0, math.max);
     250          11 :     }).fold(0, math.max);
     251             : 
     252             :     // Don't call out to [Trace.toString] here because that doesn't ensure that
     253             :     // padding is consistent across all traces.
     254          33 :     return traces.map((trace) {
     255          33 :       return trace.frames.map((frame) {
     256          44 :         return '${frame.location.padRight(longest)}  ${frame.member}\n';
     257          11 :       }).join();
     258          11 :     }).join(chainGap);
     259             :   }
     260             : }

Generated by: LCOV version 1.14