[dart2js] Migrate dart2js_tools to null safety

Change-Id: I43186c26521eca6405d0ab8b3a50b545a91b5cc2
Issue: https://github.com/dart-lang/sdk/issues/46617
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/215013
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
Nicholas Shahan 2021-10-08 23:54:54 +00:00 committed by commit-bot@chromium.org
parent 52c0a7bfd1
commit 50fb0c6e61
12 changed files with 119 additions and 118 deletions

View file

@ -220,7 +220,7 @@
"name": "dart2js_tools", "name": "dart2js_tools",
"rootUri": "../pkg/dart2js_tools", "rootUri": "../pkg/dart2js_tools",
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "2.3" "languageVersion": "2.12"
}, },
{ {
"name": "dart2native", "name": "dart2native",

View file

@ -10,7 +10,7 @@ import 'package:dart2js_tools/src/name_decoder.dart';
import 'package:dart2js_tools/src/util.dart'; import 'package:dart2js_tools/src/util.dart';
import 'package:dart2js_tools/src/trace_decoder.dart'; import 'package:dart2js_tools/src/trace_decoder.dart';
/// Script that deobuscates a stack-trace given in a text file. /// Script that deobfuscates a stack-trace given in a text file.
/// ///
/// To run this script you need 3 or more files: /// To run this script you need 3 or more files:
/// ///
@ -47,24 +47,24 @@ main(List<String> args) {
var sb = new StringBuffer(); var sb = new StringBuffer();
try { try {
String obfuscatedTrace = new File(args[0]).readAsStringSync(); String obfuscatedTrace = new File(args[0]).readAsStringSync();
String error = extractErrorMessage(obfuscatedTrace); String? error = extractErrorMessage(obfuscatedTrace);
var provider = new CachingFileProvider(logger: Logger()); var provider = new CachingFileProvider(logger: Logger());
StackDeobfuscationResult result = StackDeobfuscationResult result =
deobfuscateStack(obfuscatedTrace, provider); deobfuscateStack(obfuscatedTrace, provider);
Frame firstFrame = result.original.frames.first; Frame firstFrame = result.original.frames.first;
String translatedError = String? translatedError =
translate(error, provider.mappingFor(firstFrame.uri)); translate(error, provider.mappingFor(firstFrame.uri));
if (translatedError == null) translatedError = '<no error message found>'; if (translatedError == null) translatedError = '<no error message found>';
printPadded(translatedError, error, sb); printPadded(translatedError, error, sb);
int longest = int longest =
result.deobfuscated.frames.fold(0, (m, f) => max(f.member.length, m)); result.deobfuscated.frames.fold(0, (m, f) => max(f.member!.length, m));
for (var originalFrame in result.original.frames) { for (var originalFrame in result.original.frames) {
var deobfuscatedFrames = result.frameMap[originalFrame]; var deobfuscatedFrames = result.frameMap[originalFrame];
if (deobfuscatedFrames == null) { if (deobfuscatedFrames == null) {
printPadded('no mapping', '${originalFrame.location}', sb); printPadded('no mapping', '${originalFrame.location}', sb);
} else { } else {
for (var frame in deobfuscatedFrames) { for (var frame in deobfuscatedFrames) {
printPadded('${frame.member.padRight(longest)} ${frame.location}', printPadded('${frame.member!.padRight(longest)} ${frame.location}',
'${originalFrame.location}', sb); '${originalFrame.location}', sb);
} }
} }
@ -77,7 +77,7 @@ main(List<String> args) {
final green = stdout.hasTerminal ? '\x1b[32m' : ''; final green = stdout.hasTerminal ? '\x1b[32m' : '';
final none = stdout.hasTerminal ? '\x1b[0m' : ''; final none = stdout.hasTerminal ? '\x1b[0m' : '';
printPadded(String mapping, String original, sb) { printPadded(String mapping, String? original, sb) {
var len = mapping.length; var len = mapping.length;
var s = mapping.indexOf('\n'); var s = mapping.indexOf('\n');
if (s >= 0) len -= s + 1; if (s >= 0) len -= s + 1;

View file

@ -1,7 +1,8 @@
import 'dart:io'; import 'dart:io';
import 'dart:convert'; import 'dart:convert';
import 'package:source_maps/source_maps.dart';
import 'package:dart2js_tools/src/dart2js_mapping.dart'; import 'package:dart2js_tools/src/dart2js_mapping.dart';
import 'package:dart2js_tools/src/util.dart';
main(List<String> args) { main(List<String> args) {
if (args.length < 2) { if (args.length < 2) {
@ -16,7 +17,7 @@ main(List<String> args) {
exit(1); exit(1);
} }
var json = jsonDecode(sourcemapFile.readAsStringSync()); var json = jsonDecode(sourcemapFile.readAsStringSync());
Dart2jsMapping mapping = Dart2jsMapping(parseJson(json), json); Dart2jsMapping mapping = Dart2jsMapping(parseSingleMapping(json), json);
var global = mapping.globalNames[name]; var global = mapping.globalNames[name];
if (global != null) print('$name => $global (a global name)'); if (global != null) print('$name => $global (a global name)');
var instance = mapping.instanceNames[name]; var instance = mapping.instanceNames[name];

View file

@ -22,11 +22,11 @@ main(List<String> args) {
var mapping = provider.mappingFor(uri); var mapping = provider.mappingFor(uri);
var starts = functionStarts(provider.sourcesFor(uri)); var starts = functionStarts(provider.sourcesFor(uri));
var file = provider.fileFor(uri); var file = provider.fileFor(uri);
var frames = mapping.frames; var frames = mapping!.frames;
var offsets = frames.keys.toList()..sort(); var offsets = frames.keys.toList()..sort();
var sb = new StringBuffer(); var sb = new StringBuffer();
int depth = 0; int depth = 0;
int lastFunctionStart = null; int? lastFunctionStart = null;
for (var offset in offsets) { for (var offset in offsets) {
int functionStart = nextFunctionStart(starts, offset, lastFunctionStart); int functionStart = nextFunctionStart(starts, offset, lastFunctionStart);
if (lastFunctionStart == null || functionStart > lastFunctionStart) { if (lastFunctionStart == null || functionStart > lastFunctionStart) {
@ -43,7 +43,7 @@ main(List<String> args) {
var pad = ' ' * offsetPrefix.length; var pad = ' ' * offsetPrefix.length;
sb.write(offsetPrefix); sb.write(offsetPrefix);
bool first = true; bool first = true;
for (var frame in frames[offset]) { for (var frame in frames[offset]!) {
if (!first) sb.write('$pad'); if (!first) sb.write('$pad');
sb.write(' $frame\n'); sb.write(' $frame\n');
first = false; first = false;
@ -75,7 +75,7 @@ List<int> functionStarts(String sources) {
return result; return result;
} }
int nextFunctionStart(List<int> starts, int offset, int last) { int nextFunctionStart(List<int> starts, int offset, int? last) {
int j = last ?? 0; int j = last ?? 0;
for (; j < starts.length && starts[j] <= offset; j++); for (; j < starts.length && starts[j] <= offset; j++);
return j - 1; return j - 1;

View file

@ -5,7 +5,7 @@ import 'package:dart2js_tools/src/name_decoder.dart';
import 'package:dart2js_tools/src/util.dart'; import 'package:dart2js_tools/src/util.dart';
import 'package:dart2js_tools/src/trace_decoder.dart'; import 'package:dart2js_tools/src/trace_decoder.dart';
/// Deobuscates the given [obfuscatedTrace]. /// Deobfuscates the given [obfuscatedTrace].
/// ///
/// This method assumes a stack trace contains URIs normalized to be file URIs. /// This method assumes a stack trace contains URIs normalized to be file URIs.
/// If for example you obtain a stack from a browser, you may need to preprocess /// If for example you obtain a stack from a browser, you may need to preprocess
@ -27,7 +27,7 @@ import 'package:dart2js_tools/src/trace_decoder.dart';
/// `//# sourceMappingURL=` line at the end, and load the corresponding /// `//# sourceMappingURL=` line at the end, and load the corresponding
/// source-map file. /// source-map file.
String deobfuscateStackTrace(String obfuscatedTrace) { String deobfuscateStackTrace(String obfuscatedTrace) {
String error = extractErrorMessage(obfuscatedTrace); String? error = extractErrorMessage(obfuscatedTrace);
var provider = CachingFileProvider(); var provider = CachingFileProvider();
StackDeobfuscationResult result = deobfuscateStack(obfuscatedTrace, provider); StackDeobfuscationResult result = deobfuscateStack(obfuscatedTrace, provider);
Frame firstFrame = result.original.frames.first; Frame firstFrame = result.original.frames.first;
@ -38,14 +38,14 @@ String deobfuscateStackTrace(String obfuscatedTrace) {
var sb = StringBuffer(); var sb = StringBuffer();
sb.writeln(translatedError); sb.writeln(translatedError);
maxMemberLengthHelper(int m, Frame f) => max(f.member.length, m); maxMemberLengthHelper(int m, Frame f) => max(f.member!.length, m);
int longest = result.deobfuscated.frames.fold(0, maxMemberLengthHelper); int longest = result.deobfuscated.frames.fold(0, maxMemberLengthHelper);
longest = result.original.frames.fold(longest, maxMemberLengthHelper); longest = result.original.frames.fold(longest, maxMemberLengthHelper);
for (var originalFrame in result.original.frames) { for (var originalFrame in result.original.frames) {
var deobfuscatedFrames = result.frameMap[originalFrame]; var deobfuscatedFrames = result.frameMap[originalFrame];
if (deobfuscatedFrames == null) { if (deobfuscatedFrames == null) {
var name = originalFrame.member; var name = originalFrame.member;
sb.writeln(' at ${name.padRight(longest)} ${originalFrame.location}'); sb.writeln(' at ${name!.padRight(longest)} ${originalFrame.location}');
} else { } else {
for (var frame in deobfuscatedFrames) { for (var frame in deobfuscatedFrames) {
var name = frame.member; var name = frame.member;
@ -53,7 +53,7 @@ String deobfuscateStackTrace(String obfuscatedTrace) {
// client, we can start encoding the function name and remove this // client, we can start encoding the function name and remove this
// workaround. // workaround.
if (name == '<unknown>') name = originalFrame.member; if (name == '<unknown>') name = originalFrame.member;
sb.writeln(' at ${name.padRight(longest)} ${frame.location}'); sb.writeln(' at ${name!.padRight(longest)} ${frame.location}');
} }
} }
} }

View file

@ -23,38 +23,32 @@ class Dart2jsMapping {
final Map<String, String> globalNames = {}; final Map<String, String> globalNames = {};
final Map<String, String> instanceNames = {}; final Map<String, String> instanceNames = {};
final Map<int, List<FrameEntry>> frames = {}; final Map<int, List<FrameEntry>> frames = {};
List<int> _frameIndex; late final List<int> frameIndex = frames.keys.toList()..sort();
List<int> get frameIndex {
if (_frameIndex == null) {
_frameIndex = frames.keys.toList()..sort();
}
return _frameIndex;
}
Dart2jsMapping(this.sourceMap, Map json, {Logger logger}) { Dart2jsMapping(this.sourceMap, Map json, {Logger? logger}) {
var extensions = json['x_org_dartlang_dart2js']; var extensions = json['x_org_dartlang_dart2js'];
if (extensions == null) return; if (extensions == null) return;
var minifiedNames = extensions['minified_names']; var minifiedNames = extensions['minified_names'];
if (minifiedNames != null) { if (minifiedNames != null) {
_extractMinifedNames( _extractMinifiedNames(
minifiedNames['global'], sourceMap, globalNames, logger); minifiedNames['global'], sourceMap, globalNames, logger);
_extractMinifedNames( _extractMinifiedNames(
minifiedNames['instance'], sourceMap, instanceNames, logger); minifiedNames['instance'], sourceMap, instanceNames, logger);
} }
String jsonFrames = extensions['frames']; String? jsonFrames = extensions['frames'];
if (jsonFrames != null) { if (jsonFrames != null) {
new _FrameDecoder(jsonFrames).parseFrames(frames, sourceMap); new _FrameDecoder(jsonFrames).parseFrames(frames, sourceMap);
} }
} }
Dart2jsMapping.json(Map json) : this(parseJson(json), json); Dart2jsMapping.json(Map json) : this(parseSingleMapping(json), json);
} }
class FrameEntry { class FrameEntry {
final String callUri; final String? callUri;
final int callLine; final int? callLine;
final int callColumn; final int? callColumn;
final String inlinedMethodName; final String? inlinedMethodName;
final bool isEmpty; final bool isEmpty;
FrameEntry.push( FrameEntry.push(
this.callUri, this.callLine, this.callColumn, this.inlinedMethodName) this.callUri, this.callLine, this.callColumn, this.inlinedMethodName)
@ -76,7 +70,7 @@ class FrameEntry {
} }
const _marker = "\n//# sourceMappingURL="; const _marker = "\n//# sourceMappingURL=";
Dart2jsMapping parseMappingFor(Uri uri, {Logger logger}) { Dart2jsMapping? parseMappingFor(Uri uri, {Logger? logger}) {
var file = new File.fromUri(uri); var file = new File.fromUri(uri);
if (!file.existsSync()) { if (!file.existsSync()) {
logger?.log('Error: no such file: $uri'); logger?.log('Error: no such file: $uri');
@ -100,7 +94,7 @@ Dart2jsMapping parseMappingFor(Uri uri, {Logger logger}) {
return null; return null;
} }
var json = jsonDecode(sourcemapFile.readAsStringSync()); var json = jsonDecode(sourcemapFile.readAsStringSync());
return new Dart2jsMapping(parseJson(json), json, logger: logger); return new Dart2jsMapping(parseSingleMapping(json), json, logger: logger);
} }
class _FrameDecoder implements Iterator<String> { class _FrameDecoder implements Iterator<String> {
@ -112,8 +106,9 @@ class _FrameDecoder implements Iterator<String> {
// Iterator API is used by decodeVlq to consume VLQ entries. // Iterator API is used by decodeVlq to consume VLQ entries.
bool moveNext() => ++index < _length; bool moveNext() => ++index < _length;
String get current => String get current => (index >= 0 && index < _length)
(index >= 0 && index < _length) ? _internal[index] : null; ? _internal[index]
: throw StateError('No current value available.');
bool get hasTokens => index < _length - 1 && _length > 0; bool get hasTokens => index < _length - 1 && _length > 0;
@ -150,8 +145,8 @@ class _FrameDecoder implements Iterator<String> {
} }
} }
_extractMinifedNames(String encodedInput, SingleMapping sourceMap, _extractMinifiedNames(String encodedInput, SingleMapping sourceMap,
Map<String, String> minifiedNames, Logger logger) { Map<String, String> minifiedNames, Logger? logger) {
if (encodedInput.isEmpty) return; if (encodedInput.isEmpty) return;
List<String> input = encodedInput.split(','); List<String> input = encodedInput.split(',');
if (input.length % 2 != 0) { if (input.length % 2 != 0) {
@ -159,7 +154,7 @@ _extractMinifedNames(String encodedInput, SingleMapping sourceMap,
} }
for (int i = 0; i < input.length; i += 2) { for (int i = 0; i < input.length; i += 2) {
String minifiedName = input[i]; String minifiedName = input[i];
int id = int.tryParse(input[i + 1]); int id = int.parse(input[i + 1]);
minifiedNames[minifiedName] = sourceMap.names[id]; minifiedNames[minifiedName] = sourceMap.names[id];
} }
} }

View file

@ -9,8 +9,8 @@ import 'package:source_maps/source_maps.dart';
import 'dart2js_mapping.dart'; import 'dart2js_mapping.dart';
import 'trace.dart'; import 'trace.dart';
String translate(String error, Dart2jsMapping mapping, String? translate(String? error, Dart2jsMapping? mapping,
[StackTraceLine line, TargetEntry entry]) { [StackTraceLine? line, TargetEntry? entry]) {
for (var decoder in _errorMapDecoders) { for (var decoder in _errorMapDecoders) {
var result = decoder.decode(error, mapping, line, entry); var result = decoder.decode(error, mapping, line, entry);
// More than one decoder might be applied on a single error message. This // More than one decoder might be applied on a single error message. This
@ -30,10 +30,10 @@ abstract class ErrorMapDecoder {
/// Decode [error] that was reported in [line] and has a corresponding [entry] /// Decode [error] that was reported in [line] and has a corresponding [entry]
/// in the source-map file. The provided [mapping] includes additional /// in the source-map file. The provided [mapping] includes additional
/// minification data that may be used to decode the error message. /// minification data that may be used to decode the error message.
String decode(String error, Dart2jsMapping mapping, StackTraceLine line, String? decode(String? error, Dart2jsMapping? mapping, StackTraceLine? line,
TargetEntry entry) { TargetEntry? entry) {
if (error == null) return null; if (error == null) return null;
Match lastMatch = null; Match? lastMatch = null;
var result = new StringBuffer(); var result = new StringBuffer();
for (var match in _matcher.allMatches(error)) { for (var match in _matcher.allMatches(error)) {
var decodedMatch = _decodeInternal(match, mapping, line, entry); var decodedMatch = _decodeInternal(match, mapping, line, entry);
@ -49,8 +49,8 @@ abstract class ErrorMapDecoder {
return '$result'; return '$result';
} }
String _decodeInternal(Match match, Dart2jsMapping mapping, String? _decodeInternal(Match match, Dart2jsMapping? mapping,
StackTraceLine line, TargetEntry entry); StackTraceLine? line, TargetEntry? entry);
} }
typedef String ErrorDecoder(Match match, Dart2jsMapping mapping, typedef String ErrorDecoder(Match match, Dart2jsMapping mapping,
@ -59,45 +59,45 @@ typedef String ErrorDecoder(Match match, Dart2jsMapping mapping,
class MinifiedNameDecoder extends ErrorMapDecoder { class MinifiedNameDecoder extends ErrorMapDecoder {
final RegExp _matcher = new RegExp("minified:([a-zA-Z0-9_\$]*)"); final RegExp _matcher = new RegExp("minified:([a-zA-Z0-9_\$]*)");
String _decodeInternal(Match match, Dart2jsMapping mapping, String? _decodeInternal(Match match, Dart2jsMapping? mapping,
StackTraceLine line, TargetEntry entry) { StackTraceLine? line, TargetEntry? entry) {
var minifiedName = match.group(1); var minifiedName = match.group(1);
return mapping.globalNames[minifiedName]; return mapping!.globalNames[minifiedName];
} }
} }
class CannotReadPropertyDecoder extends ErrorMapDecoder { class CannotReadPropertyDecoder extends ErrorMapDecoder {
final RegExp _matcher = new RegExp("Cannot read property '([^']*)' of"); final RegExp _matcher = new RegExp("Cannot read property '([^']*)' of");
String _decodeInternal(Match match, Dart2jsMapping mapping, String? _decodeInternal(Match match, Dart2jsMapping? mapping,
StackTraceLine line, TargetEntry entry) { StackTraceLine? line, TargetEntry? entry) {
var minifiedName = match.group(1); var minifiedName = match.group(1);
var name = mapping.instanceNames[minifiedName]; var name = mapping!.instanceNames[minifiedName];
if (name == null) return null; if (name == null) return null;
return "Cannot read property '$name' of"; return "Cannot read property '$name' of";
} }
} }
abstract class NoSuchMethodDecoderBase extends ErrorMapDecoder { abstract class NoSuchMethodDecoderBase extends ErrorMapDecoder {
String _translateMinifiedName(Dart2jsMapping mapping, String minifiedName) { String? _translateMinifiedName(Dart2jsMapping mapping, String? minifiedName) {
var name = mapping.instanceNames[minifiedName]; var name = mapping.instanceNames[minifiedName];
if (name != null) return "'$name'"; if (name != null) return "'$name'";
if (minifiedName.startsWith(new RegExp(r'(call)?\$[0-9]'))) { if (minifiedName!.startsWith(new RegExp(r'(call)?\$[0-9]'))) {
int first$ = minifiedName.indexOf(r'$'); int first$ = minifiedName.indexOf(r'$');
return _expandCallSignature(minifiedName.substring(first$)); return _expandCallSignature(minifiedName.substring(first$));
} }
return null; return null;
} }
String _expandCallSignature(String callSignature) { String? _expandCallSignature(String callSignature) {
// Minified names are one of these forms: // Minified names are one of these forms:
// $0 // positional arguments only // $0 // positional arguments only
// $1$2 // type parameters and positional arguments // $1$2 // type parameters and positional arguments
// $3$name // positional and named arguments // $3$name // positional and named arguments
// $1$3$name // type parameters and positional and named args // $1$3$name // type parameters and positional and named args
var signature = callSignature.split(r'$'); var signature = callSignature.split(r'$');
var typeArgs = null; int? typeArgs = null;
var totalArgs = null; int? totalArgs = null;
var namedArgs = <String>[]; var namedArgs = <String>[];
for (var arg in signature) { for (var arg in signature) {
if (arg == "") continue; if (arg == "") continue;
@ -115,6 +115,7 @@ abstract class NoSuchMethodDecoderBase extends ErrorMapDecoder {
namedArgs.add(arg); namedArgs.add(arg);
} }
} }
if (totalArgs == null) return null;
var sb = new StringBuffer(); var sb = new StringBuffer();
sb.write("'call'"); sb.write("'call'");
sb.write(" (with "); sb.write(" (with ");
@ -136,11 +137,11 @@ class NoSuchMethodDecoder1 extends NoSuchMethodDecoderBase {
final RegExp _matcher = new RegExp( final RegExp _matcher = new RegExp(
"NoSuchMethodError: method not found: '([^']*)'( on [^\\(]*)? \\(.*\\)"); "NoSuchMethodError: method not found: '([^']*)'( on [^\\(]*)? \\(.*\\)");
String _decodeInternal(Match match, Dart2jsMapping mapping, String? _decodeInternal(Match match, Dart2jsMapping? mapping,
StackTraceLine line, TargetEntry entry) { StackTraceLine? line, TargetEntry? entry) {
var minifiedName = match.group(1); var minifiedName = match.group(1);
var suffix = match.group(2) ?? ''; var suffix = match.group(2) ?? '';
var name = _translateMinifiedName(mapping, minifiedName); var name = _translateMinifiedName(mapping!, minifiedName);
if (name == null) return null; if (name == null) return null;
return "NoSuchMethodError: method not found: $name$suffix"; return "NoSuchMethodError: method not found: $name$suffix";
} }
@ -150,10 +151,10 @@ class NoSuchMethodDecoder2 extends NoSuchMethodDecoderBase {
final RegExp _matcher = final RegExp _matcher =
new RegExp("NoSuchMethodError: method not found: '([^']*)'"); new RegExp("NoSuchMethodError: method not found: '([^']*)'");
String _decodeInternal(Match match, Dart2jsMapping mapping, String? _decodeInternal(Match match, Dart2jsMapping? mapping,
StackTraceLine line, TargetEntry entry) { StackTraceLine? line, TargetEntry? entry) {
var minifiedName = match.group(1); var minifiedName = match.group(1);
var name = _translateMinifiedName(mapping, minifiedName); var name = _translateMinifiedName(mapping!, minifiedName);
if (name == null) return null; if (name == null) return null;
return "NoSuchMethodError: method not found: $name"; return "NoSuchMethodError: method not found: $name";
} }
@ -162,10 +163,10 @@ class NoSuchMethodDecoder2 extends NoSuchMethodDecoderBase {
class UnhandledNotAFunctionError extends ErrorMapDecoder { class UnhandledNotAFunctionError extends ErrorMapDecoder {
final RegExp _matcher = new RegExp("Error: ([^']*) is not a function"); final RegExp _matcher = new RegExp("Error: ([^']*) is not a function");
String _decodeInternal(Match match, Dart2jsMapping mapping, String? _decodeInternal(Match match, Dart2jsMapping? mapping,
StackTraceLine line, TargetEntry entry) { StackTraceLine? line, TargetEntry? entry) {
var minifiedName = match.group(1); var minifiedName = match.group(1);
var name = mapping.instanceNames[minifiedName]; var name = mapping!.instanceNames[minifiedName];
if (name == null) return null; if (name == null) return null;
return "Error: $name is not a function"; return "Error: $name is not a function";
} }

View file

@ -10,11 +10,10 @@ import 'util.dart' show FileProvider;
/// Search backwards in [sources] for a function declaration that includes the /// Search backwards in [sources] for a function declaration that includes the
/// [start] offset. /// [start] offset.
TargetEntry findEnclosingFunction(FileProvider provider, Uri uri, int start) { TargetEntry? findEnclosingFunction(FileProvider provider, Uri uri, int start) {
String sources = provider.sourcesFor(uri); String sources = provider.sourcesFor(uri);
if (sources == null) return null;
SourceFile file = provider.fileFor(uri); SourceFile file = provider.fileFor(uri);
SingleMapping mapping = provider.mappingFor(uri).sourceMap; SingleMapping mapping = provider.mappingFor(uri)!.sourceMap;
var index = start; var index = start;
while (true) { while (true) {
index = nextDeclarationCandidate(sources, index); index = nextDeclarationCandidate(sources, index);
@ -22,7 +21,7 @@ TargetEntry findEnclosingFunction(FileProvider provider, Uri uri, int start) {
var line = file.getLine(index); var line = file.getLine(index);
var lineEntry = findLine(mapping, line); var lineEntry = findLine(mapping, line);
var column = file.getColumn(index); var column = file.getColumn(index);
TargetEntry result = findColumn(line, column, lineEntry); TargetEntry? result = findColumn(line, column, lineEntry);
// If the name entry doesn't start exactly at the column corresponding to // If the name entry doesn't start exactly at the column corresponding to
// `index`, we must be in the middle of a string or code that uses the word // `index`, we must be in the middle of a string or code that uses the word
// "function", but that doesn't have a corresponding mapping. In those // "function", but that doesn't have a corresponding mapping. In those
@ -61,7 +60,7 @@ int nextDeclarationCandidate(String sources, int start) {
/// number is lower or equal to [line]. /// number is lower or equal to [line].
/// ///
/// Copied from [SingleMapping._findLine]. /// Copied from [SingleMapping._findLine].
TargetLineEntry findLine(SingleMapping sourceMap, int line) { TargetLineEntry? findLine(SingleMapping sourceMap, int line) {
int index = binarySearch(sourceMap.lines, (e) => e.line > line); int index = binarySearch(sourceMap.lines, (e) => e.line > line);
return (index <= 0) ? null : sourceMap.lines[index - 1]; return (index <= 0) ? null : sourceMap.lines[index - 1];
} }
@ -73,7 +72,7 @@ TargetLineEntry findLine(SingleMapping sourceMap, int line) {
/// the very last entry on that line. /// the very last entry on that line.
/// ///
/// Copied from [SingleMapping._findColumn]. /// Copied from [SingleMapping._findColumn].
TargetEntry findColumn(int line, int column, TargetLineEntry lineEntry) { TargetEntry? findColumn(int line, int column, TargetLineEntry? lineEntry) {
if (lineEntry == null || lineEntry.entries.length == 0) return null; if (lineEntry == null || lineEntry.entries.length == 0) return null;
if (lineEntry.line != line) return lineEntry.entries.last; if (lineEntry.line != line) return lineEntry.entries.last;
var entries = lineEntry.entries; var entries = lineEntry.entries;

View file

@ -14,10 +14,10 @@ import 'util.dart';
/// Represents a stack trace line. /// Represents a stack trace line.
class StackTraceLine { class StackTraceLine {
String methodName; final String? methodName;
String fileName; final String fileName;
int lineNo; final int? lineNo;
int columnNo; final int columnNo;
StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo); StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo);
@ -31,11 +31,11 @@ class StackTraceLine {
/// at <fileName>:<lineNo> /// at <fileName>:<lineNo>
/// at <fileName> /// at <fileName>
/// ///
factory StackTraceLine.fromText(String text, {Logger logger}) { factory StackTraceLine.fromText(String text, {Logger? logger}) {
text = text.trim(); text = text.trim();
assert(text.startsWith('at ')); assert(text.startsWith('at '));
text = text.substring('at '.length); text = text.substring('at '.length);
String methodName; String? methodName;
int endParen = text.indexOf(')'); int endParen = text.indexOf(')');
if (endParen > 0) { if (endParen > 0) {
int nameEnd = text.indexOf('('); int nameEnd = text.indexOf('(');
@ -46,16 +46,16 @@ class StackTraceLine {
logger?.log('Missing left-paren in: $text'); logger?.log('Missing left-paren in: $text');
} }
} }
int lineNo; int? lineNo;
int columnNo; int? columnNo;
String fileName; String fileName;
int lastColon = text.lastIndexOf(':'); int lastColon = text.lastIndexOf(':');
if (lastColon != -1) { if (lastColon != -1) {
int lastValue = int.tryParse(text.substring(lastColon + 1)); int? lastValue = int.tryParse(text.substring(lastColon + 1));
if (lastValue != null) { if (lastValue != null) {
int secondToLastColon = text.lastIndexOf(':', lastColon - 1); int secondToLastColon = text.lastIndexOf(':', lastColon - 1);
if (secondToLastColon != -1) { if (secondToLastColon != -1) {
int secondToLastValue = int? secondToLastValue =
int.tryParse(text.substring(secondToLastColon + 1, lastColon)); int.tryParse(text.substring(secondToLastColon + 1, lastColon));
if (secondToLastValue != null) { if (secondToLastValue != null) {
lineNo = secondToLastValue; lineNo = secondToLastValue;
@ -84,14 +84,14 @@ class StackTraceLine {
if (methodName != null) { if (methodName != null) {
sb.write(methodName); sb.write(methodName);
sb.write(' ('); sb.write(' (');
sb.write(fileName ?? '?'); sb.write(fileName);
sb.write(':'); sb.write(':');
sb.write(lineNo); sb.write(lineNo);
sb.write(':'); sb.write(':');
sb.write(columnNo); sb.write(columnNo);
sb.write(')'); sb.write(')');
} else { } else {
sb.write(fileName ?? '?'); sb.write(fileName);
sb.write(':'); sb.write(':');
sb.write(lineNo); sb.write(lineNo);
sb.write(':'); sb.write(':');
@ -103,27 +103,27 @@ class StackTraceLine {
String get inlineString { String get inlineString {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
var padding = 20; var padding = 20;
if (methodName != null) { var lineMethodName = methodName;
sb.write(methodName); if (lineMethodName != null) {
padding -= (methodName.length); sb.write(lineMethodName);
padding -= (lineMethodName.length);
if (padding <= 0) { if (padding <= 0) {
sb.write('\n'); sb.write('\n');
padding = 20; padding = 20;
} }
} }
sb.write(' ' * padding); sb.write(' ' * padding);
if (fileName != null) { sb.write(p.url.basename(fileName));
sb.write(p.url.basename(fileName)); sb.write(' ');
sb.write(' '); sb.write(lineNo);
sb.write(lineNo); sb.write(':');
sb.write(':'); sb.write(columnNo);
sb.write(columnNo);
}
return sb.toString(); return sb.toString();
} }
} }
List<StackTraceLine> parseStackTrace(String trace, {Logger logger}) { List<StackTraceLine> parseStackTrace(String trace, {Logger? logger}) {
List<String> lines = trace.split(new RegExp(r'(\r|\n|\r\n)')); List<String> lines = trace.split(new RegExp(r'(\r|\n|\r\n)'));
List<StackTraceLine> jsStackTrace = <StackTraceLine>[]; List<StackTraceLine> jsStackTrace = <StackTraceLine>[];
for (String line in lines) { for (String line in lines) {
@ -138,7 +138,7 @@ List<StackTraceLine> parseStackTrace(String trace, {Logger logger}) {
/// Returns the portion of the output that corresponds to the error message. /// Returns the portion of the output that corresponds to the error message.
/// ///
/// Note: some errors can span multiple lines. /// Note: some errors can span multiple lines.
String extractErrorMessage(String trace) { String? extractErrorMessage(String trace) {
var firstStackFrame = trace.indexOf(new RegExp('\n +at')); var firstStackFrame = trace.indexOf(new RegExp('\n +at'));
if (firstStackFrame == -1) return null; if (firstStackFrame == -1) return null;
var errorMarker = trace.indexOf('^') + 1; var errorMarker = trace.indexOf('^') + 1;

View file

@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a // 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. // BSD-style license that can be found in the LICENSE file.
/// Logic to expand and deobuscate stack traces. /// Logic to expand and deobfuscate stack traces.
import 'package:stack_trace/stack_trace.dart'; import 'package:stack_trace/stack_trace.dart';
import 'package:source_span/source_span.dart'; import 'package:source_span/source_span.dart';
@ -16,12 +16,12 @@ class StackDeobfuscationResult {
/// Representation of the obfuscated stack trace. /// Representation of the obfuscated stack trace.
final Trace original; final Trace original;
/// Representation of the deobfsucated stack trace. /// Representation of the deobfuscated stack trace.
final Trace deobfuscated; final Trace deobfuscated;
/// Details about how one original frame maps to deobfuscated frames. A single /// Details about how one original frame maps to deobfuscated frames. A single
/// frame might map to many frames (in the case of inlining), or to a null /// frame might map to many frames (in the case of inlining), or to a null
/// value (when we were unabled to deobfuscate it). /// value (when we were unable to deobfuscate it).
final Map<Frame, List<Frame>> frameMap; final Map<Frame, List<Frame>> frameMap;
StackDeobfuscationResult(this.original, this.deobfuscated, this.frameMap); StackDeobfuscationResult(this.original, this.deobfuscated, this.frameMap);
@ -35,22 +35,23 @@ StackDeobfuscationResult deobfuscateStack(
var deobfuscatedFrames = <Frame>[]; var deobfuscatedFrames = <Frame>[];
var frameMap = <Frame, List<Frame>>{}; var frameMap = <Frame, List<Frame>>{};
for (var frame in trace.frames) { for (var frame in trace.frames) {
var frameLine = frame.line;
// If there's no line information, there's no way to translate this frame. // If there's no line information, there's no way to translate this frame.
// We could return it as-is, but these lines are usually not useful anyways. // We could return it as-is, but these lines are usually not useful anyways.
if (frame.line == null) { if (frameLine == null) {
continue; continue;
} }
// If there's no column, try using the first column of the line. // If there's no column, try using the first column of the line.
var column = frame.column ?? 1; var column = frame.column ?? 1;
Dart2jsMapping mapping = provider.mappingFor(frame.uri); Dart2jsMapping? mapping = provider.mappingFor(frame.uri);
if (mapping == null) continue; if (mapping == null) continue;
// Subtract 1 because stack traces use 1-indexed lines and columns and // Subtract 1 because stack traces use 1-indexed lines and columns and
// source maps uses 0-indexed. // source maps uses 0-indexed.
SourceSpan span = mapping.sourceMap SourceSpan? span = mapping.sourceMap
.spanFor(frame.line - 1, column - 1, uri: frame.uri?.toString()); .spanFor(frameLine - 1, column - 1, uri: frame.uri.toString());
// If we can't find a source span, ignore the frame. It's probably something // If we can't find a source span, ignore the frame. It's probably something
// internal that the user doesn't care about. // internal that the user doesn't care about.
@ -59,11 +60,11 @@ StackDeobfuscationResult deobfuscateStack(
List<Frame> mappedFrames = frameMap[frame] = []; List<Frame> mappedFrames = frameMap[frame] = [];
SourceFile jsFile = provider.fileFor(frame.uri); SourceFile jsFile = provider.fileFor(frame.uri);
int offset = jsFile.getOffset(frame.line - 1, column - 1); int offset = jsFile.getOffset(frameLine - 1, column - 1);
String nameOf(id) => String nameOf(id) =>
_normalizeName(id >= 0 ? mapping.sourceMap.names[id] : null); _normalizeName(id >= 0 ? mapping.sourceMap.names[id] : null);
Uri fileName = span.sourceUrl; Uri? fileName = span.sourceUrl;
int targetLine = span.start.line + 1; int targetLine = span.start.line + 1;
int targetColumn = span.start.column + 1; int targetColumn = span.start.column + 1;
@ -78,13 +79,13 @@ StackDeobfuscationResult deobfuscateStack(
int depth = 0; int depth = 0;
outer: outer:
while (key >= 0) { while (key >= 0) {
for (var frame in frames[index[key]].reversed) { for (var frame in frames[index[key]]!.reversed) {
if (frame.isEmpty) break outer; if (frame.isEmpty) break outer;
if (frame.isPush) { if (frame.isPush) {
if (depth <= 0) { if (depth <= 0) {
mappedFrames.add(new Frame(fileName, targetLine, targetColumn, mappedFrames.add(new Frame(fileName!, targetLine, targetColumn,
_normalizeName(frame.inlinedMethodName) + "(inlined)")); _normalizeName(frame.inlinedMethodName) + "(inlined)"));
fileName = Uri.parse(frame.callUri); fileName = Uri.parse(frame.callUri!);
targetLine = (frame.callLine ?? 0) + 1; targetLine = (frame.callLine ?? 0) + 1;
targetColumn = (frame.callColumn ?? 0) + 1; targetColumn = (frame.callColumn ?? 0) + 1;
} else { } else {
@ -100,7 +101,8 @@ StackDeobfuscationResult deobfuscateStack(
var functionEntry = findEnclosingFunction(provider, frame.uri, offset); var functionEntry = findEnclosingFunction(provider, frame.uri, offset);
String methodName = nameOf(functionEntry?.sourceNameId ?? -1); String methodName = nameOf(functionEntry?.sourceNameId ?? -1);
mappedFrames.add(new Frame(fileName, targetLine, targetColumn, methodName)); mappedFrames
.add(new Frame(fileName!, targetLine, targetColumn, methodName));
deobfuscatedFrames.addAll(mappedFrames); deobfuscatedFrames.addAll(mappedFrames);
} }
return new StackDeobfuscationResult( return new StackDeobfuscationResult(
@ -109,6 +111,6 @@ StackDeobfuscationResult deobfuscateStack(
/// Ensure we don't use spaces in method names. At this time, they are only /// Ensure we don't use spaces in method names. At this time, they are only
/// introduced by `<anonymous function>`. /// introduced by `<anonymous function>`.
_normalizeName(String methodName) => String _normalizeName(String? methodName) =>
methodName?.replaceAll("<anonymous function>", "<anonymous>") ?? methodName?.replaceAll("<anonymous function>", "<anonymous>") ??
'<unknown>'; '<unknown>';

View file

@ -1,18 +1,19 @@
import 'dart:io'; import 'dart:io';
import 'package:source_maps/parser.dart';
import 'package:source_span/source_span.dart'; import 'package:source_span/source_span.dart';
import 'dart2js_mapping.dart'; import 'dart2js_mapping.dart';
abstract class FileProvider { abstract class FileProvider {
String sourcesFor(Uri uri); String sourcesFor(Uri uri);
SourceFile fileFor(Uri uri); SourceFile fileFor(Uri uri);
Dart2jsMapping mappingFor(Uri uri); Dart2jsMapping? mappingFor(Uri uri);
} }
class CachingFileProvider implements FileProvider { class CachingFileProvider implements FileProvider {
final Map<Uri, String> _sources = {}; final Map<Uri, String> _sources = {};
final Map<Uri, SourceFile> _files = {}; final Map<Uri, SourceFile> _files = {};
final Map<Uri, Dart2jsMapping> _mappings = {}; final Map<Uri, Dart2jsMapping?> _mappings = {};
final Logger logger; final Logger? logger;
CachingFileProvider({this.logger}); CachingFileProvider({this.logger});
@ -22,7 +23,7 @@ class CachingFileProvider implements FileProvider {
SourceFile fileFor(Uri uri) => SourceFile fileFor(Uri uri) =>
_files[uri] ??= new SourceFile.fromString(sourcesFor(uri)); _files[uri] ??= new SourceFile.fromString(sourcesFor(uri));
Dart2jsMapping mappingFor(Uri uri) => Dart2jsMapping? mappingFor(Uri uri) =>
_mappings[uri] ??= parseMappingFor(uri, logger: logger); _mappings[uri] ??= parseMappingFor(uri, logger: logger);
} }
@ -44,7 +45,7 @@ class DownloadedFileProvider extends CachingFileProvider {
SourceFile fileFor(Uri uri) => super.fileFor(_localize(uri)); SourceFile fileFor(Uri uri) => super.fileFor(_localize(uri));
Dart2jsMapping mappingFor(Uri uri) => super.mappingFor(_localize(uri)); Dart2jsMapping? mappingFor(Uri uri) => super.mappingFor(_localize(uri));
} }
class Logger { class Logger {
@ -57,3 +58,5 @@ class Logger {
} }
var logger = Logger(); var logger = Logger();
SingleMapping parseSingleMapping(Map json) => parseJson(json) as SingleMapping;

View file

@ -10,4 +10,4 @@ dependencies:
source_span: any source_span: any
stack_trace: ^1.9.3 stack_trace: ^1.9.3
environment: environment:
sdk: '>=2.3.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'