| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:io'; | |
| import 'dart:math' show max; | |
| import 'package:dart2js_tools/src/name_decoder.dart'; | |
| import 'package:dart2js_tools/src/trace.dart'; | |
| import 'package:dart2js_tools/src/trace_decoder.dart'; | |
| import 'package:dart2js_tools/src/util.dart'; | |
| import 'package:stack_trace/stack_trace.dart'; | |
| /// Script that deobfuscates a stack-trace given in a text file. | |
| /// | |
| /// To run this script you need 3 or more files: | |
| /// | |
| /// * A stacktrace file | |
| /// * The deployed .js file | |
| /// * The corresponding .map file | |
| /// | |
| /// There might be more than one .js/.map file if your app is divided in | |
| /// deferred chunks. | |
| /// | |
| /// The stack trace file contains a copy/paste of a JavaScript stack trace, of | |
| /// this form: | |
| /// | |
| /// at aB.a20 (main.dart.js:71969:32) | |
| /// at aNk.goV (main.dart.js:72040:52) | |
| /// at aNk.gfK (main.dart.js:72038:27) | |
| /// at FE.gtn (main.dart.js:72640:24) | |
| /// at aBZ.ghN (main.dart.js:72642:24) | |
| /// at inheritance (main.dart.js:105334:0) | |
| /// at FE (main.dart.js:5037:18) | |
| /// | |
| /// If you download the stacktrace from a production service, you can keep the | |
| /// full URL (including http://....) and this script will simply try to match | |
| /// the name of the file at the end with a file in the current working | |
| /// directory. | |
| /// | |
| /// The .js file must contain a `//# sourceMappingURL=` line at the end, which | |
| /// tells this script how to determine the name of the source-map file. | |
| void main(List<String> args) { | |
| if (args.length != 1) { | |
| print('usage: deobfuscate.dart <stack-trace-file>'); | |
| exit(1); | |
| } | |
| var sb = StringBuffer(); | |
| try { | |
| String obfuscatedTrace = File(args[0]).readAsStringSync(); | |
| String? error = extractErrorMessage(obfuscatedTrace); | |
| var provider = CachingFileProvider(logger: Logger()); | |
| StackDeobfuscationResult result = deobfuscateStack( | |
| obfuscatedTrace, | |
| provider, | |
| ); | |
| Frame firstFrame = result.original.frames.first; | |
| String? translatedError = translate( | |
| error, | |
| provider.mappingFor(firstFrame.uri), | |
| ); | |
| translatedError ??= '<no error message found>'; | |
| printPadded(translatedError, error, sb); | |
| int longest = result.deobfuscated.frames.fold( | |
| 0, | |
| (m, f) => max(f.member!.length, m), | |
| ); | |
| for (var originalFrame in result.original.frames) { | |
| var deobfuscatedFrames = result.frameMap[originalFrame]; | |
| if (deobfuscatedFrames == null) { | |
| printPadded('no mapping', originalFrame.location, sb); | |
| } else { | |
| for (var frame in deobfuscatedFrames) { | |
| printPadded( | |
| '${frame.member!.padRight(longest)} ${frame.location}', | |
| originalFrame.location, | |
| sb, | |
| ); | |
| } | |
| } | |
| } | |
| } finally { | |
| print('$sb'); | |
| } | |
| } | |
| final green = stdout.hasTerminal ? '\x1b[32m' : ''; | |
| final none = stdout.hasTerminal ? '\x1b[0m' : ''; | |
| void printPadded(String mapping, String? original, sb) { | |
| var len = mapping.length; | |
| var s = mapping.indexOf('\n'); | |
| if (s >= 0) len -= s + 1; | |
| var pad = ' ' * (50 - len); | |
| sb.writeln('$green$mapping$none$pad ... $original'); | |
| } |