OLD | NEW |
| (Empty) |
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 library trace; | |
6 | |
7 import 'dart:collection'; | |
8 import 'dart:math' as math; | |
9 | |
10 import 'chain.dart'; | |
11 import 'frame.dart'; | |
12 import 'lazy_trace.dart'; | |
13 import 'unparsed_frame.dart'; | |
14 import 'utils.dart'; | |
15 import 'vm_trace.dart'; | |
16 | |
17 final _terseRegExp = new RegExp(r"(-patch)?([/\\].*)?$"); | |
18 | |
19 /// A RegExp to match V8's stack traces. | |
20 /// | |
21 /// V8's traces start with a line that's either just "Error" or else is a | |
22 /// description of the exception that occurred. That description can be multiple | |
23 /// lines, so we just look for any line other than the first that begins with | |
24 /// three or four spaces and "at". | |
25 final _v8Trace = new RegExp(r"\n ?at "); | |
26 | |
27 /// A RegExp to match indidual lines of V8's stack traces. | |
28 /// | |
29 /// This is intended to filter out the leading exception details of the trace | |
30 /// though it is possible for the message to match this as well. | |
31 final _v8TraceLine = new RegExp(r" ?at "); | |
32 | |
33 /// A RegExp to match Firefox and Safari's stack traces. | |
34 /// | |
35 /// Firefox and Safari have very similar stack trace formats, so we use the same | |
36 /// logic for parsing them. | |
37 /// | |
38 /// Firefox's trace frames start with the name of the function in which the | |
39 /// error occurred, possibly including its parameters inside `()`. For example, | |
40 /// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`. | |
41 /// | |
42 /// 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 | |
44 /// trailing colon if no column number is available). They can also contain | |
45 /// empty lines or lines consisting only of `[native code]`. | |
46 final _firefoxSafariTrace = new RegExp( | |
47 r"^" | |
48 r"(" // Member description. Not present in some Safari frames. | |
49 r"([.0-9A-Za-z_$/<]|\(.*\))*" // Member name and arguments. | |
50 r"@" | |
51 r")?" | |
52 r"[^\s]*" // Frame URL. | |
53 r":\d*" // Line or column number. Some older frames only have a line number. | |
54 r"$", multiLine: true); | |
55 | |
56 /// A RegExp to match this package's stack traces. | |
57 final _friendlyTrace = new RegExp(r"^[^\s]+( \d+(:\d+)?)?[ \t]+[^\s]+$", | |
58 multiLine: true); | |
59 | |
60 /// A stack trace, comprised of a list of stack frames. | |
61 class Trace implements StackTrace { | |
62 /// The stack frames that comprise this stack trace. | |
63 final List<Frame> frames; | |
64 | |
65 /// Returns a human-readable representation of [stackTrace]. If [terse] is | |
66 /// 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 /// code is visible (see [Trace.terse]). | |
69 static String format(StackTrace stackTrace, {bool terse: true}) { | |
70 var trace = new Trace.from(stackTrace); | |
71 if (terse) trace = trace.terse; | |
72 return trace.toString(); | |
73 } | |
74 | |
75 /// Returns the current stack trace. | |
76 /// | |
77 /// 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 /// many frames up instead. | |
80 factory Trace.current([int level=0]) { | |
81 if (level < 0) { | |
82 throw new ArgumentError("Argument [level] must be greater than or equal " | |
83 "to 0."); | |
84 } | |
85 | |
86 try { | |
87 throw ''; | |
88 } catch (_, nativeTrace) { | |
89 var trace = new Trace.from(nativeTrace); | |
90 return new LazyTrace(() => new Trace(trace.frames.skip(level + 1))); | |
91 } | |
92 } | |
93 | |
94 /// Returns a new stack trace containing the same data as [trace]. | |
95 /// | |
96 /// If [trace] is a native [StackTrace], its data will be parsed out; if it's | |
97 /// a [Trace], it will be returned as-is. | |
98 factory Trace.from(StackTrace trace) { | |
99 // Normally explicitly validating null arguments is bad Dart style, but here | |
100 // 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. | |
102 if (trace == null) { | |
103 throw new ArgumentError("Cannot create a Trace from null."); | |
104 } | |
105 | |
106 if (trace is Trace) return trace; | |
107 if (trace is Chain) return trace.toTrace(); | |
108 return new LazyTrace(() => new Trace.parse(trace.toString())); | |
109 } | |
110 | |
111 /// Parses a string representation of a stack trace. | |
112 /// | |
113 /// [trace] should be formatted in the same way as a Dart VM or browser stack | |
114 /// trace. | |
115 factory Trace.parse(String trace) { | |
116 try { | |
117 if (trace.isEmpty) return new Trace(<Frame>[]); | |
118 if (trace.contains(_v8Trace)) return new Trace.parseV8(trace); | |
119 if (trace.contains("\tat ")) return new Trace.parseJSCore(trace); | |
120 if (trace.contains(_firefoxSafariTrace)) { | |
121 return new Trace.parseFirefox(trace); | |
122 } | |
123 if (trace.contains(_friendlyTrace)) { | |
124 return new Trace.parseFriendly(trace); | |
125 } | |
126 | |
127 // Default to parsing the stack trace as a VM trace. This is also hit on | |
128 // IE and Safari, where the stack trace is just an empty string (issue | |
129 // 11257). | |
130 return new Trace.parseVM(trace); | |
131 } on FormatException catch (error) { | |
132 throw new FormatException('${error.message}\nStack trace:\n$trace'); | |
133 } | |
134 } | |
135 | |
136 /// Parses a string representation of a Dart VM stack trace. | |
137 Trace.parseVM(String trace) | |
138 : this(_parseVM(trace)); | |
139 | |
140 static List<Frame> _parseVM(String trace) { | |
141 var lines = trace.trim().split("\n"); | |
142 var frames = lines.take(lines.length - 1) | |
143 .map((line) => new Frame.parseVM(line)) | |
144 .toList(); | |
145 | |
146 // TODO(nweiz): Remove this when issue 23614 is fixed. | |
147 if (!lines.last.endsWith(".da")) { | |
148 frames.add(new Frame.parseVM(lines.last)); | |
149 } | |
150 | |
151 return frames; | |
152 } | |
153 | |
154 /// Parses a string representation of a Chrome/V8 stack trace. | |
155 Trace.parseV8(String trace) | |
156 : this(trace.split("\n").skip(1) | |
157 // It's possible that an Exception's description contains a line that | |
158 // looks like a V8 trace line, which will screw this up. | |
159 // Unfortunately, that's impossible to detect. | |
160 .skipWhile((line) => !line.startsWith(_v8TraceLine)) | |
161 .map((line) => new Frame.parseV8(line))); | |
162 | |
163 /// Parses a string representation of a JavaScriptCore stack trace. | |
164 Trace.parseJSCore(String trace) | |
165 : this(trace.split("\n") | |
166 .where((line) => line != "\tat ") | |
167 .map((line) => new Frame.parseV8(line))); | |
168 | |
169 /// Parses a string representation of an Internet Explorer stack trace. | |
170 /// | |
171 /// IE10+ traces look just like V8 traces. Prior to IE10, stack traces can't | |
172 /// be retrieved. | |
173 Trace.parseIE(String trace) | |
174 : this.parseV8(trace); | |
175 | |
176 /// Parses a string representation of a Firefox stack trace. | |
177 Trace.parseFirefox(String trace) | |
178 : this(trace.trim().split("\n") | |
179 .where((line) => line.isNotEmpty && line != '[native code]') | |
180 .map((line) => new Frame.parseFirefox(line))); | |
181 | |
182 /// Parses a string representation of a Safari stack trace. | |
183 Trace.parseSafari(String trace) | |
184 : this.parseFirefox(trace); | |
185 | |
186 /// Parses a string representation of a Safari 6.1+ stack trace. | |
187 @Deprecated("Use Trace.parseSafari instead.") | |
188 Trace.parseSafari6_1(String trace) | |
189 : this.parseSafari(trace); | |
190 | |
191 /// Parses a string representation of a Safari 6.0 stack trace. | |
192 @Deprecated("Use Trace.parseSafari instead.") | |
193 Trace.parseSafari6_0(String trace) | |
194 : this(trace.trim().split("\n") | |
195 .where((line) => line != '[native code]') | |
196 .map((line) => new Frame.parseFirefox(line))); | |
197 | |
198 /// Parses this package's string representation of a stack trace. | |
199 /// | |
200 /// This also parses string representations of [Chain]s. They parse to the | |
201 /// same trace that [Chain.toTrace] would return. | |
202 Trace.parseFriendly(String trace) | |
203 : this(trace.isEmpty | |
204 ? [] | |
205 : trace.trim().split("\n") | |
206 // Filter out asynchronous gaps from [Chain]s. | |
207 .where((line) => !line.startsWith('=====')) | |
208 .map((line) => new Frame.parseFriendly(line))); | |
209 | |
210 /// Returns a new [Trace] comprised of [frames]. | |
211 Trace(Iterable<Frame> frames) | |
212 : frames = new UnmodifiableListView<Frame>(frames.toList()); | |
213 | |
214 /// Returns a VM-style [StackTrace] object. | |
215 /// | |
216 /// The return value's [toString] method will always return a string | |
217 /// representation in the Dart VM's stack trace format, regardless of what | |
218 /// platform is being used. | |
219 StackTrace get vmTrace => new VMTrace(frames); | |
220 | |
221 /// Returns a terser version of [this]. | |
222 /// | |
223 /// This is accomplished by folding together multiple stack frames from the | |
224 /// core library or from this package, as in [foldFrames]. Remaining core | |
225 /// library frames have their libraries, "-patch" suffixes, and line numbers | |
226 /// removed. If the outermost frame of the stack trace is a core library | |
227 /// frame, it's removed entirely. | |
228 /// | |
229 /// For custom folding, see [foldFrames]. | |
230 Trace get terse => foldFrames((_) => false, terse: true); | |
231 | |
232 /// Returns a new [Trace] based on [this] where multiple stack frames matching | |
233 /// [predicate] are folded together. | |
234 /// | |
235 /// This means that whenever there are multiple frames in a row that match | |
236 /// [predicate], only the last one is kept. This is useful for limiting the | |
237 /// amount of library code that appears in a stack trace by only showing user | |
238 /// code and code that's called by user code. | |
239 /// | |
240 /// If [terse] is true, this will also fold together frames from the core | |
241 /// library or from this package, simplify core library frames, and | |
242 /// potentially remove the outermost frame as in [Trace.terse]. | |
243 Trace foldFrames(bool predicate(Frame frame), {bool terse: false}) { | |
244 if (terse) { | |
245 var oldPredicate = predicate; | |
246 predicate = (frame) { | |
247 if (oldPredicate(frame)) return true; | |
248 | |
249 if (frame.isCore) return true; | |
250 if (frame.package == 'stack_trace') return true; | |
251 | |
252 // Ignore async stack frames without any line or column information. | |
253 // These come from the VM's async/await implementation and represent | |
254 // internal frames. They only ever show up in stack chains and are | |
255 // always surrounded by other traces that are actually useful, so we can | |
256 // just get rid of them. | |
257 // TODO(nweiz): Get rid of this logic some time after issue 22009 is | |
258 // fixed. | |
259 if (!frame.member.contains('<async>')) return false; | |
260 return frame.line == null; | |
261 }; | |
262 } | |
263 | |
264 var newFrames = []; | |
265 for (var frame in frames.reversed) { | |
266 if (frame is UnparsedFrame || !predicate(frame)) { | |
267 newFrames.add(frame); | |
268 } else if (newFrames.isEmpty || !predicate(newFrames.last)) { | |
269 newFrames.add(new Frame( | |
270 frame.uri, frame.line, frame.column, frame.member)); | |
271 } | |
272 } | |
273 | |
274 if (terse) { | |
275 newFrames = newFrames.map((frame) { | |
276 if (frame is UnparsedFrame || !predicate(frame)) return frame; | |
277 var library = frame.library.replaceAll(_terseRegExp, ''); | |
278 return new Frame(Uri.parse(library), null, null, frame.member); | |
279 }).toList(); | |
280 if (newFrames.length > 1 && newFrames.first.isCore) newFrames.removeAt(0); | |
281 } | |
282 | |
283 return new Trace(newFrames.reversed); | |
284 } | |
285 | |
286 /// Returns a human-readable string representation of [this]. | |
287 String toString() { | |
288 // Figure out the longest path so we know how much to pad. | |
289 var longest = frames.map((frame) => frame.location.length) | |
290 .fold(0, math.max); | |
291 | |
292 // Print out the stack trace nicely formatted. | |
293 return frames.map((frame) { | |
294 if (frame is UnparsedFrame) return "$frame\n"; | |
295 return '${padRight(frame.location, longest)} ${frame.member}\n'; | |
296 }).join(); | |
297 } | |
298 } | |
OLD | NEW |