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