OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 part of dart2js.helpers; | |
6 | |
7 /// Function signature for [trace]. | |
8 typedef void Trace(String message, | |
9 {bool condition(String stackTrace), | |
10 int limit, | |
11 bool throwOnPrint}); | |
12 | |
13 /** | |
14 * Helper method for printing stack traces for debugging. | |
15 * | |
16 * [message] is printed as the header of the stack trace. | |
17 * | |
18 * If [condition] is provided, the stack trace is only printed if [condition] | |
19 * returns [:true:] on the stack trace text. This can be used to filter the | |
20 * printed stack traces based on their content. For instance only print stack | |
21 * traces that contain specific paths. | |
22 * | |
23 * If [limit] is provided, the stack trace is limited to [limit] entries. | |
24 * | |
25 * If [throwOnPrint] is `true`, [message] will be thrown after the stack trace | |
26 * has been printed. Together with [condition] this can be used to discover | |
27 * unknown call-sites in tests by filtering known call-sites and throwning | |
28 * otherwise. | |
29 */ | |
30 Trace get trace { | |
31 enableDebugMode(); | |
32 return _trace; | |
33 } | |
34 | |
35 void _trace(String message, {bool condition(String stackTrace), int limit, | |
36 bool throwOnPrint: false}) { | |
37 try { | |
38 throw ''; | |
39 } catch (e, s) { | |
40 String stackTrace; | |
41 try { | |
42 stackTrace = prettifyStackTrace( | |
43 s, rangeStart: 1, rangeEnd: limit, filePrefix: stackTraceFilePrefix); | |
44 } catch (e) { | |
45 print(e); | |
46 stackTrace = '$s'; | |
47 } | |
48 if (condition != null) { | |
49 if (!condition(stackTrace)) return; | |
50 } | |
51 print('$message\n$stackTrace'); | |
52 if (throwOnPrint) throw message; | |
53 } | |
54 } | |
55 | |
56 /// Function signature of [traceAndReport]. | |
57 typedef void TraceAndReport(Compiler compiler, Spannable node, String message, | |
58 {bool condition(String stackTrace), int limit, | |
59 bool throwOnPrint}); | |
60 | |
61 /// Calls [reportHere] and [trace] with the same message. | |
62 TraceAndReport get traceAndReport { | |
63 enableDebugMode(); | |
64 return _traceAndReport; | |
65 } | |
66 | |
67 /// Calls [reportHere] and [trace] with the same message. | |
68 TraceAndReport get reportAndTrace => traceAndReport; | |
69 | |
70 /// Implementation of [traceAndReport]. | |
71 void _traceAndReport(Compiler compiler, Spannable node, String message, | |
72 {bool condition(String stackTrace), int limit, | |
73 bool throwOnPrint: false}) { | |
74 | |
75 trace(message, limit: limit, throwOnPrint: throwOnPrint, | |
76 condition: (String stackTrace) { | |
77 bool result = condition != null ? condition(stackTrace) : true; | |
78 if (result) { | |
79 reportHere(compiler, node, message); | |
80 } | |
81 return result; | |
82 }); | |
83 } | |
84 | |
85 /// Returns the [StackTraceLines] for the current call stack. | |
86 /// | |
87 /// Use [offset] to discard the first [offset] calls of the call stack. Defaults | |
88 /// to `1`, that is, discard the call to [stackTrace] itself. Use [limit] to | |
89 /// limit the length of the stack trace lines. | |
90 StackTraceLines stackTrace({int offset: 1, | |
91 int limit: null}) { | |
92 int rangeStart = offset; | |
93 int rangeEnd = limit == null ? null : rangeStart + limit; | |
94 try { | |
95 throw ''; | |
96 } catch (_, stackTrace) { | |
97 return new StackTraceLines.fromTrace(stackTrace, | |
98 rangeStart: offset, rangeEnd: rangeEnd, | |
99 filePrefix: stackTraceFilePrefix); | |
100 } | |
101 return null; | |
102 } | |
103 | |
104 /// A stack trace as a sequence of [StackTraceLine]s. | |
105 class StackTraceLines { | |
106 final List<StackTraceLine> lines; | |
107 final int maxFileLength; | |
108 final int maxLineNoLength; | |
109 final int maxColumnNoLength; | |
110 | |
111 factory StackTraceLines.fromTrace(StackTrace s, | |
112 {int rangeStart, | |
113 int rangeEnd, | |
114 String filePrefix, | |
115 String lambda: r'?'}) { | |
116 final RegExp indexPattern = new RegExp(r'#\d+\s*'); | |
117 int index = -1; | |
118 int maxFileLength = 0; | |
119 int maxLineNoLength = 0; | |
120 int maxColumnNoLength = 0; | |
121 | |
122 String stackTrace = '$s'; | |
123 List<StackTraceLine> lines = <StackTraceLine>[]; | |
124 // Parse each line in the stack trace. The supported line formats from the | |
125 // Dart VM are: | |
126 // #n <method-name> (<uri>:<line-no>:<column-no>) | |
127 // #n <method-name> (<uri>:<line-no>) | |
128 // in which '<anonymous closure>' is the name used for an (unnamed) function | |
129 // expression. | |
130 for (String line in stackTrace.split('\n')) { | |
131 try { | |
132 index++; | |
133 if (rangeStart != null && index < rangeStart) continue; | |
134 if (rangeEnd != null && index > rangeEnd) break; | |
135 if (line.isEmpty) continue; | |
136 | |
137 // Strip index. | |
138 line = line.replaceFirst(indexPattern, ''); | |
139 | |
140 int leftParenPos = line.indexOf('('); | |
141 int rightParenPos = line.indexOf(')', leftParenPos); | |
142 int lastColon = line.lastIndexOf(':', rightParenPos); | |
143 int nextToLastColon = line.lastIndexOf(':', lastColon-1); | |
144 | |
145 String lineNo; | |
146 String columnNo; | |
147 if (nextToLastColon != -1) { | |
148 lineNo = line.substring(nextToLastColon+1, lastColon); | |
149 columnNo = line.substring(lastColon+1, rightParenPos); | |
150 try { | |
151 int.parse(lineNo); | |
152 } on FormatException catch (e) { | |
153 lineNo = columnNo; | |
154 columnNo = ''; | |
155 nextToLastColon = lastColon; | |
156 } | |
157 } else { | |
158 lineNo = line.substring(lastColon+1, rightParenPos); | |
159 columnNo = ''; | |
160 nextToLastColon = lastColon; | |
161 } | |
162 | |
163 if (lineNo.length > maxLineNoLength) { | |
164 maxLineNoLength = lineNo.length; | |
165 } | |
166 if (columnNo.length > maxColumnNoLength) { | |
167 maxColumnNoLength = columnNo.length; | |
168 } | |
169 | |
170 String file = line.substring(leftParenPos+1, nextToLastColon); | |
171 if (filePrefix != null && file.startsWith(filePrefix)) { | |
172 file = file.substring(filePrefix.length); | |
173 } | |
174 if (file.length > maxFileLength) { | |
175 maxFileLength = file.length; | |
176 } | |
177 String method = line.substring(0, leftParenPos-1); | |
178 if (lambda != null) { | |
179 method = method.replaceAll('<anonymous closure>', lambda); | |
180 } | |
181 lines.add(new StackTraceLine(index, file, lineNo, columnNo, method)); | |
182 } catch (e) { | |
183 throw 'Error prettifying "$line": $e'; | |
184 } | |
185 } | |
186 return new StackTraceLines.fromLines( | |
187 lines, maxFileLength, maxLineNoLength, maxColumnNoLength); | |
188 } | |
189 | |
190 StackTraceLines.fromLines(this.lines, | |
191 this.maxFileLength, | |
192 this.maxLineNoLength, | |
193 this.maxColumnNoLength); | |
194 | |
195 StackTraceLines subtrace(int offset) { | |
196 return new StackTraceLines.fromLines( | |
197 lines.sublist(offset), | |
198 maxFileLength, | |
199 maxLineNoLength, | |
200 maxColumnNoLength); | |
201 } | |
202 | |
203 String prettify({bool showColumnNo: false, | |
204 bool showDots: true}) { | |
205 StringBuffer sb = new StringBuffer(); | |
206 bool dots = true; | |
207 for (StackTraceLine line in lines) { | |
208 sb.write(' '); | |
209 line.printOn(sb, | |
210 fileLength: maxFileLength, | |
211 padding: showDots && dots ? ' .' : ' ', | |
212 lineNoLength: maxLineNoLength, | |
213 showColumnNo: showColumnNo, | |
214 columnNoLength: maxColumnNoLength); | |
215 | |
216 dots = !dots; | |
217 } | |
218 return sb.toString(); | |
219 } | |
220 | |
221 String toString() { | |
222 return prettify(); | |
223 } | |
224 } | |
225 | |
226 /// A parsed line from a stack trace. | |
227 class StackTraceLine { | |
228 final int index; | |
229 final String file; | |
230 final String lineNo; | |
231 final String columnNo; | |
232 final String method; | |
233 | |
234 StackTraceLine(this.index, this.file, this.lineNo, | |
235 this.columnNo, this.method); | |
236 | |
237 void printOn(StringBuffer sb, | |
238 {String padding: ' ', | |
239 int fileLength, | |
240 int lineNoLength, | |
241 int columnNoLength, | |
242 bool showColumnNo: false}) { | |
243 String fileText = '${file} '; | |
244 if (fileLength != null) { | |
245 fileText = pad(fileText, fileLength, dots: padding); | |
246 } | |
247 String lineNoText = lineNo; | |
248 if (lineNoLength != null) { | |
249 lineNoText = pad(lineNoText, lineNoLength, padLeft: true); | |
250 } | |
251 String columnNoText = showColumnNo ? '': columnNo; | |
252 if (columnNoLength != null) { | |
253 columnNoText = ':${pad(columnNoText, columnNoLength)}'; | |
254 } | |
255 sb.write('$fileText $lineNoText$columnNoText $method\n'); | |
256 } | |
257 | |
258 int get hashCode { | |
259 return 13 * index + | |
260 17 * file.hashCode + | |
261 19 * lineNo.hashCode + | |
262 23 * columnNo.hashCode + | |
263 29 * method.hashCode; | |
264 } | |
265 | |
266 bool operator ==(other) { | |
267 if (identical(this, other)) return true; | |
268 if (other is! StackTraceLine) return false; | |
269 return index == other.index && | |
270 file == other.file && | |
271 lineNo == other.lineNo && | |
272 columnNo == other.columnNo && | |
273 method == other.method; | |
274 } | |
275 | |
276 String toString() => "$method @ $file [$lineNo:$columnNo]"; | |
277 } | |
278 | |
279 // TODO(johnniwinther): Use this format for --throw-on-error. | |
280 /** | |
281 * Converts the normal VM stack trace into a more compact and readable format. | |
282 * | |
283 * The output format is [: <file> . . . <lineNo>:<columnNo> <method> :] where | |
284 * [: <file> :] is file name, [: <lineNo> :] is the line number, | |
285 * [: <columnNo> :] is the column number, and [: <method> :] is the method name. | |
286 * | |
287 * If [rangeStart] and/or [rangeEnd] are provided, only the lines within the | |
288 * range are included. | |
289 * If [showColumnNo] is [:false:], the [: :<columnNo> :] part is omitted. | |
290 * If [showDots] is [:true:], the space between [: <file> :] and [: <lineNo> :] | |
291 * is padded with dots on every other line. | |
292 * If [filePrefix] is provided, then for every file name thats starts with | |
293 * [filePrefix] only the remainder is printed. | |
294 * If [lambda] is non-null, anonymous closures are printed as [lambda]. | |
295 */ | |
296 String prettifyStackTrace(StackTrace stackTrace, | |
297 {int rangeStart, | |
298 int rangeEnd, | |
299 bool showColumnNo: false, | |
300 bool showDots: true, | |
301 String filePrefix, | |
302 String lambda: r'?'}) { | |
303 return new StackTraceLines.fromTrace(stackTrace, | |
304 rangeStart: rangeStart, rangeEnd: rangeEnd, | |
305 filePrefix: filePrefix, lambda: lambda) | |
306 .prettify(showColumnNo: showColumnNo, showDots: showDots); | |
307 } | |
308 | |
309 /** | |
310 * Pads (or truncates) [text] to the [intendedLength]. | |
311 * | |
312 * If [padLeft] is [:true:] the text is padding inserted to the left of [text]. | |
313 * A repetition of the [dots] text is used for padding. | |
314 */ | |
315 String pad(String text, int intendedLength, | |
316 {bool padLeft: false, String dots: ' '}) { | |
317 if (text.length == intendedLength) return text; | |
318 if (text.length > intendedLength) return text.substring(0, intendedLength); | |
319 if (dots == null || dots.isEmpty) dots = ' '; | |
320 int dotsLength = dots.length; | |
321 StringBuffer sb = new StringBuffer(); | |
322 if (!padLeft) { | |
323 sb.write(text); | |
324 } | |
325 for (int index = text.length ; index < intendedLength ; index ++) { | |
326 int dotsIndex = index % dotsLength; | |
327 sb.write(dots.substring(dotsIndex, dotsIndex + 1)); | |
328 } | |
329 if (padLeft) { | |
330 sb.write(text); | |
331 } | |
332 return sb.toString(); | |
333 } | |
OLD | NEW |