| // 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:stack_trace/stack_trace.dart'; |
| import 'package:dart2js_tools/src/trace.dart'; |
| import 'package:dart2js_tools/src/name_decoder.dart'; |
| import 'package:dart2js_tools/src/util.dart'; |
| import 'package:dart2js_tools/src/trace_decoder.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. |
| main(List<String> args) { |
| if (args.length != 1) { |
| print('usage: deobfuscate.dart <stack-trace-file>'); |
| exit(1); |
| } |
| var sb = new StringBuffer(); |
| try { |
| String obfuscatedTrace = new File(args[0]).readAsStringSync(); |
| String? error = extractErrorMessage(obfuscatedTrace); |
| var provider = new CachingFileProvider(logger: Logger()); |
| StackDeobfuscationResult result = |
| deobfuscateStack(obfuscatedTrace, provider); |
| Frame firstFrame = result.original.frames.first; |
| String? translatedError = |
| translate(error, provider.mappingFor(firstFrame.uri)); |
| if (translatedError == null) 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' : ''; |
| |
| 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'); |
| } |