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 : }
|