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 frame; |
| 6 |
| 7 |
| 8 import 'package:path/path.dart' as path; |
| 9 |
| 10 import 'trace.dart'; |
| 11 |
| 12 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) |
| 13 final _vmFrame = new RegExp( |
| 14 r'^#\d+\s+(\S.*) \((.+?):(\d+)(?::(\d+))?\)$'); |
| 15 |
| 16 // at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28) |
| 17 // at VW.call$0 (eval as fn |
| 18 // (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28) |
| 19 // at http://pub.dartlang.org/stuff.dart.js:560:28 |
| 20 final _v8Frame = new RegExp( |
| 21 r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$'); |
| 22 |
| 23 // http://pub.dartlang.org/stuff.dart.js:560:28 |
| 24 final _v8UrlLocation = new RegExp(r'^(.*):(\d+):(\d+)$'); |
| 25 |
| 26 // eval as function (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28 |
| 27 // eval as function (http://pub.dartlang.org/stuff.dart.js:560:28) |
| 28 // eval as function (eval as otherFunction |
| 29 // (http://pub.dartlang.org/stuff.dart.js:560:28)) |
| 30 final _v8EvalLocation = new RegExp( |
| 31 r'^eval at (?:\S.*?) \((.*)\)(?:, .*?:\d+:\d+)?$'); |
| 32 |
| 33 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560 |
| 34 // .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560 |
| 35 // .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560 |
| 36 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560:36 |
| 37 // http://pub.dartlang.org/stuff.dart.js:560 |
| 38 final _firefoxSafariFrame = new RegExp( |
| 39 r'^' |
| 40 r'(?:' // Member description. Not present in some Safari frames. |
| 41 r'([^@(/]*)' // The actual name of the member. |
| 42 r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox. |
| 43 r'((?:/[^/]*)*)' // Extra characters indicating a nested closure. |
| 44 r'(?:\(.*\))?' // Arguments to the closure. |
| 45 r'@' |
| 46 r')?' |
| 47 r'(.*?)' // The frame's URL. |
| 48 r':' |
| 49 r'(\d*)' // The line number. Empty in Safari if it's unknown. |
| 50 r'(?::(\d*))?' // The column number. Not present in older browsers and |
| 51 // empty in Safari if it's unknown. |
| 52 r'$'); |
| 53 |
| 54 // foo/bar.dart 10:11 in Foo._bar |
| 55 // http://dartlang.org/foo/bar.dart in Foo._bar |
| 56 final _friendlyFrame = new RegExp( |
| 57 r'^(\S+)(?: (\d+)(?::(\d+))?)?\s+([^\d]\S*)$'); |
| 58 |
| 59 final _initialDot = new RegExp(r"^\."); |
| 60 |
| 61 /// A single stack frame. Each frame points to a precise location in Dart code. |
| 62 class Frame { |
| 63 /// The URI of the file in which the code is located. |
| 64 /// |
| 65 /// This URI will usually have the scheme `dart`, `file`, `http`, or `https`. |
| 66 final Uri uri; |
| 67 |
| 68 /// The line number on which the code location is located. |
| 69 /// |
| 70 /// This can be null, indicating that the line number is unknown or |
| 71 /// unimportant. |
| 72 final int line; |
| 73 |
| 74 /// The column number of the code location. |
| 75 /// |
| 76 /// This can be null, indicating that the column number is unknown or |
| 77 /// unimportant. |
| 78 final int column; |
| 79 |
| 80 /// The name of the member in which the code location occurs. |
| 81 /// |
| 82 /// Anonymous closures are represented as `<fn>` in this member string. |
| 83 final String member; |
| 84 |
| 85 /// Whether this stack frame comes from the Dart core libraries. |
| 86 bool get isCore => uri.scheme == 'dart'; |
| 87 |
| 88 /// Returns a human-friendly description of the library that this stack frame |
| 89 /// comes from. |
| 90 /// |
| 91 /// This will usually be the string form of [uri], but a relative URI will be |
| 92 /// used if possible. |
| 93 String get library => path.prettyUri(uri); |
| 94 |
| 95 /// Returns the name of the package this stack frame comes from, or `null` if |
| 96 /// this stack frame doesn't come from a `package:` URL. |
| 97 String get package { |
| 98 if (uri.scheme != 'package') return null; |
| 99 return uri.path.split('/').first; |
| 100 } |
| 101 |
| 102 /// A human-friendly description of the code location. |
| 103 String get location { |
| 104 if (line == null) return library; |
| 105 if (column == null) return '$library $line'; |
| 106 return '$library $line:$column'; |
| 107 } |
| 108 |
| 109 /// Returns a single frame of the current stack. |
| 110 /// |
| 111 /// By default, this will return the frame above the current method. If |
| 112 /// [level] is `0`, it will return the current method's frame; if [level] is |
| 113 /// higher than `1`, it will return higher frames. |
| 114 factory Frame.caller([int level=1]) { |
| 115 if (level < 0) { |
| 116 throw new ArgumentError("Argument [level] must be greater than or equal " |
| 117 "to 0."); |
| 118 } |
| 119 |
| 120 return new Trace.current(level + 1).frames.first; |
| 121 } |
| 122 |
| 123 /// Parses a string representation of a Dart VM stack frame. |
| 124 factory Frame.parseVM(String frame) { |
| 125 // The VM sometimes folds multiple stack frames together and replaces them |
| 126 // with "...". |
| 127 if (frame == '...') { |
| 128 return new Frame(new Uri(), null, null, '...'); |
| 129 } |
| 130 |
| 131 var match = _vmFrame.firstMatch(frame); |
| 132 if (match == null) { |
| 133 throw new FormatException("Couldn't parse VM stack trace line '$frame'."); |
| 134 } |
| 135 |
| 136 // Get the pieces out of the regexp match. Function, URI and line should |
| 137 // always be found. The column is optional. |
| 138 var member = match[1].replaceAll("<anonymous closure>", "<fn>"); |
| 139 var uri = Uri.parse(match[2]); |
| 140 var line = int.parse(match[3]); |
| 141 var column = null; |
| 142 var columnMatch = match[4]; |
| 143 if (columnMatch != null) { |
| 144 column = int.parse(columnMatch); |
| 145 } |
| 146 return new Frame(uri, line, column, member); |
| 147 } |
| 148 |
| 149 /// Parses a string representation of a Chrome/V8 stack frame. |
| 150 factory Frame.parseV8(String frame) { |
| 151 var match = _v8Frame.firstMatch(frame); |
| 152 if (match == null) { |
| 153 throw new FormatException("Couldn't parse V8 stack trace line '$frame'."); |
| 154 } |
| 155 |
| 156 // v8 location strings can be arbitrarily-nested, since it adds a layer of |
| 157 // nesting for each eval performed on that line. |
| 158 parseLocation(location, member) { |
| 159 var evalMatch = _v8EvalLocation.firstMatch(location); |
| 160 while (evalMatch != null) { |
| 161 location = evalMatch[1]; |
| 162 evalMatch = _v8EvalLocation.firstMatch(location); |
| 163 } |
| 164 |
| 165 var urlMatch = _v8UrlLocation.firstMatch(location); |
| 166 if (urlMatch == null) { |
| 167 throw new FormatException( |
| 168 "Couldn't parse V8 stack trace line '$frame'."); |
| 169 } |
| 170 |
| 171 return new Frame( |
| 172 _uriOrPathToUri(urlMatch[1]), |
| 173 int.parse(urlMatch[2]), |
| 174 int.parse(urlMatch[3]), |
| 175 member); |
| 176 } |
| 177 |
| 178 // V8 stack frames can be in two forms. |
| 179 if (match[2] != null) { |
| 180 // The first form looks like " at FUNCTION (LOCATION)". V8 proper lists |
| 181 // anonymous functions within eval as "<anonymous>", while IE10 lists them |
| 182 // as "Anonymous function". |
| 183 return parseLocation(match[2], |
| 184 match[1].replaceAll("<anonymous>", "<fn>") |
| 185 .replaceAll("Anonymous function", "<fn>")); |
| 186 } else { |
| 187 // The second form looks like " at LOCATION", and is used for anonymous |
| 188 // functions. |
| 189 return parseLocation(match[3], "<fn>"); |
| 190 } |
| 191 } |
| 192 |
| 193 /// Parses a string representation of an IE stack frame. |
| 194 /// |
| 195 /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't |
| 196 /// be retrieved. |
| 197 factory Frame.parseIE(String frame) => new Frame.parseV8(frame); |
| 198 |
| 199 /// Parses a string representation of a Firefox stack frame. |
| 200 factory Frame.parseFirefox(String frame) { |
| 201 var match = _firefoxSafariFrame.firstMatch(frame); |
| 202 if (match == null) { |
| 203 throw new FormatException( |
| 204 "Couldn't parse Firefox/Safari stack trace line '$frame'."); |
| 205 } |
| 206 |
| 207 // Normally this is a URI, but in a jsshell trace it can be a path. |
| 208 var uri = _uriOrPathToUri(match[3]); |
| 209 |
| 210 var member; |
| 211 if (match[1] != null) { |
| 212 member = match[1]; |
| 213 member += |
| 214 new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); |
| 215 if (member == '') member = '<fn>'; |
| 216 |
| 217 // Some Firefox members have initial dots. We remove them for consistency |
| 218 // with other platforms. |
| 219 member = member.replaceFirst(_initialDot, ''); |
| 220 } else { |
| 221 member = '<fn>'; |
| 222 } |
| 223 |
| 224 var line = match[4] == '' ? null : int.parse(match[4]); |
| 225 var column = match[5] == null || match[5] == '' ? |
| 226 null : int.parse(match[5]); |
| 227 return new Frame(uri, line, column, member); |
| 228 } |
| 229 |
| 230 /// Parses a string representation of a Safari 6.0 stack frame. |
| 231 @Deprecated("Use Frame.parseSafari instead.") |
| 232 factory Frame.parseSafari6_0(String frame) => new Frame.parseFirefox(frame); |
| 233 |
| 234 /// Parses a string representation of a Safari 6.1+ stack frame. |
| 235 @Deprecated("Use Frame.parseSafari instead.") |
| 236 factory Frame.parseSafari6_1(String frame) => new Frame.parseFirefox(frame); |
| 237 |
| 238 /// Parses a string representation of a Safari stack frame. |
| 239 factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame); |
| 240 |
| 241 /// Parses this package's string representation of a stack frame. |
| 242 factory Frame.parseFriendly(String frame) { |
| 243 var match = _friendlyFrame.firstMatch(frame); |
| 244 if (match == null) { |
| 245 throw new FormatException( |
| 246 "Couldn't parse package:stack_trace stack trace line '$frame'."); |
| 247 } |
| 248 |
| 249 var uri = Uri.parse(match[1]); |
| 250 // If there's no scheme, this is a relative URI. We should interpret it as |
| 251 // relative to the current working directory. |
| 252 if (uri.scheme == '') { |
| 253 uri = path.toUri(path.absolute(path.fromUri(uri))); |
| 254 } |
| 255 |
| 256 var line = match[2] == null ? null : int.parse(match[2]); |
| 257 var column = match[3] == null ? null : int.parse(match[3]); |
| 258 return new Frame(uri, line, column, match[4]); |
| 259 } |
| 260 |
| 261 /// A regular expression matching an absolute URI. |
| 262 static final _uriRegExp = new RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://'); |
| 263 |
| 264 /// A regular expression matching a Windows path. |
| 265 static final _windowsRegExp = new RegExp(r'^([a-zA-Z]:[\\/]|\\\\)'); |
| 266 |
| 267 /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path, |
| 268 /// to a URI (absolute if possible). |
| 269 static Uri _uriOrPathToUri(String uriOrPath) { |
| 270 if (uriOrPath.contains(_uriRegExp)) { |
| 271 return Uri.parse(uriOrPath); |
| 272 } else if (uriOrPath.contains(_windowsRegExp)) { |
| 273 return new Uri.file(uriOrPath, windows: true); |
| 274 } else if (uriOrPath.startsWith('/')) { |
| 275 return new Uri.file(uriOrPath, windows: false); |
| 276 } |
| 277 |
| 278 // As far as I've seen, Firefox and V8 both always report absolute paths in |
| 279 // their stack frames. However, if we do get a relative path, we should |
| 280 // handle it gracefully. |
| 281 if (uriOrPath.contains('\\')) return path.windows.toUri(uriOrPath); |
| 282 return Uri.parse(uriOrPath); |
| 283 } |
| 284 |
| 285 Frame(this.uri, this.line, this.column, this.member); |
| 286 |
| 287 String toString() => '$location in $member'; |
| 288 } |
OLD | NEW |