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