LCOV - code coverage report
Current view: top level - stack_trace-1.10.0/lib/src - stack_zone_specification.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 72 0.0 %
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             : 
       7             : import 'chain.dart';
       8             : import 'lazy_chain.dart';
       9             : import 'lazy_trace.dart';
      10             : import 'trace.dart';
      11             : import 'utils.dart';
      12             : 
      13             : /// A class encapsulating the zone specification for a [Chain.capture] zone.
      14             : ///
      15             : /// Until they're materialized and exposed to the user, stack chains are tracked
      16             : /// as linked lists of [Trace]s using the [_Node] class. These nodes are stored
      17             : /// in three distinct ways:
      18             : ///
      19             : /// * When a callback is registered, a node is created and stored as a captured
      20             : ///   local variable until the callback is run.
      21             : ///
      22             : /// * When a callback is run, its captured node is set as the [_currentNode] so
      23             : ///   it can be available to [Chain.current] and to be linked into additional
      24             : ///   chains when more callbacks are scheduled.
      25             : ///
      26             : /// * When a callback throws an error or a Future or Stream emits an error, the
      27             : ///   current node is associated with that error's stack trace using the
      28             : ///   [_chains] expando.
      29             : ///
      30             : /// Since [ZoneSpecification] can't be extended or even implemented, in order to
      31             : /// get a real [ZoneSpecification] instance it's necessary to call [toSpec].
      32             : class StackZoneSpecification {
      33             :   /// An opaque object used as a zone value to disable chain tracking in a given
      34             :   /// zone.
      35             :   ///
      36             :   /// If `Zone.current[disableKey]` is `true`, no stack chains will be tracked.
      37           0 :   static final disableKey = Object();
      38             : 
      39             :   /// Whether chain-tracking is disabled in the current zone.
      40           0 :   bool get _disabled => Zone.current[disableKey] == true;
      41             : 
      42             :   /// The expando that associates stack chains with [StackTrace]s.
      43             :   ///
      44             :   /// The chains are associated with stack traces rather than errors themselves
      45             :   /// because it's a common practice to throw strings as errors, which can't be
      46             :   /// used with expandos.
      47             :   ///
      48             :   /// The chain associated with a given stack trace doesn't contain a node for
      49             :   /// that stack trace.
      50             :   final _chains = Expando<_Node>('stack chains');
      51             : 
      52             :   /// The error handler for the zone.
      53             :   ///
      54             :   /// If this is null, that indicates that any unhandled errors should be passed
      55             :   /// to the parent zone.
      56             :   final void Function(Object error, Chain)? _onError;
      57             : 
      58             :   /// The most recent node of the current stack chain.
      59             :   _Node? _currentNode;
      60             : 
      61             :   /// Whether this is an error zone.
      62             :   final bool _errorZone;
      63             : 
      64           0 :   StackZoneSpecification(this._onError, {bool errorZone = true})
      65             :       : _errorZone = errorZone;
      66             : 
      67             :   /// Converts [this] to a real [ZoneSpecification].
      68           0 :   ZoneSpecification toSpec() {
      69           0 :     return ZoneSpecification(
      70           0 :         handleUncaughtError: _errorZone ? _handleUncaughtError : null,
      71           0 :         registerCallback: _registerCallback,
      72           0 :         registerUnaryCallback: _registerUnaryCallback,
      73           0 :         registerBinaryCallback: _registerBinaryCallback,
      74           0 :         errorCallback: _errorCallback);
      75             :   }
      76             : 
      77             :   /// Returns the current stack chain.
      78             :   ///
      79             :   /// By default, the first frame of the first trace will be the line where
      80             :   /// [currentChain] is called. If [level] is passed, the first trace will start
      81             :   /// that many frames up instead.
      82           0 :   Chain currentChain([int level = 0]) => _createNode(level + 1).toChain();
      83             : 
      84             :   /// Returns the stack chain associated with [trace], if one exists.
      85             :   ///
      86             :   /// The first stack trace in the returned chain will always be [trace]
      87             :   /// (converted to a [Trace] if necessary). If there is no chain associated
      88             :   /// with [trace], this just returns a single-trace chain containing [trace].
      89           0 :   Chain chainFor(StackTrace? trace) {
      90           0 :     if (trace is Chain) return trace;
      91           0 :     trace ??= StackTrace.current;
      92             : 
      93           0 :     var previous = _chains[trace] ?? _currentNode;
      94             :     if (previous == null) {
      95             :       // If there's no [_currentNode], we're running synchronously beneath
      96             :       // [Chain.capture] and we should fall back to the VM's stack chaining. We
      97             :       // can't use [Chain.from] here because it'll just call [chainFor] again.
      98           0 :       if (trace is Trace) return Chain([trace]);
      99           0 :       return LazyChain(() => Chain.parse(trace!.toString()));
     100             :     } else {
     101           0 :       if (trace is! Trace) {
     102             :         var original = trace;
     103           0 :         trace = LazyTrace(() => Trace.parse(_trimVMChain(original)));
     104             :       }
     105             : 
     106           0 :       return _Node(trace, previous).toChain();
     107             :     }
     108             :   }
     109             : 
     110             :   /// Tracks the current stack chain so it can be set to [_currentChain] when
     111             :   /// [f] is run.
     112           0 :   ZoneCallback<R> _registerCallback<R>(
     113             :       Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
     114           0 :     if (_disabled) return parent.registerCallback(zone, f);
     115           0 :     var node = _createNode(1);
     116           0 :     return parent.registerCallback(zone, () => _run(f, node));
     117             :   }
     118             : 
     119             :   /// Tracks the current stack chain so it can be set to [_currentChain] when
     120             :   /// [f] is run.
     121           0 :   ZoneUnaryCallback<R, T> _registerUnaryCallback<R, T>(
     122             :       Zone self, ZoneDelegate parent, Zone zone, R Function(T) f) {
     123           0 :     if (_disabled) return parent.registerUnaryCallback(zone, f);
     124           0 :     var node = _createNode(1);
     125           0 :     return parent.registerUnaryCallback(zone, (arg) {
     126           0 :       return _run(() => f(arg), node);
     127             :     });
     128             :   }
     129             : 
     130             :   /// Tracks the current stack chain so it can be set to [_currentChain] when
     131             :   /// [f] is run.
     132           0 :   ZoneBinaryCallback<R, T1, T2> _registerBinaryCallback<R, T1, T2>(
     133             :       Zone self, ZoneDelegate parent, Zone zone, R Function(T1, T2) f) {
     134           0 :     if (_disabled) return parent.registerBinaryCallback(zone, f);
     135             : 
     136           0 :     var node = _createNode(1);
     137           0 :     return parent.registerBinaryCallback(zone, (arg1, arg2) {
     138           0 :       return _run(() => f(arg1, arg2), node);
     139             :     });
     140             :   }
     141             : 
     142             :   /// Looks up the chain associated with [stackTrace] and passes it either to
     143             :   /// [_onError] or [parent]'s error handler.
     144           0 :   void _handleUncaughtError(Zone self, ZoneDelegate parent, Zone zone,
     145             :       Object error, StackTrace stackTrace) {
     146           0 :     if (_disabled) {
     147           0 :       parent.handleUncaughtError(zone, error, stackTrace);
     148             :       return;
     149             :     }
     150             : 
     151           0 :     var stackChain = chainFor(stackTrace);
     152           0 :     if (_onError == null) {
     153           0 :       parent.handleUncaughtError(zone, error, stackChain);
     154             :       return;
     155             :     }
     156             : 
     157             :     // TODO(nweiz): Currently this copies a lot of logic from [runZoned]. Just
     158             :     // allow [runBinary] to throw instead once issue 18134 is fixed.
     159             :     try {
     160             :       // TODO(rnystrom): Is the null-assertion correct here? It is nullable in
     161             :       // Zone. Should we check for that here?
     162           0 :       self.parent!.runBinary(_onError!, error, stackChain);
     163             :     } on Object catch (newError, newStackTrace) {
     164             :       if (identical(newError, error)) {
     165           0 :         parent.handleUncaughtError(zone, error, stackChain);
     166             :       } else {
     167           0 :         parent.handleUncaughtError(zone, newError, newStackTrace);
     168             :       }
     169             :     }
     170             :   }
     171             : 
     172             :   /// Attaches the current stack chain to [stackTrace], replacing it if
     173             :   /// necessary.
     174           0 :   AsyncError? _errorCallback(Zone self, ZoneDelegate parent, Zone zone,
     175             :       Object error, StackTrace? stackTrace) {
     176           0 :     if (_disabled) return parent.errorCallback(zone, error, stackTrace);
     177             : 
     178             :     // Go up two levels to get through [_CustomZone.errorCallback].
     179             :     if (stackTrace == null) {
     180           0 :       stackTrace = _createNode(2).toChain();
     181             :     } else {
     182           0 :       if (_chains[stackTrace] == null) _chains[stackTrace] = _createNode(2);
     183             :     }
     184             : 
     185           0 :     var asyncError = parent.errorCallback(zone, error, stackTrace);
     186           0 :     return asyncError ?? AsyncError(error, stackTrace);
     187             :   }
     188             : 
     189             :   /// Creates a [_Node] with the current stack trace and linked to
     190             :   /// [_currentNode].
     191             :   ///
     192             :   /// By default, the first frame of the first trace will be the line where
     193             :   /// [_createNode] is called. If [level] is passed, the first trace will start
     194             :   /// that many frames up instead.
     195           0 :   _Node _createNode([int level = 0]) =>
     196           0 :       _Node(_currentTrace(level + 1), _currentNode);
     197             : 
     198             :   // TODO(nweiz): use a more robust way of detecting and tracking errors when
     199             :   // issue 15105 is fixed.
     200             :   /// Runs [f] with [_currentNode] set to [node].
     201             :   ///
     202             :   /// If [f] throws an error, this associates [node] with that error's stack
     203             :   /// trace.
     204           0 :   T _run<T>(T Function() f, _Node node) {
     205           0 :     var previousNode = _currentNode;
     206           0 :     _currentNode = node;
     207             :     try {
     208             :       return f();
     209             :     } catch (e, stackTrace) {
     210             :       // We can see the same stack trace multiple times if it's rethrown through
     211             :       // guarded callbacks.  The innermost chain will have the most
     212             :       // information so it should take precedence.
     213           0 :       _chains[stackTrace] ??= node;
     214             :       rethrow;
     215             :     } finally {
     216           0 :       _currentNode = previousNode;
     217             :     }
     218             :   }
     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           0 :   Trace _currentTrace([int? level]) {
     223           0 :     var stackTrace = StackTrace.current;
     224           0 :     return LazyTrace(() {
     225           0 :       var text = _trimVMChain(stackTrace);
     226           0 :       var trace = Trace.parse(text);
     227             :       // JS includes a frame for the call to StackTrace.current, but the VM
     228             :       // doesn't, so we skip an extra frame in a JS context.
     229           0 :       return Trace(trace.frames.skip((level ?? 0) + (inJS ? 2 : 1)),
     230             :           original: text);
     231             :     });
     232             :   }
     233             : 
     234             :   /// Removes the VM's stack chains from the native [trace], since we're
     235             :   /// generating our own and we don't want duplicate frames.
     236           0 :   String _trimVMChain(StackTrace trace) {
     237           0 :     var text = trace.toString();
     238           0 :     var index = text.indexOf(vmChainGap);
     239           0 :     return index == -1 ? text : text.substring(0, index);
     240             :   }
     241             : }
     242             : 
     243             : /// A linked list node representing a single entry in a stack chain.
     244             : class _Node {
     245             :   /// The stack trace for this link of the chain.
     246             :   final Trace trace;
     247             : 
     248             :   /// The previous node in the chain.
     249             :   final _Node? previous;
     250             : 
     251           0 :   _Node(StackTrace trace, [this.previous]) : trace = Trace.from(trace);
     252             : 
     253             :   /// Converts this to a [Chain].
     254           0 :   Chain toChain() {
     255           0 :     var nodes = <Trace>[];
     256             :     _Node? node = this;
     257             :     while (node != null) {
     258           0 :       nodes.add(node.trace);
     259           0 :       node = node.previous;
     260             :     }
     261           0 :     return Chain(nodes);
     262             :   }
     263             : }

Generated by: LCOV version 1.14