|
| 1 | +// Copyright (c) 2018, 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:io'; |
| 6 | +import 'package:source_maps/source_maps.dart'; |
| 7 | +import 'package:source_maps/src/utils.dart'; |
| 8 | +import 'package:dart2js_tools/src/trace.dart'; |
| 9 | +import 'package:dart2js_tools/src/sourcemap_helper.dart'; |
| 10 | +import 'package:dart2js_tools/src/name_decoder.dart'; |
| 11 | +import 'package:dart2js_tools/src/dart2js_mapping.dart'; |
| 12 | +import 'package:dart2js_tools/src/util.dart'; |
| 13 | + |
| 14 | +/// Script that deobuscates a stack-trace given in a text file. |
| 15 | +/// |
| 16 | +/// To run this script you need 3 or more files: |
| 17 | +/// |
| 18 | +/// * A stacktrace file |
| 19 | +/// * The deployed .js file |
| 20 | +/// * The corresponding .map file |
| 21 | +/// |
| 22 | +/// There might be more than one .js/.map file if your app is divided in |
| 23 | +/// deferred chunks. |
| 24 | +/// |
| 25 | +/// The stack trace file contains a copy/paste of a JavaScript stack trace, of |
| 26 | +/// this form: |
| 27 | +/// |
| 28 | +/// at aB.a20 (main.dart.js:71969:32) |
| 29 | +/// at aNk.goV (main.dart.js:72040:52) |
| 30 | +/// at aNk.gfK (main.dart.js:72038:27) |
| 31 | +/// at FE.gtn (main.dart.js:72640:24) |
| 32 | +/// at aBZ.ghN (main.dart.js:72642:24) |
| 33 | +/// at inheritance (main.dart.js:105334:0) |
| 34 | +/// at FE (main.dart.js:5037:18) |
| 35 | +/// |
| 36 | +/// If you download the stacktrace from a production service, you can keep the |
| 37 | +/// full URL (including http://....) and this script will simply try to match |
| 38 | +/// the name of the file at the end with a file in the current working |
| 39 | +/// directory. |
| 40 | +/// |
| 41 | +/// The .js file must contain a `//# sourceMappingURL=` line at the end, which |
| 42 | +/// tells this script how to determine the name of the source-map file. |
| 43 | +main(List<String> args) { |
| 44 | + if (args.length != 1) { |
| 45 | + print('usage: deobfuscate.dart <stack-trace-file>'); |
| 46 | + exit(1); |
| 47 | + } |
| 48 | + var sb = new StringBuffer(); |
| 49 | + try { |
| 50 | + deobfuscate(new File(args[0]).readAsStringSync(), sb); |
| 51 | + } finally { |
| 52 | + print('$sb'); |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +void deobfuscate(trace, StringBuffer sb) { |
| 57 | + String error = extractErrorMessage(trace); |
| 58 | + String translatedError; |
| 59 | + var provider = new CachingFileProvider(); |
| 60 | + |
| 61 | + List<StackTraceLine> jsStackTrace = parseStackTrace(trace); |
| 62 | + |
| 63 | + for (StackTraceLine line in jsStackTrace) { |
| 64 | + var uri = resolveUri(line.fileName); |
| 65 | + var mapping = provider.mappingFor(uri); |
| 66 | + if (mapping == null) { |
| 67 | + printPadded('no mapping', line.inlineString, sb); |
| 68 | + continue; |
| 69 | + } |
| 70 | + |
| 71 | + TargetEntry targetEntry = findColumn(line.lineNo - 1, line.columnNo - 1, |
| 72 | + findLine(mapping.sourceMap, line.lineNo - 1)); |
| 73 | + if (targetEntry == null) { |
| 74 | + printPadded('no entry', line.inlineString, sb); |
| 75 | + continue; |
| 76 | + } |
| 77 | + |
| 78 | + if (translatedError == null) { |
| 79 | + translatedError = translate(error, mapping, line, targetEntry); |
| 80 | + if (translatedError == null) translatedError = '<no error message found>'; |
| 81 | + printPadded(translatedError, error, sb); |
| 82 | + } |
| 83 | + |
| 84 | + int offset = |
| 85 | + provider.fileFor(uri).getOffset(line.lineNo - 1, line.columnNo - 1); |
| 86 | + |
| 87 | + String nameOf(id) => id != 0 ? mapping.sourceMap.names[id] : null; |
| 88 | + String urlOf(id) => id != 0 ? mapping.sourceMap.urls[id] : null; |
| 89 | + |
| 90 | + String fileName = urlOf(targetEntry.sourceUrlId ?? 0); |
| 91 | + int targetLine = (targetEntry.sourceLine ?? 0) + 1; |
| 92 | + int targetColumn = (targetEntry.sourceColumn ?? 0) + 1; |
| 93 | + |
| 94 | + // Expand inlined frames. |
| 95 | + Map<int, List<FrameEntry>> frames = mapping.frames; |
| 96 | + List<int> index = mapping.frameIndex; |
| 97 | + int key = binarySearch(index, (i) => i > offset) - 1; |
| 98 | + int depth = 0; |
| 99 | + outer: |
| 100 | + while (key >= 0) { |
| 101 | + for (var frame in frames[index[key]].reversed) { |
| 102 | + if (frame.isEmpty) break outer; |
| 103 | + if (frame.isPush) { |
| 104 | + if (depth <= 0) { |
| 105 | + var mappedLine = new StackTraceLine( |
| 106 | + frame.inlinedMethodName + "(inlined)", |
| 107 | + fileName, |
| 108 | + targetLine, |
| 109 | + targetColumn); |
| 110 | + printPadded(mappedLine.inlineString, "", sb); |
| 111 | + fileName = frame.callUri; |
| 112 | + targetLine = (frame.callLine ?? 0) + 1; |
| 113 | + targetColumn = (frame.callColumn ?? 0) + 1; |
| 114 | + } else { |
| 115 | + depth--; |
| 116 | + } |
| 117 | + } |
| 118 | + if (frame.isPop) { |
| 119 | + depth++; |
| 120 | + } |
| 121 | + } |
| 122 | + key--; |
| 123 | + } |
| 124 | + |
| 125 | + var functionEntry = findEnclosingFunction(provider, uri, offset); |
| 126 | + String methodName = nameOf(functionEntry.sourceNameId ?? 0); |
| 127 | + var mappedLine = |
| 128 | + new StackTraceLine(methodName, fileName, targetLine, targetColumn); |
| 129 | + printPadded(mappedLine.inlineString, line.inlineString, sb); |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +final green = stdout.hasTerminal ? '[32m' : ''; |
| 134 | +final none = stdout.hasTerminal ? '[0m' : ''; |
| 135 | + |
| 136 | +printPadded(String mapping, String original, sb) { |
| 137 | + var len = mapping.length; |
| 138 | + var s = mapping.indexOf('\n'); |
| 139 | + if (s >= 0) len -= s + 1; |
| 140 | + var pad = ' ' * (50 - len); |
| 141 | + sb.writeln('$green$mapping$none$pad ... $original'); |
| 142 | +} |
| 143 | + |
| 144 | +Uri resolveUri(String filename) { |
| 145 | + var uri = Uri.base.resolve(filename); |
| 146 | + if (uri.scheme == 'http' || uri.scheme == 'https') { |
| 147 | + filename = uri.path.substring(uri.path.lastIndexOf('/') + 1); |
| 148 | + uri = Uri.base.resolve(filename); |
| 149 | + } |
| 150 | + return uri; |
| 151 | +} |
0 commit comments