| 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 |