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 |