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