| // 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.group(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.group(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.group(1); |
| var suffix = match.group(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.group(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.group(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(), |
| ]; |