mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 18:36:29 +00:00
Move deobfuscation tools to the SDK repo
This is an initial implementation of the dart deobfuscator tool. Let me know your thoughts on the package name. I used to have this named as `package:deobfuscate`, but it feels like we will want to add more tools that are not about deobfuscation in the future, so I picked `package:dart2js_tools` instead. That also gives us the opportunity to move over the dart2js_info code here too. Change-Id: I2ff948982969c9c76bc84cdc78cbe237abc87378 Reviewed-on: https://dart-review.googlesource.com/69243 Reviewed-by: Stephen Adams <sra@google.com> Commit-Queue: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
parent
666c8c1a89
commit
7c77ed04f4
|
@ -29,6 +29,7 @@ convert:third_party/pkg/convert/lib
|
|||
crypto:third_party/pkg/crypto/lib
|
||||
csslib:third_party/pkg/csslib/lib
|
||||
dart2js_info:third_party/pkg/dart2js_info/lib
|
||||
dart2js_tools:pkg/dart2js_tools/lib
|
||||
dart_internal:pkg/dart_internal/lib
|
||||
dart_messages:pkg/dart_messages/lib
|
||||
dart_style:third_party/pkg_tested/dart_style/lib
|
||||
|
|
10
pkg/dart2js_tools/README.md
Normal file
10
pkg/dart2js_tools/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
### dart2js\_tools
|
||||
|
||||
This package collects tools used with dart2js.
|
||||
|
||||
For now, this contains scripts useful to work with obfuscated stack traces in
|
||||
production and reading data from the extensions added to source-maps by dart2js
|
||||
(like minified names and inlined stack frames).
|
||||
|
||||
In the future we plan to merge here tools in the dart2js\_info package as well.
|
151
pkg/dart2js_tools/bin/deobfuscate.dart
Normal file
151
pkg/dart2js_tools/bin/deobfuscate.dart
Normal file
|
@ -0,0 +1,151 @@
|
|||
// 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 'package:source_maps/source_maps.dart';
|
||||
import 'package:source_maps/src/utils.dart';
|
||||
import 'package:dart2js_tools/src/trace.dart';
|
||||
import 'package:dart2js_tools/src/sourcemap_helper.dart';
|
||||
import 'package:dart2js_tools/src/name_decoder.dart';
|
||||
import 'package:dart2js_tools/src/dart2js_mapping.dart';
|
||||
import 'package:dart2js_tools/src/util.dart';
|
||||
|
||||
/// Script that deobuscates 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 {
|
||||
deobfuscate(new File(args[0]).readAsStringSync(), sb);
|
||||
} finally {
|
||||
print('$sb');
|
||||
}
|
||||
}
|
||||
|
||||
void deobfuscate(trace, StringBuffer sb) {
|
||||
String error = extractErrorMessage(trace);
|
||||
String translatedError;
|
||||
var provider = new CachingFileProvider();
|
||||
|
||||
List<StackTraceLine> jsStackTrace = parseStackTrace(trace);
|
||||
|
||||
for (StackTraceLine line in jsStackTrace) {
|
||||
var uri = resolveUri(line.fileName);
|
||||
var mapping = provider.mappingFor(uri);
|
||||
if (mapping == null) {
|
||||
printPadded('no mapping', line.inlineString, sb);
|
||||
continue;
|
||||
}
|
||||
|
||||
TargetEntry targetEntry = findColumn(line.lineNo - 1, line.columnNo - 1,
|
||||
findLine(mapping.sourceMap, line.lineNo - 1));
|
||||
if (targetEntry == null) {
|
||||
printPadded('no entry', line.inlineString, sb);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (translatedError == null) {
|
||||
translatedError = translate(error, mapping, line, targetEntry);
|
||||
if (translatedError == null) translatedError = '<no error message found>';
|
||||
printPadded(translatedError, error, sb);
|
||||
}
|
||||
|
||||
int offset =
|
||||
provider.fileFor(uri).getOffset(line.lineNo - 1, line.columnNo - 1);
|
||||
|
||||
String nameOf(id) => id != 0 ? mapping.sourceMap.names[id] : null;
|
||||
String urlOf(id) => id != 0 ? mapping.sourceMap.urls[id] : null;
|
||||
|
||||
String fileName = urlOf(targetEntry.sourceUrlId ?? 0);
|
||||
int targetLine = (targetEntry.sourceLine ?? 0) + 1;
|
||||
int targetColumn = (targetEntry.sourceColumn ?? 0) + 1;
|
||||
|
||||
// Expand inlined frames.
|
||||
Map<int, List<FrameEntry>> frames = mapping.frames;
|
||||
List<int> index = mapping.frameIndex;
|
||||
int key = binarySearch(index, (i) => i > offset) - 1;
|
||||
int depth = 0;
|
||||
outer:
|
||||
while (key >= 0) {
|
||||
for (var frame in frames[index[key]].reversed) {
|
||||
if (frame.isEmpty) break outer;
|
||||
if (frame.isPush) {
|
||||
if (depth <= 0) {
|
||||
var mappedLine = new StackTraceLine(
|
||||
frame.inlinedMethodName + "(inlined)",
|
||||
fileName,
|
||||
targetLine,
|
||||
targetColumn);
|
||||
printPadded(mappedLine.inlineString, "", sb);
|
||||
fileName = frame.callUri;
|
||||
targetLine = (frame.callLine ?? 0) + 1;
|
||||
targetColumn = (frame.callColumn ?? 0) + 1;
|
||||
} else {
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
if (frame.isPop) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
key--;
|
||||
}
|
||||
|
||||
var functionEntry = findEnclosingFunction(provider, uri, offset);
|
||||
String methodName = nameOf(functionEntry.sourceNameId ?? 0);
|
||||
var mappedLine =
|
||||
new StackTraceLine(methodName, fileName, targetLine, targetColumn);
|
||||
printPadded(mappedLine.inlineString, line.inlineString, sb);
|
||||
}
|
||||
}
|
||||
|
||||
final green = stdout.hasTerminal ? '[32m' : '';
|
||||
final none = stdout.hasTerminal ? '[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');
|
||||
}
|
||||
|
||||
Uri resolveUri(String filename) {
|
||||
var uri = Uri.base.resolve(filename);
|
||||
if (uri.scheme == 'http' || uri.scheme == 'https') {
|
||||
filename = uri.path.substring(uri.path.lastIndexOf('/') + 1);
|
||||
uri = Uri.base.resolve(filename);
|
||||
}
|
||||
return uri;
|
||||
}
|
33
pkg/dart2js_tools/bin/lookup_name.dart
Normal file
33
pkg/dart2js_tools/bin/lookup_name.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
|
||||
main(List<String> args) {
|
||||
if (args.length < 2) {
|
||||
print('usage: read.dart <source-map-file> <name>');
|
||||
exit(1);
|
||||
}
|
||||
var sourcemapFile = new File.fromUri(Uri.base.resolve(args[0]));
|
||||
if (!sourcemapFile.existsSync()) {
|
||||
print('no source-map-file in ${args[0]}');
|
||||
exit(1);
|
||||
}
|
||||
var name = args[1];
|
||||
var json = jsonDecode(sourcemapFile.readAsStringSync());
|
||||
SingleMapping mapping = parseJson(json);
|
||||
var extensions = json['x_org_dartlang_dart2js'];
|
||||
if (extensions == null) {
|
||||
print('source-map file has no dart2js extensions');
|
||||
exit(1);
|
||||
}
|
||||
var minifiedNames = extensions['minified_names'];
|
||||
if (minifiedNames == null) {
|
||||
print('source-map file has no minified names in the dart2js extensions');
|
||||
exit(1);
|
||||
}
|
||||
var gid = minifiedNames['global'][name];
|
||||
if (gid != null) print('$name => ${mapping.names[gid]} (a global name)');
|
||||
var iid = minifiedNames['instance'][name];
|
||||
if (iid != null) print('$name => ${mapping.names[iid]} (an instance name)');
|
||||
if (gid == null && iid == null) print('Name \'$name\' not found.');
|
||||
}
|
17
pkg/dart2js_tools/bin/read.dart
Normal file
17
pkg/dart2js_tools/bin/read.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
|
||||
main(List<String> args) {
|
||||
if (args.length != 1) {
|
||||
print('usage: read.dart <source-map-file>');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var sourcemapFile = new File.fromUri(Uri.base.resolve(args[0]));
|
||||
if (!sourcemapFile.existsSync()) {
|
||||
print('no source-map-file in ${args[0]}');
|
||||
}
|
||||
var bytes = sourcemapFile.readAsBytesSync();
|
||||
parse(utf8.decode(bytes));
|
||||
}
|
89
pkg/dart2js_tools/bin/show_inline_data.dart
Normal file
89
pkg/dart2js_tools/bin/show_inline_data.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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 'package:dart2js_tools/src/util.dart';
|
||||
|
||||
/// Script to show a text representation of the inlining data attached to
|
||||
/// source-map files.
|
||||
///
|
||||
/// This expands the push/pop operations and checks simple invariants (e.g. that
|
||||
/// the stack is always empty at the beginning of a function).
|
||||
main(List<String> args) {
|
||||
if (args.length != 1) {
|
||||
print('usage: show_inline_data.dart <js-file>');
|
||||
exit(1);
|
||||
}
|
||||
var uri = Uri.base.resolve(args[0]);
|
||||
var provider = new CachingFileProvider();
|
||||
|
||||
var mapping = provider.mappingFor(uri);
|
||||
var starts = functionStarts(provider.sourcesFor(uri));
|
||||
var file = provider.fileFor(uri);
|
||||
var frames = mapping.frames;
|
||||
var offsets = frames.keys.toList()..sort();
|
||||
var sb = new StringBuffer();
|
||||
int depth = 0;
|
||||
int lastFunctionStart = null;
|
||||
for (var offset in offsets) {
|
||||
int functionStart = nextFunctionStart(starts, offset, lastFunctionStart);
|
||||
if (lastFunctionStart == null || functionStart > lastFunctionStart) {
|
||||
sb.write('\n${location(starts[functionStart], file)}: function start\n');
|
||||
|
||||
if (depth != 0) {
|
||||
sb.write(
|
||||
"[31m[invalid] function start with non-zero depth: $depth[0m\n");
|
||||
}
|
||||
lastFunctionStart = functionStart;
|
||||
}
|
||||
|
||||
var offsetPrefix = '${location(offset, file)}:';
|
||||
var pad = ' ' * offsetPrefix.length;
|
||||
sb.write(offsetPrefix);
|
||||
bool first = true;
|
||||
for (var frame in frames[offset]) {
|
||||
if (!first) sb.write('$pad');
|
||||
sb.write(' $frame\n');
|
||||
first = false;
|
||||
if (frame.isPush) depth++;
|
||||
if (frame.isPop) depth--;
|
||||
if (frame.isEmpty && depth != 0) {
|
||||
sb.write("[31m[invalid] pop-empty with non-zero depth: $depth[0m\n");
|
||||
}
|
||||
if (!frame.isEmpty && depth == 0) {
|
||||
sb.write("[31m[invalid] non-empty pop with zero depth: $depth[0m\n");
|
||||
}
|
||||
if (depth < 0) {
|
||||
sb.write("[31m[invalid] negative depth: $depth[0m\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
print('$sb');
|
||||
}
|
||||
|
||||
var _functionDeclarationRegExp = new RegExp(r':( )?function\(');
|
||||
|
||||
List<int> functionStarts(String sources) {
|
||||
List<int> result = [];
|
||||
int index = sources.indexOf(_functionDeclarationRegExp);
|
||||
while (index != -1) {
|
||||
result.add(index + 2);
|
||||
index = sources.indexOf(_functionDeclarationRegExp, index + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int nextFunctionStart(List<int> starts, int offset, int last) {
|
||||
int j = last ?? 0;
|
||||
for (; j < starts.length && starts[j] <= offset; j++);
|
||||
return j - 1;
|
||||
}
|
||||
|
||||
String location(int offset, file) {
|
||||
var line = file.getLine(offset) + 1;
|
||||
var column = file.getColumn(offset) + 1;
|
||||
var location = '$offset ($line:$column)';
|
||||
return location + (' ' * (16 - location.length));
|
||||
}
|
133
pkg/dart2js_tools/lib/src/dart2js_mapping.dart
Normal file
133
pkg/dart2js_tools/lib/src/dart2js_mapping.dart
Normal file
|
@ -0,0 +1,133 @@
|
|||
// 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.
|
||||
|
||||
/// Representation of a source-map file with dart2js-specific extensions, and
|
||||
/// helper functions to parse them.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
/// Representation of a source-map file with dart2js-specific extensions.
|
||||
///
|
||||
/// Dart2js adds a special section that provides: tables of minified names and a
|
||||
/// table of inlining frame data.
|
||||
class Dart2jsMapping {
|
||||
final SingleMapping sourceMap;
|
||||
|
||||
final Map<String, String> globalNames = {};
|
||||
final Map<String, String> instanceNames = {};
|
||||
final Map<int, List<FrameEntry>> frames = {};
|
||||
List<int> _frameIndex;
|
||||
List<int> get frameIndex {
|
||||
if (_frameIndex == null) {
|
||||
_frameIndex = frames.keys.toList()..sort();
|
||||
}
|
||||
return _frameIndex;
|
||||
}
|
||||
|
||||
Dart2jsMapping(this.sourceMap, Map json) {
|
||||
var extensions = json['x_org_dartlang_dart2js'];
|
||||
if (extensions == null) return;
|
||||
var minifiedNames = extensions['minified_names'];
|
||||
if (minifiedNames != null) {
|
||||
minifiedNames['global'].forEach((minifiedName, id) {
|
||||
globalNames[minifiedName] = sourceMap.names[id];
|
||||
});
|
||||
minifiedNames['instance'].forEach((minifiedName, id) {
|
||||
instanceNames[minifiedName] = sourceMap.names[id];
|
||||
});
|
||||
}
|
||||
List jsonFrames = extensions['frames'];
|
||||
if (jsonFrames != null) {
|
||||
for (List values in jsonFrames) {
|
||||
if (values.length < 2) {
|
||||
warn("warning: incomplete frame data: $values");
|
||||
continue;
|
||||
}
|
||||
int offset = values[0];
|
||||
List<FrameEntry> entries = frames[offset] ??= [];
|
||||
if (entries.length > 0) {
|
||||
warn("warning: duplicate entries for $offset");
|
||||
continue;
|
||||
}
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
var current = values[i];
|
||||
if (current == -1) {
|
||||
entries.add(new FrameEntry.pop(false));
|
||||
} else if (current == 0) {
|
||||
entries.add(new FrameEntry.pop(true));
|
||||
} else {
|
||||
if (current is List) {
|
||||
if (current.length == 4) {
|
||||
entries.add(new FrameEntry.push(sourceMap.urls[current[0]],
|
||||
current[1], current[2], sourceMap.names[current[3]]));
|
||||
} else {
|
||||
warn("warning: unexpected entry $current");
|
||||
}
|
||||
} else {
|
||||
warn("warning: unexpected entry $current");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FrameEntry {
|
||||
final String callUri;
|
||||
final int callLine;
|
||||
final int callColumn;
|
||||
final String inlinedMethodName;
|
||||
final bool isEmpty;
|
||||
FrameEntry.push(
|
||||
this.callUri, this.callLine, this.callColumn, this.inlinedMethodName)
|
||||
: isEmpty = false;
|
||||
FrameEntry.pop(this.isEmpty)
|
||||
: callUri = null,
|
||||
callLine = null,
|
||||
callColumn = null,
|
||||
inlinedMethodName = null;
|
||||
|
||||
bool get isPush => callUri != null;
|
||||
bool get isPop => callUri == null;
|
||||
|
||||
toString() {
|
||||
if (isPush)
|
||||
return "push $inlinedMethodName @ $callUri:$callLine:$callColumn";
|
||||
return isEmpty ? 'pop: empty' : 'pop';
|
||||
}
|
||||
}
|
||||
|
||||
const _marker = "\n//# sourceMappingURL=";
|
||||
Dart2jsMapping parseMappingFor(Uri uri) {
|
||||
var file = new File.fromUri(uri);
|
||||
if (!file.existsSync()) {
|
||||
warn('Error: no such file: $uri');
|
||||
return null;
|
||||
}
|
||||
var contents = file.readAsStringSync();
|
||||
var urlIndex = contents.indexOf(_marker);
|
||||
var sourcemapPath;
|
||||
if (urlIndex != -1) {
|
||||
sourcemapPath = contents.substring(urlIndex + _marker.length).trim();
|
||||
} else {
|
||||
warn('Error: source-map url marker not found in $uri\n'
|
||||
' trying $uri.map');
|
||||
sourcemapPath = '${uri.pathSegments.last}.map';
|
||||
}
|
||||
|
||||
assert(!sourcemapPath.contains('\n'));
|
||||
var sourcemapFile = new File.fromUri(uri.resolve(sourcemapPath));
|
||||
if (!sourcemapFile.existsSync()) {
|
||||
warn('Error: no such file: $sourcemapFile');
|
||||
return null;
|
||||
}
|
||||
var json = jsonDecode(sourcemapFile.readAsStringSync());
|
||||
return new Dart2jsMapping(parseJson(json), json);
|
||||
}
|
78
pkg/dart2js_tools/lib/src/name_decoder.dart
Normal file
78
pkg/dart2js_tools/lib/src/name_decoder.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
// 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.
|
||||
|
||||
/// Logic to deobfuscate minified names that appear in error messages.
|
||||
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
|
||||
import 'dart2js_mapping.dart';
|
||||
import 'trace.dart';
|
||||
|
||||
String translate(String error, Dart2jsMapping mapping, StackTraceLine line,
|
||||
TargetEntry entry) {
|
||||
for (var decoder in _errorMapDecoders) {
|
||||
var result = decoder.decode(error, mapping, line, entry);
|
||||
// More than one decoder might be applied on a single error message. This
|
||||
// can be useful, for example, if an error contains details about a member
|
||||
// and the type.
|
||||
if (result != null) error = result;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/// A decoder that matches an error against a regular expression and
|
||||
/// uses data from the source-file and source-map file to translate minified
|
||||
/// names to un-minified names.
|
||||
abstract class ErrorMapDecoder {
|
||||
RegExp get _matcher;
|
||||
|
||||
/// Decode [error] that was reported in [line] and has a corresponding [entry]
|
||||
/// in the source-map file. The provided [mapping] includes additional
|
||||
/// minification data that may be used to decode the error message.
|
||||
String decode(String error, Dart2jsMapping mapping, StackTraceLine line,
|
||||
TargetEntry entry) {
|
||||
if (error == null) return null;
|
||||
var match = _matcher.firstMatch(error);
|
||||
if (match == null) return null;
|
||||
var result = _decodeInternal(match, mapping, line, entry);
|
||||
if (result == null) return null;
|
||||
return '${error.substring(0, match.start)}'
|
||||
'$result${error.substring(match.end, error.length)}';
|
||||
}
|
||||
|
||||
String _decodeInternal(Match match, Dart2jsMapping mapping,
|
||||
StackTraceLine line, TargetEntry entry);
|
||||
}
|
||||
|
||||
typedef String ErrorDecoder(Match match, Dart2jsMapping mapping,
|
||||
StackTraceLine line, TargetEntry entry);
|
||||
|
||||
class MinifiedNameDecoder extends ErrorMapDecoder {
|
||||
final RegExp _matcher = new RegExp("minified:([a-zA-Z]*)");
|
||||
|
||||
String _decodeInternal(Match match, Dart2jsMapping mapping,
|
||||
StackTraceLine line, TargetEntry entry) {
|
||||
var minifiedName = match.group(1);
|
||||
var name = mapping.globalNames[minifiedName];
|
||||
if (name == null) return null;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
class CannotReadPropertyDecoder extends ErrorMapDecoder {
|
||||
final RegExp _matcher = new RegExp("Cannot read property '([^']*)' of");
|
||||
|
||||
String _decodeInternal(Match match, Dart2jsMapping mapping,
|
||||
StackTraceLine line, TargetEntry entry) {
|
||||
var minifiedName = match.group(1);
|
||||
var name = mapping.instanceNames[minifiedName];
|
||||
if (name == null) return null;
|
||||
return "Cannot read property '$name' of";
|
||||
}
|
||||
}
|
||||
|
||||
List<ErrorMapDecoder> _errorMapDecoders = [
|
||||
new MinifiedNameDecoder(),
|
||||
new CannotReadPropertyDecoder()
|
||||
];
|
50
pkg/dart2js_tools/lib/src/sourcemap_helper.dart
Normal file
50
pkg/dart2js_tools/lib/src/sourcemap_helper.dart
Normal file
|
@ -0,0 +1,50 @@
|
|||
// 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.
|
||||
|
||||
/// Utility functions to make it easier to work with source-map files.
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
import 'package:source_maps/src/utils.dart';
|
||||
import 'util.dart' show FileProvider;
|
||||
|
||||
/// Search backwards in [sources] for a function declaration that includes the
|
||||
/// [start] offset.
|
||||
TargetEntry findEnclosingFunction(FileProvider provider, Uri uri, int start) {
|
||||
String sources = provider.sourcesFor(uri);
|
||||
if (sources == null) return null;
|
||||
int index = sources.lastIndexOf(': function(', start);
|
||||
if (index < 0) index = sources.lastIndexOf(':function(', start);
|
||||
if (index < 0) return null;
|
||||
index += 2;
|
||||
SourceFile file = provider.fileFor(uri);
|
||||
SingleMapping mapping = provider.mappingFor(uri).sourceMap;
|
||||
var line = file.getLine(index);
|
||||
var lineEntry = findLine(mapping, line);
|
||||
return findColumn(line, file.getColumn(index), lineEntry);
|
||||
}
|
||||
|
||||
/// Returns [TargetLineEntry] which includes the location in the target [line]
|
||||
/// number. In particular, the resulting entry is the last entry whose line
|
||||
/// number is lower or equal to [line].
|
||||
///
|
||||
/// Copied from [SingleMapping._findLine].
|
||||
TargetLineEntry findLine(SingleMapping sourceMap, int line) {
|
||||
int index = binarySearch(sourceMap.lines, (e) => e.line > line);
|
||||
return (index <= 0) ? null : sourceMap.lines[index - 1];
|
||||
}
|
||||
|
||||
/// Returns [TargetEntry] which includes the location denoted by
|
||||
/// [line], [column]. If [lineEntry] corresponds to [line], then this will be
|
||||
/// the last entry whose column is lower or equal than [column]. If
|
||||
/// [lineEntry] corresponds to a line prior to [line], then the result will be
|
||||
/// the very last entry on that line.
|
||||
///
|
||||
/// Copied from [SingleMapping._findColumn].
|
||||
TargetEntry findColumn(int line, int column, TargetLineEntry lineEntry) {
|
||||
if (lineEntry == null || lineEntry.entries.length == 0) return null;
|
||||
if (lineEntry.line != line) return lineEntry.entries.last;
|
||||
var entries = lineEntry.entries;
|
||||
int index = binarySearch(entries, (e) => e.column > column);
|
||||
return (index <= 0) ? null : entries[index - 1];
|
||||
}
|
146
pkg/dart2js_tools/lib/src/trace.dart
Normal file
146
pkg/dart2js_tools/lib/src/trace.dart
Normal file
|
@ -0,0 +1,146 @@
|
|||
// 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.
|
||||
|
||||
/// Representation of stack traces and logic to parse d8 stack traces.
|
||||
// TODO(sigmund): we should delete this implementation and instead:
|
||||
// - switch to use the stack_trace package
|
||||
// - add support non-d8 frames
|
||||
// - add support for secondary regexps to detect stranger frames (like eval frames)
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
/// Represents a stack trace line.
|
||||
class StackTraceLine {
|
||||
String methodName;
|
||||
String fileName;
|
||||
int lineNo;
|
||||
int columnNo;
|
||||
|
||||
StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo);
|
||||
|
||||
/// Creates a [StackTraceLine] by parsing a d8 stack trace line [text]. The
|
||||
/// expected formats are
|
||||
///
|
||||
/// at <methodName>(<fileName>:<lineNo>:<columnNo>)
|
||||
/// at <methodName>(<fileName>:<lineNo>)
|
||||
/// at <methodName>(<fileName>)
|
||||
/// at <fileName>:<lineNo>:<columnNo>
|
||||
/// at <fileName>:<lineNo>
|
||||
/// at <fileName>
|
||||
///
|
||||
factory StackTraceLine.fromText(String text) {
|
||||
text = text.trim();
|
||||
assert(text.startsWith('at '));
|
||||
text = text.substring('at '.length);
|
||||
String methodName;
|
||||
int endParen = text.indexOf(')');
|
||||
if (endParen > 0) {
|
||||
int nameEnd = text.indexOf('(');
|
||||
if (nameEnd != -1) {
|
||||
methodName = text.substring(0, nameEnd).trim();
|
||||
text = text.substring(nameEnd + 1, endParen).trim();
|
||||
} else {
|
||||
warn('Missing left-paren in: $text');
|
||||
}
|
||||
}
|
||||
int lineNo;
|
||||
int columnNo;
|
||||
String fileName;
|
||||
int lastColon = text.lastIndexOf(':');
|
||||
if (lastColon != -1) {
|
||||
int lastValue = int.tryParse(text.substring(lastColon + 1));
|
||||
if (lastValue != null) {
|
||||
int secondToLastColon = text.lastIndexOf(':', lastColon - 1);
|
||||
if (secondToLastColon != -1) {
|
||||
int secondToLastValue =
|
||||
int.tryParse(text.substring(secondToLastColon + 1, lastColon));
|
||||
if (secondToLastValue != null) {
|
||||
lineNo = secondToLastValue;
|
||||
columnNo = lastValue;
|
||||
fileName = text.substring(0, secondToLastColon);
|
||||
} else {
|
||||
lineNo = lastValue;
|
||||
fileName = text.substring(0, lastColon);
|
||||
}
|
||||
} else {
|
||||
lineNo = lastValue;
|
||||
fileName = text.substring(0, lastColon);
|
||||
}
|
||||
} else {
|
||||
fileName = text;
|
||||
}
|
||||
} else {
|
||||
fileName = text;
|
||||
}
|
||||
return new StackTraceLine(methodName, fileName, lineNo, columnNo ?? 1);
|
||||
}
|
||||
|
||||
String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.write(' at ');
|
||||
if (methodName != null) {
|
||||
sb.write(methodName);
|
||||
sb.write(' (');
|
||||
sb.write(fileName ?? '?');
|
||||
sb.write(':');
|
||||
sb.write(lineNo);
|
||||
sb.write(':');
|
||||
sb.write(columnNo);
|
||||
sb.write(')');
|
||||
} else {
|
||||
sb.write(fileName ?? '?');
|
||||
sb.write(':');
|
||||
sb.write(lineNo);
|
||||
sb.write(':');
|
||||
sb.write(columnNo);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
String get inlineString {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
var padding = 20;
|
||||
if (methodName != null) {
|
||||
sb.write(methodName);
|
||||
padding -= (methodName.length);
|
||||
if (padding <= 0) {
|
||||
sb.write('\n');
|
||||
padding = 20;
|
||||
}
|
||||
}
|
||||
sb.write(' ' * padding);
|
||||
if (fileName != null) {
|
||||
sb.write(p.url.basename(fileName));
|
||||
sb.write(' ');
|
||||
sb.write(lineNo);
|
||||
sb.write(':');
|
||||
sb.write(columnNo);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
List<StackTraceLine> parseStackTrace(String trace) {
|
||||
List<String> lines = trace.split(new RegExp(r'(\r|\n|\r\n)'));
|
||||
List<StackTraceLine> jsStackTrace = <StackTraceLine>[];
|
||||
for (String line in lines) {
|
||||
line = line.trim();
|
||||
if (line.startsWith('at ')) {
|
||||
jsStackTrace.add(new StackTraceLine.fromText(line));
|
||||
}
|
||||
}
|
||||
return jsStackTrace;
|
||||
}
|
||||
|
||||
/// Returns the portion of the output that corresponds to the error message.
|
||||
///
|
||||
/// Note: some errors can span multiple lines.
|
||||
String extractErrorMessage(String trace) {
|
||||
var firstStackFrame = trace.indexOf(new RegExp('\n +at'));
|
||||
if (firstStackFrame == -1) return null;
|
||||
var errorMarker = trace.indexOf('^') + 1;
|
||||
return trace.substring(errorMarker, firstStackFrame).trim();
|
||||
}
|
31
pkg/dart2js_tools/lib/src/util.dart
Normal file
31
pkg/dart2js_tools/lib/src/util.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
import 'dart:io';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'dart2js_mapping.dart';
|
||||
|
||||
abstract class FileProvider {
|
||||
String sourcesFor(Uri uri);
|
||||
SourceFile fileFor(Uri uri);
|
||||
Dart2jsMapping mappingFor(Uri uri);
|
||||
}
|
||||
|
||||
class CachingFileProvider implements FileProvider {
|
||||
final Map<Uri, String> _sources = {};
|
||||
final Map<Uri, SourceFile> _files = {};
|
||||
final Map<Uri, Dart2jsMapping> _mappings = {};
|
||||
|
||||
String sourcesFor(Uri uri) =>
|
||||
_sources[uri] ??= new File.fromUri(uri).readAsStringSync();
|
||||
|
||||
SourceFile fileFor(Uri uri) =>
|
||||
_files[uri] ??= new SourceFile.fromString(sourcesFor(uri));
|
||||
|
||||
Dart2jsMapping mappingFor(Uri uri) => _mappings[uri] ??= parseMappingFor(uri);
|
||||
}
|
||||
|
||||
warn(String message) {
|
||||
if (_seenMessages.add(message)) {
|
||||
print(message);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> _seenMessages = new Set<String>();
|
9
pkg/dart2js_tools/pubspec.yaml
Normal file
9
pkg/dart2js_tools/pubspec.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name: dart2js_tools
|
||||
version: 0.0.1
|
||||
description: >
|
||||
Collection of tools used with dart2js including analyzing compilation
|
||||
information, deobfuscation of stack-traces and minified names.
|
||||
dependencies:
|
||||
source_maps: ^0.10.7
|
||||
environment:
|
||||
sdk: '>=2.0.0 <3.0.0'
|
Loading…
Reference in a new issue