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; | |
6 | |
7 import 'package:path/path.dart' as path; | 5 import 'package:path/path.dart' as path; |
8 | 6 |
9 import 'trace.dart'; | 7 import 'trace.dart'; |
10 import 'unparsed_frame.dart'; | 8 import 'unparsed_frame.dart'; |
11 | 9 |
12 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) | 10 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42:21) |
13 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42) | 11 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart:42) |
14 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart) | 12 // #1 Foo._bar (file:///home/nweiz/code/stuff.dart) |
15 final _vmFrame = new RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$'); | 13 final _vmFrame = new RegExp(r'^#\d+\s+(\S.*) \((.+?)((?::\d+){0,2})\)$'); |
16 | 14 |
17 // at Object.stringify (native) | 15 // at Object.stringify (native) |
18 // at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28) | 16 // at VW.call$0 (http://pub.dartlang.org/stuff.dart.js:560:28) |
19 // at VW.call$0 (eval as fn | 17 // at VW.call$0 (eval as fn |
20 // (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28) | 18 // (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28) |
21 // at http://pub.dartlang.org/stuff.dart.js:560:28 | 19 // at http://pub.dartlang.org/stuff.dart.js:560:28 |
22 final _v8Frame = new RegExp( | 20 final _v8Frame = |
23 r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$'); | 21 new RegExp(r'^\s*at (?:(\S.*?)(?: \[as [^\]]+\])? \((.*)\)|(.*))$'); |
24 | 22 |
25 // http://pub.dartlang.org/stuff.dart.js:560:28 | 23 // http://pub.dartlang.org/stuff.dart.js:560:28 |
26 final _v8UrlLocation = new RegExp(r'^(.*):(\d+):(\d+)|native$'); | 24 final _v8UrlLocation = new RegExp(r'^(.*):(\d+):(\d+)|native$'); |
27 | 25 |
28 // eval as function (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28 | 26 // eval as function (http://pub.dartlang.org/stuff.dart.js:560:28), efn:3:28 |
29 // eval as function (http://pub.dartlang.org/stuff.dart.js:560:28) | 27 // eval as function (http://pub.dartlang.org/stuff.dart.js:560:28) |
30 // eval as function (eval as otherFunction | 28 // eval as function (eval as otherFunction |
31 // (http://pub.dartlang.org/stuff.dart.js:560:28)) | 29 // (http://pub.dartlang.org/stuff.dart.js:560:28)) |
32 final _v8EvalLocation = new RegExp( | 30 final _v8EvalLocation = |
33 r'^eval at (?:\S.*?) \((.*)\)(?:, .*?:\d+:\d+)?$'); | 31 new RegExp(r'^eval at (?:\S.*?) \((.*)\)(?:, .*?:\d+:\d+)?$'); |
34 | 32 |
35 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560 | 33 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560 |
36 // .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560 | 34 // .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560 |
37 // .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560 | 35 // .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560 |
38 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560:36 | 36 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560:36 |
39 // http://pub.dartlang.org/stuff.dart.js:560 | 37 // http://pub.dartlang.org/stuff.dart.js:560 |
40 final _firefoxSafariFrame = new RegExp( | 38 final _firefoxSafariFrame = new RegExp(r'^' |
41 r'^' | |
42 r'(?:' // Member description. Not present in some Safari frames. | 39 r'(?:' // Member description. Not present in some Safari frames. |
43 r'([^@(/]*)' // The actual name of the member. | 40 r'([^@(/]*)' // The actual name of the member. |
44 r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox. | 41 r'(?:\(.*\))?' // Arguments to the member, sometimes captured by Firefox. |
45 r'((?:/[^/]*)*)' // Extra characters indicating a nested closure. | 42 r'((?:/[^/]*)*)' // Extra characters indicating a nested closure. |
46 r'(?:\(.*\))?' // Arguments to the closure. | 43 r'(?:\(.*\))?' // Arguments to the closure. |
47 r'@' | 44 r'@' |
48 r')?' | 45 r')?' |
49 r'(.*?)' // The frame's URL. | 46 r'(.*?)' // The frame's URL. |
50 r':' | 47 r':' |
51 r'(\d*)' // The line number. Empty in Safari if it's unknown. | 48 r'(\d*)' // The line number. Empty in Safari if it's unknown. |
52 r'(?::(\d*))?' // The column number. Not present in older browsers and | 49 r'(?::(\d*))?' // The column number. Not present in older browsers and |
53 // empty in Safari if it's unknown. | 50 // empty in Safari if it's unknown. |
54 r'$'); | 51 r'$'); |
55 | 52 |
56 // foo/bar.dart 10:11 in Foo._bar | 53 // foo/bar.dart 10:11 Foo._bar |
57 // http://dartlang.org/foo/bar.dart in Foo._bar | 54 // foo/bar.dart 10:11 (anonymous function).dart.fn |
58 final _friendlyFrame = new RegExp( | 55 // http://dartlang.org/foo/bar.dart Foo._bar |
59 r'^(\S+)(?: (\d+)(?::(\d+))?)?\s+([^\d]\S*)$'); | 56 // data:... 10:11 Foo._bar |
| 57 final _friendlyFrame = new RegExp(r'^(\S+)(?: (\d+)(?::(\d+))?)?\s+([^\d].*)$'); |
60 | 58 |
61 /// A regular expression that matches asynchronous member names generated by the | 59 /// A regular expression that matches asynchronous member names generated by the |
62 /// VM. | 60 /// VM. |
63 final _asyncBody = new RegExp(r'<(<anonymous closure>|[^>]+)_async_body>'); | 61 final _asyncBody = new RegExp(r'<(<anonymous closure>|[^>]+)_async_body>'); |
64 | 62 |
65 final _initialDot = new RegExp(r"^\."); | 63 final _initialDot = new RegExp(r"^\."); |
66 | 64 |
67 /// A single stack frame. Each frame points to a precise location in Dart code. | 65 /// A single stack frame. Each frame points to a precise location in Dart code. |
68 class Frame { | 66 class Frame { |
69 /// The URI of the file in which the code is located. | 67 /// The URI of the file in which the code is located. |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 if (line == null) return library; | 111 if (line == null) return library; |
114 if (column == null) return '$library $line'; | 112 if (column == null) return '$library $line'; |
115 return '$library $line:$column'; | 113 return '$library $line:$column'; |
116 } | 114 } |
117 | 115 |
118 /// Returns a single frame of the current stack. | 116 /// Returns a single frame of the current stack. |
119 /// | 117 /// |
120 /// By default, this will return the frame above the current method. If | 118 /// By default, this will return the frame above the current method. If |
121 /// [level] is `0`, it will return the current method's frame; if [level] is | 119 /// [level] is `0`, it will return the current method's frame; if [level] is |
122 /// higher than `1`, it will return higher frames. | 120 /// higher than `1`, it will return higher frames. |
123 factory Frame.caller([int level=1]) { | 121 factory Frame.caller([int level = 1]) { |
124 if (level < 0) { | 122 if (level < 0) { |
125 throw new ArgumentError("Argument [level] must be greater than or equal " | 123 throw new ArgumentError("Argument [level] must be greater than or equal " |
126 "to 0."); | 124 "to 0."); |
127 } | 125 } |
128 | 126 |
129 return new Trace.current(level + 1).frames.first; | 127 return new Trace.current(level + 1).frames.first; |
130 } | 128 } |
131 | 129 |
132 /// Parses a string representation of a Dart VM stack frame. | 130 /// Parses a string representation of a Dart VM stack frame. |
133 factory Frame.parseVM(String frame) => _catchFormatException(frame, () { | 131 factory Frame.parseVM(String frame) => _catchFormatException(frame, () { |
134 // The VM sometimes folds multiple stack frames together and replaces them | 132 // The VM sometimes folds multiple stack frames together and replaces th
em |
135 // with "...". | 133 // with "...". |
136 if (frame == '...') { | 134 if (frame == '...') { |
137 return new Frame(new Uri(), null, null, '...'); | 135 return new Frame(new Uri(), null, null, '...'); |
138 } | 136 } |
139 | 137 |
140 var match = _vmFrame.firstMatch(frame); | 138 var match = _vmFrame.firstMatch(frame); |
141 if (match == null) return new UnparsedFrame(frame); | 139 if (match == null) return new UnparsedFrame(frame); |
142 | 140 |
143 // Get the pieces out of the regexp match. Function, URI and line should | 141 // Get the pieces out of the regexp match. Function, URI and line should |
144 // always be found. The column is optional. | 142 // always be found. The column is optional. |
145 var member = match[1] | 143 var member = match[1] |
146 .replaceAll(_asyncBody, "<async>") | 144 .replaceAll(_asyncBody, "<async>") |
147 .replaceAll("<anonymous closure>", "<fn>"); | 145 .replaceAll("<anonymous closure>", "<fn>"); |
148 var uri = Uri.parse(match[2]); | 146 var uri = Uri.parse(match[2]); |
149 | 147 |
150 var lineAndColumn = match[3].split(':'); | 148 var lineAndColumn = match[3].split(':'); |
151 var line = lineAndColumn.length > 1 ? int.parse(lineAndColumn[1]) : null; | 149 var line = |
152 var column = lineAndColumn.length > 2 ? int.parse(lineAndColumn[2]) : null; | 150 lineAndColumn.length > 1 ? int.parse(lineAndColumn[1]) : null; |
153 return new Frame(uri, line, column, member); | 151 var column = |
154 }); | 152 lineAndColumn.length > 2 ? int.parse(lineAndColumn[2]) : null; |
| 153 return new Frame(uri, line, column, member); |
| 154 }); |
155 | 155 |
156 /// Parses a string representation of a Chrome/V8 stack frame. | 156 /// Parses a string representation of a Chrome/V8 stack frame. |
157 factory Frame.parseV8(String frame) => _catchFormatException(frame, () { | 157 factory Frame.parseV8(String frame) => _catchFormatException(frame, () { |
158 var match = _v8Frame.firstMatch(frame); | 158 var match = _v8Frame.firstMatch(frame); |
159 if (match == null) return new UnparsedFrame(frame); | 159 if (match == null) return new UnparsedFrame(frame); |
160 | 160 |
161 // v8 location strings can be arbitrarily-nested, since it adds a layer of | 161 // v8 location strings can be arbitrarily-nested, since it adds a layer
of |
162 // nesting for each eval performed on that line. | 162 // nesting for each eval performed on that line. |
163 parseLocation(location, member) { | 163 parseLocation(location, member) { |
164 var evalMatch = _v8EvalLocation.firstMatch(location); | 164 var evalMatch = _v8EvalLocation.firstMatch(location); |
165 while (evalMatch != null) { | 165 while (evalMatch != null) { |
166 location = evalMatch[1]; | 166 location = evalMatch[1]; |
167 evalMatch = _v8EvalLocation.firstMatch(location); | 167 evalMatch = _v8EvalLocation.firstMatch(location); |
168 } | 168 } |
169 | 169 |
170 if (location == 'native') { | 170 if (location == 'native') { |
171 return new Frame(Uri.parse('native'), null, null, member); | 171 return new Frame(Uri.parse('native'), null, null, member); |
172 } | 172 } |
173 | 173 |
174 var urlMatch = _v8UrlLocation.firstMatch(location); | 174 var urlMatch = _v8UrlLocation.firstMatch(location); |
175 if (urlMatch == null) return new UnparsedFrame(frame); | 175 if (urlMatch == null) return new UnparsedFrame(frame); |
176 | 176 |
177 return new Frame( | 177 return new Frame(_uriOrPathToUri(urlMatch[1]), int.parse(urlMatch[2]), |
178 _uriOrPathToUri(urlMatch[1]), | 178 int.parse(urlMatch[3]), member); |
179 int.parse(urlMatch[2]), | 179 } |
180 int.parse(urlMatch[3]), | |
181 member); | |
182 } | |
183 | 180 |
184 // V8 stack frames can be in two forms. | 181 // V8 stack frames can be in two forms. |
185 if (match[2] != null) { | 182 if (match[2] != null) { |
186 // The first form looks like " at FUNCTION (LOCATION)". V8 proper lists | 183 // The first form looks like " at FUNCTION (LOCATION)". V8 proper list
s |
187 // anonymous functions within eval as "<anonymous>", while IE10 lists them | 184 // anonymous functions within eval as "<anonymous>", while IE10 lists
them |
188 // as "Anonymous function". | 185 // as "Anonymous function". |
189 return parseLocation(match[2], | 186 return parseLocation( |
190 match[1].replaceAll("<anonymous>", "<fn>") | 187 match[2], |
191 .replaceAll("Anonymous function", "<fn>")); | 188 match[1] |
192 } else { | 189 .replaceAll("<anonymous>", "<fn>") |
193 // The second form looks like " at LOCATION", and is used for anonymous | 190 .replaceAll("Anonymous function", "<fn>") |
194 // functions. | 191 .replaceAll("(anonymous function)", "<fn>")); |
195 return parseLocation(match[3], "<fn>"); | 192 } else { |
196 } | 193 // The second form looks like " at LOCATION", and is used for anonymou
s |
197 }); | 194 // functions. |
| 195 return parseLocation(match[3], "<fn>"); |
| 196 } |
| 197 }); |
198 | 198 |
199 /// Parses a string representation of a JavaScriptCore stack trace. | 199 /// Parses a string representation of a JavaScriptCore stack trace. |
200 factory Frame.parseJSCore(String frame) => new Frame.parseV8(frame); | 200 factory Frame.parseJSCore(String frame) => new Frame.parseV8(frame); |
201 | 201 |
202 /// Parses a string representation of an IE stack frame. | 202 /// Parses a string representation of an IE stack frame. |
203 /// | 203 /// |
204 /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't | 204 /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't |
205 /// be retrieved. | 205 /// be retrieved. |
206 factory Frame.parseIE(String frame) => new Frame.parseV8(frame); | 206 factory Frame.parseIE(String frame) => new Frame.parseV8(frame); |
207 | 207 |
208 /// Parses a string representation of a Firefox stack frame. | 208 /// Parses a string representation of a Firefox stack frame. |
209 factory Frame.parseFirefox(String frame) => _catchFormatException(frame, () { | 209 factory Frame.parseFirefox(String frame) => _catchFormatException(frame, () { |
210 var match = _firefoxSafariFrame.firstMatch(frame); | 210 var match = _firefoxSafariFrame.firstMatch(frame); |
211 if (match == null) return new UnparsedFrame(frame); | 211 if (match == null) return new UnparsedFrame(frame); |
212 | 212 |
213 // Normally this is a URI, but in a jsshell trace it can be a path. | 213 // Normally this is a URI, but in a jsshell trace it can be a path. |
214 var uri = _uriOrPathToUri(match[3]); | 214 var uri = _uriOrPathToUri(match[3]); |
215 | 215 |
216 var member; | 216 var member; |
217 if (match[1] != null) { | 217 if (match[1] != null) { |
218 member = match[1]; | 218 member = match[1]; |
219 member += | 219 member += |
220 new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); | 220 new List.filled('/'.allMatches(match[2]).length, ".<fn>").join(); |
221 if (member == '') member = '<fn>'; | 221 if (member == '') member = '<fn>'; |
222 | 222 |
223 // Some Firefox members have initial dots. We remove them for consistency | 223 // Some Firefox members have initial dots. We remove them for consiste
ncy |
224 // with other platforms. | 224 // with other platforms. |
225 member = member.replaceFirst(_initialDot, ''); | 225 member = member.replaceFirst(_initialDot, ''); |
226 } else { | 226 } else { |
227 member = '<fn>'; | 227 member = '<fn>'; |
228 } | 228 } |
229 | 229 |
230 var line = match[4] == '' ? null : int.parse(match[4]); | 230 var line = match[4] == '' ? null : int.parse(match[4]); |
231 var column = match[5] == null || match[5] == '' ? | 231 var column = |
232 null : int.parse(match[5]); | 232 match[5] == null || match[5] == '' ? null : int.parse(match[5]); |
233 return new Frame(uri, line, column, member); | 233 return new Frame(uri, line, column, member); |
234 }); | 234 }); |
235 | 235 |
236 /// Parses a string representation of a Safari 6.0 stack frame. | 236 /// Parses a string representation of a Safari 6.0 stack frame. |
237 @Deprecated("Use Frame.parseSafari instead.") | 237 @Deprecated("Use Frame.parseSafari instead.") |
238 factory Frame.parseSafari6_0(String frame) => new Frame.parseFirefox(frame); | 238 factory Frame.parseSafari6_0(String frame) => new Frame.parseFirefox(frame); |
239 | 239 |
240 /// Parses a string representation of a Safari 6.1+ stack frame. | 240 /// Parses a string representation of a Safari 6.1+ stack frame. |
241 @Deprecated("Use Frame.parseSafari instead.") | 241 @Deprecated("Use Frame.parseSafari instead.") |
242 factory Frame.parseSafari6_1(String frame) => new Frame.parseFirefox(frame); | 242 factory Frame.parseSafari6_1(String frame) => new Frame.parseFirefox(frame); |
243 | 243 |
244 /// Parses a string representation of a Safari stack frame. | 244 /// Parses a string representation of a Safari stack frame. |
245 factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame); | 245 factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame); |
246 | 246 |
247 /// Parses this package's string representation of a stack frame. | 247 /// Parses this package's string representation of a stack frame. |
248 factory Frame.parseFriendly(String frame) => _catchFormatException(frame, () { | 248 factory Frame.parseFriendly(String frame) => _catchFormatException(frame, () { |
249 var match = _friendlyFrame.firstMatch(frame); | 249 var match = _friendlyFrame.firstMatch(frame); |
250 if (match == null) { | 250 if (match == null) { |
251 throw new FormatException( | 251 throw new FormatException( |
252 "Couldn't parse package:stack_trace stack trace line '$frame'."); | 252 "Couldn't parse package:stack_trace stack trace line '$frame'."); |
253 } | 253 } |
| 254 // Fake truncated data urls generated by the friendly stack trace format |
| 255 // cause Uri.parse to throw an exception so we have to special case them
. |
| 256 var uri = match[1] == 'data:...' |
| 257 ? new Uri.dataFromString('') |
| 258 : Uri.parse(match[1]); |
| 259 // If there's no scheme, this is a relative URI. We should interpret it
as |
| 260 // relative to the current working directory. |
| 261 if (uri.scheme == '') { |
| 262 uri = path.toUri(path.absolute(path.fromUri(uri))); |
| 263 } |
254 | 264 |
255 var uri = Uri.parse(match[1]); | 265 var line = match[2] == null ? null : int.parse(match[2]); |
256 // If there's no scheme, this is a relative URI. We should interpret it as | 266 var column = match[3] == null ? null : int.parse(match[3]); |
257 // relative to the current working directory. | 267 return new Frame(uri, line, column, match[4]); |
258 if (uri.scheme == '') { | 268 }); |
259 uri = path.toUri(path.absolute(path.fromUri(uri))); | |
260 } | |
261 | |
262 var line = match[2] == null ? null : int.parse(match[2]); | |
263 var column = match[3] == null ? null : int.parse(match[3]); | |
264 return new Frame(uri, line, column, match[4]); | |
265 }); | |
266 | 269 |
267 /// A regular expression matching an absolute URI. | 270 /// A regular expression matching an absolute URI. |
268 static final _uriRegExp = new RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://'); | 271 static final _uriRegExp = new RegExp(r'^[a-zA-Z][-+.a-zA-Z\d]*://'); |
269 | 272 |
270 /// A regular expression matching a Windows path. | 273 /// A regular expression matching a Windows path. |
271 static final _windowsRegExp = new RegExp(r'^([a-zA-Z]:[\\/]|\\\\)'); | 274 static final _windowsRegExp = new RegExp(r'^([a-zA-Z]:[\\/]|\\\\)'); |
272 | 275 |
273 /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path, | 276 /// Converts [uriOrPath], which can be a URI, a Windows path, or a Posix path, |
274 /// to a URI (absolute if possible). | 277 /// to a URI (absolute if possible). |
275 static Uri _uriOrPathToUri(String uriOrPath) { | 278 static Uri _uriOrPathToUri(String uriOrPath) { |
(...skipping 21 matching lines...) Expand all Loading... |
297 return body(); | 300 return body(); |
298 } on FormatException catch (_) { | 301 } on FormatException catch (_) { |
299 return new UnparsedFrame(text); | 302 return new UnparsedFrame(text); |
300 } | 303 } |
301 } | 304 } |
302 | 305 |
303 Frame(this.uri, this.line, this.column, this.member); | 306 Frame(this.uri, this.line, this.column, this.member); |
304 | 307 |
305 String toString() => '$location in $member'; | 308 String toString() => '$location in $member'; |
306 } | 309 } |
OLD | NEW |