| 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 frame; | 5 library frame; |
| 6 | 6 |
| 7 import 'package:path/path.dart' as path; | 7 import 'package:path/path.dart' as path; |
| 8 | 8 |
| 9 import 'trace.dart'; | 9 import 'trace.dart'; |
| 10 import 'unparsed_frame.dart'; |
| 10 | 11 |
| 11 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) | 12 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) |
| 12 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42) | 13 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42) |
| 13 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart) | 14 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart) |
| 14 final _vmFrame = new RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$'); | 15 final _vmFrame = new RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$'); |
| 15 | 16 |
| 16 // at Object.stringify (native) | 17 // at Object.stringify (native) |
| 17 // at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28) | 18 // at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28) |
| 18 // at VW.call$0 (eval as fn | 19 // at VW.call$0 (eval as fn |
| 19 // (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28) | 20 // (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28) |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 119 factory Frame.caller([int level=1]) { | 120 factory Frame.caller([int level=1]) { |
| 120 if (level < 0) { | 121 if (level < 0) { |
| 121 throw new ArgumentError("Argument [level] must be greater than or equal " | 122 throw new ArgumentError("Argument [level] must be greater than or equal " |
| 122 "to 0."); | 123 "to 0."); |
| 123 } | 124 } |
| 124 | 125 |
| 125 return new Trace.current(level + 1).frames.first; | 126 return new Trace.current(level + 1).frames.first; |
| 126 } | 127 } |
| 127 | 128 |
| 128 /// Parses a string representation of a Dart VM stack frame. | 129 /// Parses a string representation of a Dart VM stack frame. |
| 129 factory Frame.parseVM(String frame) { | 130 factory Frame.parseVM(String frame) => _catchFormatException(frame, () { |
| 130 // The VM sometimes folds multiple stack frames together and replaces them | 131 // The VM sometimes folds multiple stack frames together and replaces them |
| 131 // with "...". | 132 // with "...". |
| 132 if (frame == '...') { | 133 if (frame == '...') { |
| 133 return new Frame(new Uri(), null, null, '...'); | 134 return new Frame(new Uri(), null, null, '...'); |
| 134 } | 135 } |
| 135 | 136 |
| 136 var match = _vmFrame.firstMatch(frame); | 137 var match = _vmFrame.firstMatch(frame); |
| 137 if (match == null) { | 138 if (match == null) return new UnparsedFrame(frame); |
| 138 throw new FormatException("Couldn't parse VM stack trace line '$frame'."); | |
| 139 } | |
| 140 | 139 |
| 141 // Get the pieces out of the regexp match. Function, URI and line should | 140 // Get the pieces out of the regexp match. Function, URI and line should |
| 142 // always be found. The column is optional. | 141 // always be found. The column is optional. |
| 143 var member = match[1] | 142 var member = match[1] |
| 144 .replaceAll(_asyncBody, "<async>") | 143 .replaceAll(_asyncBody, "<async>") |
| 145 .replaceAll("<anonymous closure>", "<fn>"); | 144 .replaceAll("<anonymous closure>", "<fn>"); |
| 146 var uri = Uri.parse(match[2]); | 145 var uri = Uri.parse(match[2]); |
| 147 | 146 |
| 148 var lineAndColumn = match[3].split(':'); | 147 var lineAndColumn = match[3].split(':'); |
| 149 var line = lineAndColumn.length > 1 ? int.parse(lineAndColumn[1]) : null; | 148 var line = lineAndColumn.length > 1 ? int.parse(lineAndColumn[1]) : null; |
| 150 var column = lineAndColumn.length > 2 ? int.parse(lineAndColumn[2]) : null; | 149 var column = lineAndColumn.length > 2 ? int.parse(lineAndColumn[2]) : null; |
| 151 return new Frame(uri, line, column, member); | 150 return new Frame(uri, line, column, member); |
| 152 } | 151 }); |
| 153 | 152 |
| 154 /// Parses a string representation of a Chrome/V8 stack frame. | 153 /// Parses a string representation of a Chrome/V8 stack frame. |
| 155 factory Frame.parseV8(String frame) { | 154 factory Frame.parseV8(String frame) => _catchFormatException(frame, () { |
| 156 var match = _v8Frame.firstMatch(frame); | 155 var match = _v8Frame.firstMatch(frame); |
| 157 if (match == null) { | 156 if (match == null) return new UnparsedFrame(frame); |
| 158 throw new FormatException("Couldn't parse V8 stack trace line '$frame'."); | |
| 159 } | |
| 160 | 157 |
| 161 // v8 location strings can be arbitrarily-nested, since it adds a layer of | 158 // v8 location strings can be arbitrarily-nested, since it adds a layer of |
| 162 // nesting for each eval performed on that line. | 159 // nesting for each eval performed on that line. |
| 163 parseLocation(location, member) { | 160 parseLocation(location, member) { |
| 164 var evalMatch = _v8EvalLocation.firstMatch(location); | 161 var evalMatch = _v8EvalLocation.firstMatch(location); |
| 165 while (evalMatch != null) { | 162 while (evalMatch != null) { |
| 166 location = evalMatch[1]; | 163 location = evalMatch[1]; |
| 167 evalMatch = _v8EvalLocation.firstMatch(location); | 164 evalMatch = _v8EvalLocation.firstMatch(location); |
| 168 } | 165 } |
| 169 | 166 |
| 170 if (location == 'native') { | 167 if (location == 'native') { |
| 171 return new Frame(Uri.parse('native'), null, null, member); | 168 return new Frame(Uri.parse('native'), null, null, member); |
| 172 } | 169 } |
| 173 | 170 |
| 174 var urlMatch = _v8UrlLocation.firstMatch(location); | 171 var urlMatch = _v8UrlLocation.firstMatch(location); |
| 175 if (urlMatch == null) { | 172 if (urlMatch == null) return new UnparsedFrame(frame); |
| 176 throw new FormatException( | |
| 177 "Couldn't parse V8 stack trace line '$frame'."); | |
| 178 } | |
| 179 | 173 |
| 180 return new Frame( | 174 return new Frame( |
| 181 _uriOrPathToUri(urlMatch[1]), | 175 _uriOrPathToUri(urlMatch[1]), |
| 182 int.parse(urlMatch[2]), | 176 int.parse(urlMatch[2]), |
| 183 int.parse(urlMatch[3]), | 177 int.parse(urlMatch[3]), |
| 184 member); | 178 member); |
| 185 } | 179 } |
| 186 | 180 |
| 187 // V8 stack frames can be in two forms. | 181 // V8 stack frames can be in two forms. |
| 188 if (match[2] != null) { | 182 if (match[2] != null) { |
| 189 // The first form looks like " at FUNCTION (LOCATION)". V8 proper lists | 183 // The first form looks like " at FUNCTION (LOCATION)". V8 proper lists |
| 190 // anonymous functions within eval as "<anonymous>", while IE10 lists them | 184 // anonymous functions within eval as "<anonymous>", while IE10 lists them |
| 191 // as "Anonymous function". | 185 // as "Anonymous function". |
| 192 return parseLocation(match[2], | 186 return parseLocation(match[2], |
| 193 match[1].replaceAll("<anonymous>", "<fn>") | 187 match[1].replaceAll("<anonymous>", "<fn>") |
| 194 .replaceAll("Anonymous function", "<fn>")); | 188 .replaceAll("Anonymous function", "<fn>")); |
| 195 } else { | 189 } else { |
| 196 // The second form looks like " at LOCATION", and is used for anonymous | 190 // The second form looks like " at LOCATION", and is used for anonymous |
| 197 // functions. | 191 // functions. |
| 198 return parseLocation(match[3], "<fn>"); | 192 return parseLocation(match[3], "<fn>"); |
| 199 } | 193 } |
| 200 } | 194 }); |
| 201 | 195 |
| 202 /// Parses a string representation of a JavaScriptCore stack trace. | 196 /// Parses a string representation of a JavaScriptCore stack trace. |
| 203 factory Frame.parseJSCore(String frame) => new Frame.parseV8(frame); | 197 factory Frame.parseJSCore(String frame) => new Frame.parseV8(frame); |
| 204 | 198 |
| 205 /// Parses a string representation of an IE stack frame. | 199 /// Parses a string representation of an IE stack frame. |
| 206 /// | 200 /// |
| 207 /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't | 201 /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't |
| 208 /// be retrieved. | 202 /// be retrieved. |
| 209 factory Frame.parseIE(String frame) => new Frame.parseV8(frame); | 203 factory Frame.parseIE(String frame) => new Frame.parseV8(frame); |
| 210 | 204 |
| 211 /// Parses a string representation of a Firefox stack frame. | 205 /// Parses a string representation of a Firefox stack frame. |
| 212 factory Frame.parseFirefox(String frame) { | 206 factory Frame.parseFirefox(String frame) => _catchFormatException(frame, () { |
| 213 var match = _firefoxSafariFrame.firstMatch(frame); | 207 var match = _firefoxSafariFrame.firstMatch(frame); |
| 214 if (match == null) { | 208 if (match == null) return new UnparsedFrame(frame); |
| 215 throw new FormatException( | |
| 216 "Couldn't parse Firefox/Safari stack trace line '$frame'."); | |
| 217 } | |
| 218 | 209 |
| 219 // Normally this is a URI, but in a jsshell trace it can be a path. | 210 // Normally this is a URI, but in a jsshell trace it can be a path. |
| 220 var uri = _uriOrPathToUri(match[3]); | 211 var uri = _uriOrPathToUri(match[3]); |
| 221 | 212 |
| 222 var member; | 213 var member; |
| 223 if (match[1] != null) { | 214 if (match[1] != null) { |
| 224 member = match[1]; | 215 member = match[1]; |
| 225 member += | 216 member += |
| 226 new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); | 217 new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); |
| 227 if (member == '') member = '<fn>'; | 218 if (member == '') member = '<fn>'; |
| 228 | 219 |
| 229 // Some Firefox members have initial dots. We remove them for consistency | 220 // Some Firefox members have initial dots. We remove them for consistency |
| 230 // with other platforms. | 221 // with other platforms. |
| 231 member = member.replaceFirst(_initialDot, ''); | 222 member = member.replaceFirst(_initialDot, ''); |
| 232 } else { | 223 } else { |
| 233 member = '<fn>'; | 224 member = '<fn>'; |
| 234 } | 225 } |
| 235 | 226 |
| 236 var line = match[4] == '' ? null : int.parse(match[4]); | 227 var line = match[4] == '' ? null : int.parse(match[4]); |
| 237 var column = match[5] == null || match[5] == '' ? | 228 var column = match[5] == null || match[5] == '' ? |
| 238 null : int.parse(match[5]); | 229 null : int.parse(match[5]); |
| 239 return new Frame(uri, line, column, member); | 230 return new Frame(uri, line, column, member); |
| 240 } | 231 }); |
| 241 | 232 |
| 242 /// Parses a string representation of a Safari 6.0 stack frame. | 233 /// Parses a string representation of a Safari 6.0 stack frame. |
| 243 @Deprecated("Use Frame.parseSafari instead.") | 234 @Deprecated("Use Frame.parseSafari instead.") |
| 244 factory Frame.parseSafari6_0(String frame) => new Frame.parseFirefox(frame); | 235 factory Frame.parseSafari6_0(String frame) => new Frame.parseFirefox(frame); |
| 245 | 236 |
| 246 /// Parses a string representation of a Safari 6.1+ stack frame. | 237 /// Parses a string representation of a Safari 6.1+ stack frame. |
| 247 @Deprecated("Use Frame.parseSafari instead.") | 238 @Deprecated("Use Frame.parseSafari instead.") |
| 248 factory Frame.parseSafari6_1(String frame) => new Frame.parseFirefox(frame); | 239 factory Frame.parseSafari6_1(String frame) => new Frame.parseFirefox(frame); |
| 249 | 240 |
| 250 /// Parses a string representation of a Safari stack frame. | 241 /// Parses a string representation of a Safari stack frame. |
| 251 factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame); | 242 factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame); |
| 252 | 243 |
| 253 /// Parses this package's string representation of a stack frame. | 244 /// Parses this package's string representation of a stack frame. |
| 254 factory Frame.parseFriendly(String frame) { | 245 factory Frame.parseFriendly(String frame) => _catchFormatException(frame, () { |
| 255 var match = _friendlyFrame.firstMatch(frame); | 246 var match = _friendlyFrame.firstMatch(frame); |
| 256 if (match == null) { | 247 if (match == null) { |
| 257 throw new FormatException( | 248 throw new FormatException( |
| 258 "Couldn't parse package:stack_trace stack trace line '$frame'."); | 249 "Couldn't parse package:stack_trace stack trace line '$frame'."); |
| 259 } | 250 } |
| 260 | 251 |
| 261 var uri = Uri.parse(match[1]); | 252 var uri = Uri.parse(match[1]); |
| 262 // If there's no scheme, this is a relative URI. We should interpret it as | 253 // If there's no scheme, this is a relative URI. We should interpret it as |
| 263 // relative to the current working directory. | 254 // relative to the current working directory. |
| 264 if (uri.scheme == '') { | 255 if (uri.scheme == '') { |
| 265 uri = path.toUri(path.absolute(path.fromUri(uri))); | 256 uri = path.toUri(path.absolute(path.fromUri(uri))); |
| 266 } | 257 } |
| 267 | 258 |
| 268 var line = match[2] == null ? null : int.parse(match[2]); | 259 var line = match[2] == null ? null : int.parse(match[2]); |
| 269 var column = match[3] == null ? null : int.parse(match[3]); | 260 var column = match[3] == null ? null : int.parse(match[3]); |
| 270 return new Frame(uri, line, column, match[4]); | 261 return new Frame(uri, line, column, match[4]); |
| 271 } | 262 }); |
| 272 | 263 |
| 273 /// A regular expression matching an absolute URI. | 264 /// A regular expression matching an absolute URI. |
| 274 static final _uriRegExp = new RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://'); | 265 static final _uriRegExp = new RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://'); |
| 275 | 266 |
| 276 /// A regular expression matching a Windows path. | 267 /// A regular expression matching a Windows path. |
| 277 static final _windowsRegExp = new RegExp(r'^([a-zA-Z]:[\\/]|\\\\)'); | 268 static final _windowsRegExp = new RegExp(r'^([a-zA-Z]:[\\/]|\\\\)'); |
| 278 | 269 |
| 279 /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path, | 270 /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path, |
| 280 /// to a URI (absolute if possible). | 271 /// to a URI (absolute if possible). |
| 281 static Uri _uriOrPathToUri(String uriOrPath) { | 272 static Uri _uriOrPathToUri(String uriOrPath) { |
| 282 if (uriOrPath.contains(_uriRegExp)) { | 273 if (uriOrPath.contains(_uriRegExp)) { |
| 283 return Uri.parse(uriOrPath); | 274 return Uri.parse(uriOrPath); |
| 284 } else if (uriOrPath.contains(_windowsRegExp)) { | 275 } else if (uriOrPath.contains(_windowsRegExp)) { |
| 285 return new Uri.file(uriOrPath, windows: true); | 276 return new Uri.file(uriOrPath, windows: true); |
| 286 } else if (uriOrPath.startsWith('/')) { | 277 } else if (uriOrPath.startsWith('/')) { |
| 287 return new Uri.file(uriOrPath, windows: false); | 278 return new Uri.file(uriOrPath, windows: false); |
| 288 } | 279 } |
| 289 | 280 |
| 290 // As far as I've seen, Firefox and V8 both always report absolute paths in | 281 // As far as I've seen, Firefox and V8 both always report absolute paths in |
| 291 // their stack frames. However, if we do get a relative path, we should | 282 // their stack frames. However, if we do get a relative path, we should |
| 292 // handle it gracefully. | 283 // handle it gracefully. |
| 293 if (uriOrPath.contains('\\')) return path.windows.toUri(uriOrPath); | 284 if (uriOrPath.contains('\\')) return path.windows.toUri(uriOrPath); |
| 294 return Uri.parse(uriOrPath); | 285 return Uri.parse(uriOrPath); |
| 295 } | 286 } |
| 296 | 287 |
| 288 /// Runs [body] and returns its result. |
| 289 /// |
| 290 /// If [body] throws a [FormatException], returns an [UnparsedFrame] with |
| 291 /// [text] instead. |
| 292 static Frame _catchFormatException(String text, Frame body()) { |
| 293 try { |
| 294 return body(); |
| 295 } on FormatException catch (_) { |
| 296 return new UnparsedFrame(text); |
| 297 } |
| 298 } |
| 299 |
| 297 Frame(this.uri, this.line, this.column, this.member); | 300 Frame(this.uri, this.line, this.column, this.member); |
| 298 | 301 |
| 299 String toString() => '$location in $member'; | 302 String toString() => '$location in $member'; |
| 300 } | 303 } |
| OLD | NEW |