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