| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2016, 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 'dart:async'; |
| 6 import 'dart:convert'; |
| 7 import 'dart:io'; |
| 8 |
| 9 import 'package:async_helper/async_helper.dart'; |
| 10 import 'package:compiler/compiler_new.dart'; |
| 11 import 'package:compiler/src/apiimpl.dart'; |
| 12 import 'package:compiler/src/commandline_options.dart'; |
| 13 import 'package:compiler/src/dart2js.dart' as entry; |
| 14 import 'package:expect/expect.dart'; |
| 15 import 'package:source_maps/source_maps.dart'; |
| 16 import 'package:source_maps/src/utils.dart'; |
| 17 |
| 18 import '../source_map_validator_helper.dart'; |
| 19 |
| 20 const String EXCEPTION_MARKER = '>ExceptionMarker<'; |
| 21 const String INPUT_FILE_NAME = 'in.dart'; |
| 22 |
| 23 const List<String> TESTS = const <String>[ |
| 24 ''' |
| 25 main() { |
| 26 @{1:main}throw '$EXCEPTION_MARKER'; |
| 27 } |
| 28 ''', |
| 29 ''' |
| 30 main() { |
| 31 @{1:main}test(); |
| 32 } |
| 33 test() { |
| 34 @{2:test}throw '$EXCEPTION_MARKER'; |
| 35 } |
| 36 ''', |
| 37 ''' |
| 38 main() { |
| 39 @{1:main}Class.test(); |
| 40 } |
| 41 class Class { |
| 42 static test() { |
| 43 @{2:Class.test}throw '$EXCEPTION_MARKER'; |
| 44 } |
| 45 } |
| 46 ''', |
| 47 ''' |
| 48 main() { |
| 49 var c = new Class(); |
| 50 c.@{1:main}test(); |
| 51 } |
| 52 class Class { |
| 53 test() { |
| 54 @{2:Class.test}throw '$EXCEPTION_MARKER'; |
| 55 } |
| 56 } |
| 57 ''', |
| 58 ''' |
| 59 import 'package:expect/expect.dart'; |
| 60 main() { |
| 61 var c = @{1:main}new Class(); |
| 62 } |
| 63 class Class { |
| 64 @NoInline() |
| 65 Class() { |
| 66 @{2:Class}throw '$EXCEPTION_MARKER'; |
| 67 } |
| 68 } |
| 69 ''', |
| 70 ]; |
| 71 |
| 72 class Test { |
| 73 final String code; |
| 74 final List<StackTraceLine> expectedLines; |
| 75 |
| 76 Test(this.code, this.expectedLines); |
| 77 } |
| 78 |
| 79 const int _LF = 0x0A; |
| 80 const int _CR = 0x0D; |
| 81 const int _LBRACE = 0x7B; |
| 82 |
| 83 |
| 84 Test processTestCode(String code) { |
| 85 StringBuffer codeBuffer = new StringBuffer(); |
| 86 Map<int, StackTraceLine> stackTraceMap = <int, StackTraceLine>{}; |
| 87 int index = 0; |
| 88 int lineNo = 1; |
| 89 int columnNo = 1; |
| 90 while (index < code.length) { |
| 91 int charCode = code.codeUnitAt(index); |
| 92 switch (charCode) { |
| 93 case _LF: |
| 94 codeBuffer.write('\n'); |
| 95 lineNo++; |
| 96 columnNo = 1; |
| 97 break; |
| 98 case _CR: |
| 99 if (index + 1 < code.length && code.codeUnitAt(index + 1) == _LF) { |
| 100 index++; |
| 101 } |
| 102 codeBuffer.write('\n'); |
| 103 lineNo++; |
| 104 columnNo = 1; |
| 105 break; |
| 106 case 0x40: |
| 107 if (index + 1 < code.length && code.codeUnitAt(index + 1) == _LBRACE) { |
| 108 int colonIndex = code.indexOf(':', index); |
| 109 int endIndex = code.indexOf('}', index); |
| 110 int stackTraceIndex = |
| 111 int.parse(code.substring(index + 2, colonIndex)); |
| 112 String methodName = code.substring(colonIndex + 1, endIndex); |
| 113 assert(!stackTraceMap.containsKey(stackTraceIndex)); |
| 114 stackTraceMap[stackTraceIndex] = |
| 115 new StackTraceLine(methodName, INPUT_FILE_NAME, lineNo, columnNo); |
| 116 index = endIndex; |
| 117 } else { |
| 118 codeBuffer.writeCharCode(charCode); |
| 119 columnNo++; |
| 120 } |
| 121 break; |
| 122 default: |
| 123 codeBuffer.writeCharCode(charCode); |
| 124 columnNo++; |
| 125 } |
| 126 index++; |
| 127 } |
| 128 List<StackTraceLine> expectedLines = <StackTraceLine>[]; |
| 129 for (int stackTraceIndex in (stackTraceMap.keys.toList()..sort()).reversed) { |
| 130 expectedLines.add(stackTraceMap[stackTraceIndex]); |
| 131 } |
| 132 return new Test(codeBuffer.toString(), expectedLines); |
| 133 } |
| 134 |
| 135 void main(List<String> arguments) { |
| 136 asyncTest(() async { |
| 137 for (String code in TESTS) { |
| 138 await runTest(processTestCode(code)); |
| 139 } |
| 140 }); |
| 141 } |
| 142 |
| 143 Future runTest(Test test) async { |
| 144 Directory tmpDir = await createTempDir(); |
| 145 String input = '${tmpDir.path}/$INPUT_FILE_NAME'; |
| 146 new File(input).writeAsStringSync(test.code); |
| 147 String output = '${tmpDir.path}/out.js'; |
| 148 List<String> arguments = [ |
| 149 '-o$output', |
| 150 '--library-root=sdk', |
| 151 '--packages=${Platform.packageConfig}', |
| 152 Flags.useNewSourceInfo, |
| 153 input, |
| 154 ]; |
| 155 print("--------------------------------------------------------------------"); |
| 156 print("Compiling dart2js ${arguments.join(' ')}\n${test.code}"); |
| 157 CompilationResult compilationResult = await entry.internalMain(arguments); |
| 158 Expect.isTrue(compilationResult.isSuccess, |
| 159 "Unsuccessful compilation of test:\n${test.code}"); |
| 160 CompilerImpl compiler = compilationResult.compiler; |
| 161 SingleMapping sourceMap = new SingleMapping.fromJson( |
| 162 JSON.decode(new File('$output.map').readAsStringSync())); |
| 163 |
| 164 print("Running d8 $output"); |
| 165 ProcessResult runResult = |
| 166 Process.runSync(d8executable, [output]); |
| 167 String out = '${runResult.stderr}\n${runResult.stdout}'; |
| 168 List<String> lines = out.split(new RegExp(r'(\r|\n|\r\n)')); |
| 169 List<StackTraceLine> jsStackTrace = <StackTraceLine>[]; |
| 170 bool seenMarker = false; |
| 171 for (String line in lines) { |
| 172 if (seenMarker) { |
| 173 line = line.trim(); |
| 174 if (line.startsWith('at ')) { |
| 175 jsStackTrace.add(new StackTraceLine.fromText(line)); |
| 176 } |
| 177 } else if (line == EXCEPTION_MARKER) { |
| 178 seenMarker = true; |
| 179 } |
| 180 } |
| 181 |
| 182 List<StackTraceLine> dartStackTrace = <StackTraceLine>[]; |
| 183 for (StackTraceLine line in jsStackTrace) { |
| 184 TargetEntry targetEntry = _findColumn(line.lineNo - 1, line.columnNo - 1, |
| 185 _findLine(sourceMap, line.lineNo - 1)); |
| 186 if (targetEntry == null) { |
| 187 dartStackTrace.add(line); |
| 188 } else { |
| 189 String methodName; |
| 190 if (targetEntry.sourceNameId != 0) { |
| 191 methodName = sourceMap.names[targetEntry.sourceNameId]; |
| 192 } |
| 193 String fileName; |
| 194 if (targetEntry.sourceUrlId != 0) { |
| 195 fileName = sourceMap.urls[targetEntry.sourceUrlId]; |
| 196 } |
| 197 dartStackTrace.add(new StackTraceLine(methodName, fileName, |
| 198 targetEntry.sourceLine + 1, targetEntry.sourceColumn + 1)); |
| 199 } |
| 200 } |
| 201 |
| 202 int expectedIndex = 0; |
| 203 for (StackTraceLine line in dartStackTrace) { |
| 204 if (expectedIndex < test.expectedLines.length) { |
| 205 StackTraceLine expectedLine = test.expectedLines[expectedIndex]; |
| 206 if (line.methodName == expectedLine.methodName && |
| 207 line.lineNo == expectedLine.lineNo && |
| 208 line.columnNo == expectedLine.columnNo) { |
| 209 expectedIndex++; |
| 210 } |
| 211 } |
| 212 } |
| 213 Expect.equals( |
| 214 expectedIndex, |
| 215 test.expectedLines.length, |
| 216 "Missing stack trace lines for test:\n${test.code}\n" |
| 217 "Actual:\n${dartStackTrace.join('\n')}\n" |
| 218 "Expected:\n${test.expectedLines.join('\n')}\n"); |
| 219 |
| 220 print("Deleting '${tmpDir.path}'."); |
| 221 tmpDir.deleteSync(recursive: true); |
| 222 } |
| 223 |
| 224 class StackTraceLine { |
| 225 String methodName; |
| 226 String fileName; |
| 227 int lineNo; |
| 228 int columnNo; |
| 229 |
| 230 StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo); |
| 231 |
| 232 /// Creates a [StackTraceLine] by parsing a d8 stack trace line [text]. The |
| 233 /// expected formats are |
| 234 /// |
| 235 /// at <methodName>(<fileName>:<lineNo>:<columnNo>) |
| 236 /// at <methodName>(<fileName>:<lineNo>) |
| 237 /// at <methodName>(<fileName>) |
| 238 /// at <fileName>:<lineNo>:<columnNo> |
| 239 /// at <fileName>:<lineNo> |
| 240 /// at <fileName> |
| 241 /// |
| 242 factory StackTraceLine.fromText(String text) { |
| 243 text = text.trim(); |
| 244 assert(text.startsWith('at ')); |
| 245 text = text.substring('at '.length); |
| 246 String methodName; |
| 247 if (text.endsWith(')')) { |
| 248 int nameEnd = text.indexOf(' ('); |
| 249 methodName = text.substring(0, nameEnd); |
| 250 text = text.substring(nameEnd + 2, text.length - 1); |
| 251 } |
| 252 int lineNo; |
| 253 int columnNo; |
| 254 String fileName; |
| 255 int lastColon = text.lastIndexOf(':'); |
| 256 if (lastColon != -1) { |
| 257 int lastValue = |
| 258 int.parse(text.substring(lastColon + 1), onError: (_) => null); |
| 259 if (lastValue != null) { |
| 260 int secondToLastColon = text.lastIndexOf(':', lastColon - 1); |
| 261 if (secondToLastColon != -1) { |
| 262 int secondToLastValue = int.parse( |
| 263 text.substring(secondToLastColon + 1, lastColon), |
| 264 onError: (_) => null); |
| 265 if (secondToLastValue != null) { |
| 266 lineNo = secondToLastValue; |
| 267 columnNo = lastValue; |
| 268 fileName = text.substring(0, secondToLastColon); |
| 269 } else { |
| 270 lineNo = lastValue; |
| 271 fileName = text.substring(0, lastColon); |
| 272 } |
| 273 } else { |
| 274 lineNo = lastValue; |
| 275 fileName = text.substring(0, lastColon); |
| 276 } |
| 277 } else { |
| 278 fileName = text; |
| 279 } |
| 280 } else { |
| 281 fileName = text; |
| 282 } |
| 283 return new StackTraceLine(methodName, fileName, lineNo, columnNo); |
| 284 } |
| 285 |
| 286 String toString() { |
| 287 StringBuffer sb = new StringBuffer(); |
| 288 sb.write(' at '); |
| 289 if (methodName != null) { |
| 290 sb.write(methodName); |
| 291 sb.write(' ('); |
| 292 sb.write(fileName ?? '?'); |
| 293 sb.write(':'); |
| 294 sb.write(lineNo); |
| 295 sb.write(':'); |
| 296 sb.write(columnNo); |
| 297 sb.write(')'); |
| 298 } else { |
| 299 sb.write(fileName ?? '?'); |
| 300 sb.write(':'); |
| 301 sb.write(lineNo); |
| 302 sb.write(':'); |
| 303 sb.write(columnNo); |
| 304 } |
| 305 return sb.toString(); |
| 306 } |
| 307 } |
| 308 |
| 309 /// Returns [TargetLineEntry] which includes the location in the target [line] |
| 310 /// number. In particular, the resulting entry is the last entry whose line |
| 311 /// number is lower or equal to [line]. |
| 312 /// |
| 313 /// Copied from [SingleMapping._findLine]. |
| 314 TargetLineEntry _findLine(SingleMapping sourceMap, int line) { |
| 315 int index = binarySearch(sourceMap.lines, (e) => e.line > line); |
| 316 return (index <= 0) ? null : sourceMap.lines[index - 1]; |
| 317 } |
| 318 |
| 319 /// Returns [TargetEntry] which includes the location denoted by |
| 320 /// [line], [column]. If [lineEntry] corresponds to [line], then this will be |
| 321 /// the last entry whose column is lower or equal than [column]. If |
| 322 /// [lineEntry] corresponds to a line prior to [line], then the result will be |
| 323 /// the very last entry on that line. |
| 324 /// |
| 325 /// Copied from [SingleMapping._findColumn]. |
| 326 TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) { |
| 327 if (lineEntry == null || lineEntry.entries.length == 0) return null; |
| 328 if (lineEntry.line != line) return lineEntry.entries.last; |
| 329 var entries = lineEntry.entries; |
| 330 int index = binarySearch(entries, (e) => e.column > column); |
| 331 return (index <= 0) ? null : entries[index - 1]; |
| 332 } |
| 333 |
| 334 /// Returns the path of the d8 executable. |
| 335 String get d8executable { |
| 336 if (Platform.isWindows) { |
| 337 return 'third_party/d8/windows/d8.exe'; |
| 338 } else if (Platform.isLinux) { |
| 339 return 'third_party/d8/linux/d8'; |
| 340 } else if (Platform.isMacOS) { |
| 341 return 'third_party/d8/macos/d8'; |
| 342 } |
| 343 throw new UnsupportedError('Unsupported platform.'); |
| 344 } |
| OLD | NEW |