OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library trace; | |
6 | |
7 import 'dart:collection'; | |
8 import 'dart:math' as math; | 5 import 'dart:math' as math; |
9 | 6 |
10 import 'chain.dart'; | 7 import 'chain.dart'; |
11 import 'frame.dart'; | 8 import 'frame.dart'; |
12 import 'lazy_trace.dart'; | 9 import 'lazy_trace.dart'; |
13 import 'unparsed_frame.dart'; | 10 import 'unparsed_frame.dart'; |
14 import 'utils.dart'; | 11 import 'utils.dart'; |
15 import 'vm_trace.dart'; | 12 import 'vm_trace.dart'; |
16 | 13 |
17 final _terseRegExp = new RegExp(r"(-patch)?([/\\].*)?$"); | 14 final _terseRegExp = new RegExp(r"(-patch)?([/\\].*)?$"); |
(...skipping 21 matching lines...) Expand all Loading... |
39 /// error occurred, possibly including its parameters inside `()`. For example, | 36 /// error occurred, possibly including its parameters inside `()`. For example, |
40 /// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`. | 37 /// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`. |
41 /// | 38 /// |
42 /// Safari traces occasionally don't include the initial method name followed by | 39 /// Safari traces occasionally don't include the initial method name followed by |
43 /// "@", and they always have both the line and column number (or just a | 40 /// "@", and they always have both the line and column number (or just a |
44 /// trailing colon if no column number is available). They can also contain | 41 /// trailing colon if no column number is available). They can also contain |
45 /// empty lines or lines consisting only of `[native code]`. | 42 /// empty lines or lines consisting only of `[native code]`. |
46 final _firefoxSafariTrace = new RegExp( | 43 final _firefoxSafariTrace = new RegExp( |
47 r"^" | 44 r"^" |
48 r"(" // Member description. Not present in some Safari frames. | 45 r"(" // Member description. Not present in some Safari frames. |
49 r"([.0-9A-Za-z_$/<]|\(.*\))*" // Member name and arguments. | 46 r"([.0-9A-Za-z_$/<]|\(.*\))*" // Member name and arguments. |
50 r"@" | 47 r"@" |
51 r")?" | 48 r")?" |
52 r"[^\s]*" // Frame URL. | 49 r"[^\s]*" // Frame URL. |
53 r":\d*" // Line or column number. Some older frames only have a line number. | 50 r":\d*" // Line or column number. Some older frames only have a line number. |
54 r"$", multiLine: true); | 51 r"$", |
| 52 multiLine: true); |
55 | 53 |
56 /// A RegExp to match this package's stack traces. | 54 /// A RegExp to match this package's stack traces. |
57 final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?[ \t]+[^\s]+$", | 55 final _friendlyTrace = |
58 multiLine: true); | 56 new RegExp(r"^[^\s<][^\s]*( \d+(:\d+)?)?[ \t]+[^\s]+$", multiLine: true); |
59 | 57 |
60 /// A stack trace, comprised of a list of stack frames. | 58 /// A stack trace, comprised of a list of stack frames. |
61 class Trace implements StackTrace { | 59 class Trace implements StackTrace { |
62 /// The stack frames that comprise this stack trace. | 60 /// The stack frames that comprise this stack trace. |
63 final List<Frame> frames; | 61 final List<Frame> frames; |
64 | 62 |
| 63 /// The original stack trace from which this trace was parsed. |
| 64 final StackTrace original; |
| 65 |
65 /// Returns a human-readable representation of [stackTrace]. If [terse] is | 66 /// Returns a human-readable representation of [stackTrace]. If [terse] is |
66 /// set, this folds together multiple stack frames from the Dart core | 67 /// set, this folds together multiple stack frames from the Dart core |
67 /// libraries, so that only the core library method directly called from user | 68 /// libraries, so that only the core library method directly called from user |
68 /// code is visible (see [Trace.terse]). | 69 /// code is visible (see [Trace.terse]). |
69 static String format(StackTrace stackTrace, {bool terse: true}) { | 70 static String format(StackTrace stackTrace, {bool terse: true}) { |
70 var trace = new Trace.from(stackTrace); | 71 var trace = new Trace.from(stackTrace); |
71 if (terse) trace = trace.terse; | 72 if (terse) trace = trace.terse; |
72 return trace.toString(); | 73 return trace.toString(); |
73 } | 74 } |
74 | 75 |
75 /// Returns the current stack trace. | 76 /// Returns the current stack trace. |
76 /// | 77 /// |
77 /// By default, the first frame of this trace will be the line where | 78 /// By default, the first frame of this trace will be the line where |
78 /// [Trace.current] is called. If [level] is passed, the trace will start that | 79 /// [Trace.current] is called. If [level] is passed, the trace will start that |
79 /// many frames up instead. | 80 /// many frames up instead. |
80 factory Trace.current([int level=0]) { | 81 factory Trace.current([int level = 0]) { |
81 if (level < 0) { | 82 if (level < 0) { |
82 throw new ArgumentError("Argument [level] must be greater than or equal " | 83 throw new ArgumentError("Argument [level] must be greater than or equal " |
83 "to 0."); | 84 "to 0."); |
84 } | 85 } |
85 | 86 |
86 try { | 87 var trace = new Trace.from(StackTrace.current); |
87 throw ''; | 88 return new LazyTrace(() { |
88 } catch (_, nativeTrace) { | 89 // JS includes a frame for the call to StackTrace.current, but the VM |
89 var trace = new Trace.from(nativeTrace); | 90 // doesn't, so we skip an extra frame in a JS context. |
90 return new LazyTrace(() => new Trace(trace.frames.skip(level + 1))); | 91 return new Trace(trace.frames.skip(level + (inJS ? 2 : 1)), |
91 } | 92 original: trace.original.toString()); |
| 93 }); |
92 } | 94 } |
93 | 95 |
94 /// Returns a new stack trace containing the same data as [trace]. | 96 /// Returns a new stack trace containing the same data as [trace]. |
95 /// | 97 /// |
96 /// If [trace] is a native [StackTrace], its data will be parsed out; if it's | 98 /// If [trace] is a native [StackTrace], its data will be parsed out; if it's |
97 /// a [Trace], it will be returned as-is. | 99 /// a [Trace], it will be returned as-is. |
98 factory Trace.from(StackTrace trace) { | 100 factory Trace.from(StackTrace trace) { |
99 // Normally explicitly validating null arguments is bad Dart style, but here | 101 // Normally explicitly validating null arguments is bad Dart style, but here |
100 // the natural failure will only occur when the LazyTrace is materialized, | 102 // the natural failure will only occur when the LazyTrace is materialized, |
101 // and we want to provide an error that's more local to the actual problem. | 103 // and we want to provide an error that's more local to the actual problem. |
(...skipping 27 matching lines...) Expand all Loading... |
129 // Default to parsing the stack trace as a VM trace. This is also hit on | 131 // Default to parsing the stack trace as a VM trace. This is also hit on |
130 // IE and Safari, where the stack trace is just an empty string (issue | 132 // IE and Safari, where the stack trace is just an empty string (issue |
131 // 11257). | 133 // 11257). |
132 return new Trace.parseVM(trace); | 134 return new Trace.parseVM(trace); |
133 } on FormatException catch (error) { | 135 } on FormatException catch (error) { |
134 throw new FormatException('${error.message}\nStack trace:\n$trace'); | 136 throw new FormatException('${error.message}\nStack trace:\n$trace'); |
135 } | 137 } |
136 } | 138 } |
137 | 139 |
138 /// Parses a string representation of a Dart VM stack trace. | 140 /// Parses a string representation of a Dart VM stack trace. |
139 Trace.parseVM(String trace) | 141 Trace.parseVM(String trace) : this(_parseVM(trace), original: trace); |
140 : this(_parseVM(trace)); | |
141 | 142 |
142 static List<Frame> _parseVM(String trace) { | 143 static List<Frame> _parseVM(String trace) { |
143 var lines = trace.trim().split("\n"); | 144 // Ignore [vmChainGap]. This matches the behavior of |
144 var frames = lines.take(lines.length - 1) | 145 // `Chain.parse().toTrace()`. |
| 146 var lines = trace.trim().replaceAll(vmChainGap, '').split("\n"); |
| 147 var frames = lines |
| 148 .take(lines.length - 1) |
145 .map((line) => new Frame.parseVM(line)) | 149 .map((line) => new Frame.parseVM(line)) |
146 .toList(); | 150 .toList(); |
147 | 151 |
148 // TODO(nweiz): Remove this when issue 23614 is fixed. | 152 // TODO(nweiz): Remove this when issue 23614 is fixed. |
149 if (!lines.last.endsWith(".da")) { | 153 if (!lines.last.endsWith(".da")) { |
150 frames.add(new Frame.parseVM(lines.last)); | 154 frames.add(new Frame.parseVM(lines.last)); |
151 } | 155 } |
152 | 156 |
153 return frames; | 157 return frames; |
154 } | 158 } |
155 | 159 |
156 /// Parses a string representation of a Chrome/V8 stack trace. | 160 /// Parses a string representation of a Chrome/V8 stack trace. |
157 Trace.parseV8(String trace) | 161 Trace.parseV8(String trace) |
158 : this(trace.split("\n").skip(1) | 162 : this( |
159 // It's possible that an Exception's description contains a line that | 163 trace |
160 // looks like a V8 trace line, which will screw this up. | 164 .split("\n") |
161 // Unfortunately, that's impossible to detect. | 165 .skip(1) |
162 .skipWhile((line) => !line.startsWith(_v8TraceLine)) | 166 // It's possible that an Exception's description contains a line
that |
163 .map((line) => new Frame.parseV8(line))); | 167 // looks like a V8 trace line, which will screw this up. |
| 168 // Unfortunately, that's impossible to detect. |
| 169 .skipWhile((line) => !line.startsWith(_v8TraceLine)) |
| 170 .map((line) => new Frame.parseV8(line)), |
| 171 original: trace); |
164 | 172 |
165 /// Parses a string representation of a JavaScriptCore stack trace. | 173 /// Parses a string representation of a JavaScriptCore stack trace. |
166 Trace.parseJSCore(String trace) | 174 Trace.parseJSCore(String trace) |
167 : this(trace.split("\n") | 175 : this( |
168 .where((line) => line != "\tat ") | 176 trace |
169 .map((line) => new Frame.parseV8(line))); | 177 .split("\n") |
| 178 .where((line) => line != "\tat ") |
| 179 .map((line) => new Frame.parseV8(line)), |
| 180 original: trace); |
170 | 181 |
171 /// Parses a string representation of an Internet Explorer stack trace. | 182 /// Parses a string representation of an Internet Explorer stack trace. |
172 /// | 183 /// |
173 /// IE10+ traces look just like V8 traces. Prior to IE10, stack traces can't | 184 /// IE10+ traces look just like V8 traces. Prior to IE10, stack traces can't |
174 /// be retrieved. | 185 /// be retrieved. |
175 Trace.parseIE(String trace) | 186 Trace.parseIE(String trace) : this.parseV8(trace); |
176 : this.parseV8(trace); | |
177 | 187 |
178 /// Parses a string representation of a Firefox stack trace. | 188 /// Parses a string representation of a Firefox stack trace. |
179 Trace.parseFirefox(String trace) | 189 Trace.parseFirefox(String trace) |
180 : this(trace.trim().split("\n") | 190 : this( |
181 .where((line) => line.isNotEmpty && line != '[native code]') | 191 trace |
182 .map((line) => new Frame.parseFirefox(line))); | 192 .trim() |
| 193 .split("\n") |
| 194 .where((line) => line.isNotEmpty && line != '[native code]') |
| 195 .map((line) => new Frame.parseFirefox(line)), |
| 196 original: trace); |
183 | 197 |
184 /// Parses a string representation of a Safari stack trace. | 198 /// Parses a string representation of a Safari stack trace. |
185 Trace.parseSafari(String trace) | 199 Trace.parseSafari(String trace) : this.parseFirefox(trace); |
186 : this.parseFirefox(trace); | |
187 | 200 |
188 /// Parses a string representation of a Safari 6.1+ stack trace. | 201 /// Parses a string representation of a Safari 6.1+ stack trace. |
189 @Deprecated("Use Trace.parseSafari instead.") | 202 @Deprecated("Use Trace.parseSafari instead.") |
190 Trace.parseSafari6_1(String trace) | 203 Trace.parseSafari6_1(String trace) : this.parseSafari(trace); |
191 : this.parseSafari(trace); | |
192 | 204 |
193 /// Parses a string representation of a Safari 6.0 stack trace. | 205 /// Parses a string representation of a Safari 6.0 stack trace. |
194 @Deprecated("Use Trace.parseSafari instead.") | 206 @Deprecated("Use Trace.parseSafari instead.") |
195 Trace.parseSafari6_0(String trace) | 207 Trace.parseSafari6_0(String trace) |
196 : this(trace.trim().split("\n") | 208 : this( |
197 .where((line) => line != '[native code]') | 209 trace |
198 .map((line) => new Frame.parseFirefox(line))); | 210 .trim() |
| 211 .split("\n") |
| 212 .where((line) => line != '[native code]') |
| 213 .map((line) => new Frame.parseFirefox(line)), |
| 214 original: trace); |
199 | 215 |
200 /// Parses this package's string representation of a stack trace. | 216 /// Parses this package's string representation of a stack trace. |
201 /// | 217 /// |
202 /// This also parses string representations of [Chain]s. They parse to the | 218 /// This also parses string representations of [Chain]s. They parse to the |
203 /// same trace that [Chain.toTrace] would return. | 219 /// same trace that [Chain.toTrace] would return. |
204 Trace.parseFriendly(String trace) | 220 Trace.parseFriendly(String trace) |
205 : this(trace.isEmpty | 221 : this( |
206 ? [] | 222 trace.isEmpty |
207 : trace.trim().split("\n") | 223 ? [] |
208 // Filter out asynchronous gaps from [Chain]s. | 224 : trace |
209 .where((line) => !line.startsWith('=====')) | 225 .trim() |
210 .map((line) => new Frame.parseFriendly(line))); | 226 .split("\n") |
| 227 // Filter out asynchronous gaps from [Chain]s. |
| 228 .where((line) => !line.startsWith('=====')) |
| 229 .map((line) => new Frame.parseFriendly(line)), |
| 230 original: trace); |
211 | 231 |
212 /// Returns a new [Trace] comprised of [frames]. | 232 /// Returns a new [Trace] comprised of [frames]. |
213 Trace(Iterable<Frame> frames) | 233 Trace(Iterable<Frame> frames, {String original}) |
214 : frames = new UnmodifiableListView<Frame>(frames.toList()); | 234 : frames = new List<Frame>.unmodifiable(frames), |
| 235 original = new StackTrace.fromString(original); |
215 | 236 |
216 /// Returns a VM-style [StackTrace] object. | 237 /// Returns a VM-style [StackTrace] object. |
217 /// | 238 /// |
218 /// The return value's [toString] method will always return a string | 239 /// The return value's [toString] method will always return a string |
219 /// representation in the Dart VM's stack trace format, regardless of what | 240 /// representation in the Dart VM's stack trace format, regardless of what |
220 /// platform is being used. | 241 /// platform is being used. |
221 StackTrace get vmTrace => new VMTrace(frames); | 242 StackTrace get vmTrace => new VMTrace(frames); |
222 | 243 |
223 /// Returns a terser version of [this]. | 244 /// Returns a terser version of [this]. |
224 /// | 245 /// |
225 /// This is accomplished by folding together multiple stack frames from the | 246 /// This is accomplished by folding together multiple stack frames from the |
226 /// core library or from this package, as in [foldFrames]. Remaining core | 247 /// core library or from this package, as in [foldFrames]. Remaining core |
227 /// library frames have their libraries, "-patch" suffixes, and line numbers | 248 /// library frames have their libraries, "-patch" suffixes, and line numbers |
228 /// removed. If the outermost frame of the stack trace is a core library | 249 /// removed. If the outermost frame of the stack trace is a core library |
229 /// frame, it's removed entirely. | 250 /// frame, it's removed entirely. |
230 /// | 251 /// |
| 252 /// This won't do anything with a raw JavaScript trace, since there's no way |
| 253 /// to determine which frames come from which Dart libraries. However, the |
| 254 /// [`source_map_stack_trace`][source_map_stack_trace] package can be used to |
| 255 /// convert JavaScript traces into Dart-style traces. |
| 256 /// |
| 257 /// [source_map_stack_trace]: https://pub.dartlang.org/packages/source_map_sta
ck_trace |
| 258 /// |
231 /// For custom folding, see [foldFrames]. | 259 /// For custom folding, see [foldFrames]. |
232 Trace get terse => foldFrames((_) => false, terse: true); | 260 Trace get terse => foldFrames((_) => false, terse: true); |
233 | 261 |
234 /// Returns a new [Trace] based on [this] where multiple stack frames matching | 262 /// Returns a new [Trace] based on [this] where multiple stack frames matching |
235 /// [predicate] are folded together. | 263 /// [predicate] are folded together. |
236 /// | 264 /// |
237 /// This means that whenever there are multiple frames in a row that match | 265 /// This means that whenever there are multiple frames in a row that match |
238 /// [predicate], only the last one is kept. This is useful for limiting the | 266 /// [predicate], only the last one is kept. This is useful for limiting the |
239 /// amount of library code that appears in a stack trace by only showing user | 267 /// amount of library code that appears in a stack trace by only showing user |
240 /// code and code that's called by user code. | 268 /// code and code that's called by user code. |
(...skipping 15 matching lines...) Expand all Loading... |
256 // internal frames. They only ever show up in stack chains and are | 284 // internal frames. They only ever show up in stack chains and are |
257 // always surrounded by other traces that are actually useful, so we can | 285 // always surrounded by other traces that are actually useful, so we can |
258 // just get rid of them. | 286 // just get rid of them. |
259 // TODO(nweiz): Get rid of this logic some time after issue 22009 is | 287 // TODO(nweiz): Get rid of this logic some time after issue 22009 is |
260 // fixed. | 288 // fixed. |
261 if (!frame.member.contains('<async>')) return false; | 289 if (!frame.member.contains('<async>')) return false; |
262 return frame.line == null; | 290 return frame.line == null; |
263 }; | 291 }; |
264 } | 292 } |
265 | 293 |
266 var newFrames = []; | 294 var newFrames = <Frame>[]; |
267 for (var frame in frames.reversed) { | 295 for (var frame in frames.reversed) { |
268 if (frame is UnparsedFrame || !predicate(frame)) { | 296 if (frame is UnparsedFrame || !predicate(frame)) { |
269 newFrames.add(frame); | 297 newFrames.add(frame); |
270 } else if (newFrames.isEmpty || !predicate(newFrames.last)) { | 298 } else if (newFrames.isEmpty || !predicate(newFrames.last)) { |
271 newFrames.add(new Frame( | 299 newFrames |
272 frame.uri, frame.line, frame.column, frame.member)); | 300 .add(new Frame(frame.uri, frame.line, frame.column, frame.member)); |
273 } | 301 } |
274 } | 302 } |
275 | 303 |
276 if (terse) { | 304 if (terse) { |
277 newFrames = newFrames.map((frame) { | 305 newFrames = newFrames.map((frame) { |
278 if (frame is UnparsedFrame || !predicate(frame)) return frame; | 306 if (frame is UnparsedFrame || !predicate(frame)) return frame; |
279 var library = frame.library.replaceAll(_terseRegExp, ''); | 307 var library = frame.library.replaceAll(_terseRegExp, ''); |
280 return new Frame(Uri.parse(library), null, null, frame.member); | 308 return new Frame(Uri.parse(library), null, null, frame.member); |
281 }).toList(); | 309 }).toList(); |
282 if (newFrames.length > 1 && newFrames.first.isCore) newFrames.removeAt(0); | 310 |
| 311 if (newFrames.length > 1 && predicate(newFrames.first)) { |
| 312 newFrames.removeAt(0); |
| 313 } |
283 } | 314 } |
284 | 315 |
285 return new Trace(newFrames.reversed); | 316 return new Trace(newFrames.reversed, original: this.original.toString()); |
286 } | 317 } |
287 | 318 |
288 /// Returns a human-readable string representation of [this]. | 319 /// Returns a human-readable string representation of [this]. |
289 String toString() { | 320 String toString() { |
290 // Figure out the longest path so we know how much to pad. | 321 // Figure out the longest path so we know how much to pad. |
291 var longest = frames.map((frame) => frame.location.length) | 322 var longest = |
292 .fold(0, math.max); | 323 frames.map((frame) => frame.location.length).fold(0, math.max); |
293 | 324 |
294 // Print out the stack trace nicely formatted. | 325 // Print out the stack trace nicely formatted. |
295 return frames.map((frame) { | 326 return frames.map((frame) { |
296 if (frame is UnparsedFrame) return "$frame\n"; | 327 if (frame is UnparsedFrame) return "$frame\n"; |
297 return '${padRight(frame.location, longest)} ${frame.member}\n'; | 328 return '${frame.location.padRight(longest)} ${frame.member}\n'; |
298 }).join(); | 329 }).join(); |
299 } | 330 } |
300 } | 331 } |
OLD | NEW |