| // 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. | |
| library; | |
| 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; | |
| Match? lastMatch; | |
| var result = StringBuffer(); | |
| for (var match in _matcher.allMatches(error)) { | |
| var decodedMatch = _decodeInternal(match, mapping, line, entry); | |
| if (decodedMatch == null) { | |
| continue; | |
| } | |
| result.write(error.substring(lastMatch?.end ?? 0, match.start)); | |
| result.write(decodedMatch); | |
| lastMatch = match; | |
| } | |
| if (lastMatch == null) return null; | |
| result.write(error.substring(lastMatch.end, error.length)); | |
| return '$result'; | |
| } | |
| String? _decodeInternal( | |
| Match match, | |
| Dart2jsMapping? mapping, | |
| StackTraceLine? line, | |
| TargetEntry? entry, | |
| ); | |
| } | |
| typedef ErrorDecoder = | |
| String Function( | |
| Match match, | |
| Dart2jsMapping mapping, | |
| StackTraceLine line, | |
| TargetEntry entry, | |
| ); | |
| class MinifiedNameDecoder extends ErrorMapDecoder { | |
| @override | |
| final RegExp _matcher = RegExp("minified:([a-zA-Z0-9_\$]*)"); | |
| @override | |
| String? _decodeInternal( | |
| Match match, | |
| Dart2jsMapping? mapping, | |
| StackTraceLine? line, | |
| TargetEntry? entry, | |
| ) { | |
| var minifiedName = match[1]; | |
| return mapping!.globalNames[minifiedName]; | |
| } | |
| } | |
| class CannotReadPropertyDecoder extends ErrorMapDecoder { | |
| @override | |
| final RegExp _matcher = RegExp("Cannot read property '([^']*)' of"); | |
| @override | |
| String? _decodeInternal( | |
| Match match, | |
| Dart2jsMapping? mapping, | |
| StackTraceLine? line, | |
| TargetEntry? entry, | |
| ) { | |
| var minifiedName = match[1]; | |
| var name = mapping!.instanceNames[minifiedName]; | |
| if (name == null) return null; | |
| return "Cannot read property '$name' of"; | |
| } | |
| } | |
| abstract class NoSuchMethodDecoderBase extends ErrorMapDecoder { | |
| String? _translateMinifiedName(Dart2jsMapping mapping, String? minifiedName) { | |
| var name = mapping.instanceNames[minifiedName]; | |
| if (name != null) return "'$name'"; | |
| if (minifiedName!.startsWith(RegExp(r'(call)?\$[0-9]'))) { | |
| int first$ = minifiedName.indexOf(r'$'); | |
| return _expandCallSignature(minifiedName.substring(first$)); | |
| } | |
| return null; | |
| } | |
| String? _expandCallSignature(String callSignature) { | |
| // Minified names are one of these forms: | |
| // $0 // positional arguments only | |
| // $1$2 // type parameters and positional arguments | |
| // $3$name // positional and named arguments | |
| // $1$3$name // type parameters and positional and named args | |
| var signature = callSignature.split(r'$'); | |
| int? typeArgs; | |
| int? totalArgs; | |
| var namedArgs = <String>[]; | |
| for (var arg in signature) { | |
| if (arg == "") continue; | |
| var count = int.tryParse(arg); | |
| if (count != null) { | |
| if (totalArgs != null) { | |
| if (typeArgs != null) { | |
| // unexpected format, leave it unchanged. | |
| return null; | |
| } | |
| typeArgs = totalArgs; | |
| } | |
| totalArgs = count; | |
| } else { | |
| namedArgs.add(arg); | |
| } | |
| } | |
| if (totalArgs == null) return null; | |
| var sb = StringBuffer(); | |
| sb.write("'call'"); | |
| sb.write(" (with "); | |
| if (typeArgs != null) { | |
| sb.write("$typeArgs type arguments"); | |
| sb.write(namedArgs.isNotEmpty ? ", " : " and "); | |
| } | |
| sb.write("${totalArgs - namedArgs.length} positional arguments"); | |
| if (namedArgs.isNotEmpty) { | |
| sb.write(typeArgs != null ? "," : ""); | |
| sb.write(" and named arguments '${namedArgs.join("', '")}'"); | |
| } | |
| sb.write(')'); | |
| return "$sb"; | |
| } | |
| } | |
| class NoSuchMethodDecoder1 extends NoSuchMethodDecoderBase { | |
| @override | |
| final RegExp _matcher = RegExp( | |
| "NoSuchMethodError: method not found: '([^']*)'( on [^\\(]*)? \\(.*\\)", | |
| ); | |
| @override | |
| String? _decodeInternal( | |
| Match match, | |
| Dart2jsMapping? mapping, | |
| StackTraceLine? line, | |
| TargetEntry? entry, | |
| ) { | |
| var minifiedName = match[1]; | |
| var suffix = match[2] ?? ''; | |
| var name = _translateMinifiedName(mapping!, minifiedName); | |
| if (name == null) return null; | |
| return "NoSuchMethodError: method not found: $name$suffix"; | |
| } | |
| } | |
| class NoSuchMethodDecoder2 extends NoSuchMethodDecoderBase { | |
| @override | |
| final RegExp _matcher = RegExp( | |
| "NoSuchMethodError: method not found: '([^']*)'", | |
| ); | |
| @override | |
| String? _decodeInternal( | |
| Match match, | |
| Dart2jsMapping? mapping, | |
| StackTraceLine? line, | |
| TargetEntry? entry, | |
| ) { | |
| var minifiedName = match[1]; | |
| var name = _translateMinifiedName(mapping!, minifiedName); | |
| if (name == null) return null; | |
| return "NoSuchMethodError: method not found: $name"; | |
| } | |
| } | |
| class UnhandledNotAFunctionError extends ErrorMapDecoder { | |
| @override | |
| final RegExp _matcher = RegExp("Error: ([^']*) is not a function"); | |
| @override | |
| String? _decodeInternal( | |
| Match match, | |
| Dart2jsMapping? mapping, | |
| StackTraceLine? line, | |
| TargetEntry? entry, | |
| ) { | |
| var minifiedName = match[1]; | |
| var name = mapping!.instanceNames[minifiedName]; | |
| if (name == null) return null; | |
| return "Error: $name is not a function"; | |
| } | |
| } | |
| List<ErrorMapDecoder> _errorMapDecoders = [ | |
| MinifiedNameDecoder(), | |
| CannotReadPropertyDecoder(), | |
| NoSuchMethodDecoder1(), | |
| NoSuchMethodDecoder2(), | |
| UnhandledNotAFunctionError(), | |
| ]; |