// 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. // @dart = 2.7 import 'dart:async'; import 'dart:io'; import 'dart:convert'; import 'package:args/args.dart'; import 'package:async_helper/async_helper.dart'; import 'package:compiler/src/commandline_options.dart'; import 'package:dart2js_tools/src/dart2js_mapping.dart'; import '../helpers/d8_helper.dart'; import '../helpers/memory_compiler.dart'; import 'package:expect/expect.dart'; void main(List args) { ArgParser argParser = new ArgParser(allowTrailingOptions: true); argParser.addFlag('continued', abbr: 'c', defaultsTo: false); ArgResults argResults = argParser.parse(args); Directory dataDir = new Directory.fromUri(Platform.script.resolve('minified')); print('Input folder: ${dataDir.uri}'); asyncTest(() async { bool continuing = false; await for (FileSystemEntity entity in dataDir.list()) { String name = entity.uri.pathSegments.last; if (!name.endsWith('.dart')) continue; if (argResults.rest.isNotEmpty && !argResults.rest.contains(name) && !continuing) { continue; } print('\ninput $name'); await runTest(await new File.fromUri(entity.uri).readAsString()); if (argResults['continued']) continuing = true; } }); } // Object to hold the expectations of a individual minified-name test. class MinifiedNameTest { /// Pattern used to find a minified name in the error message. final RegExp pattern; /// The kind of minified name, it can be global, instance, or other /// (the first two correspond to the two namespaces that contain extra data in /// the source-map file). final String _kind; /// Whether the minified name is from the global namespace. bool get isGlobal => _kind == 'global'; /// Whether the minified name is from the instance namespace. bool get isInstance => _kind == 'instance'; /// The deobfuscated name we expect to find. final String expectedName; /// The actual test code. final String code; MinifiedNameTest(this.pattern, this._kind, this.expectedName, this.code); } RegExp _patternMatcher = new RegExp("// Error pattern: (.*)\n"); RegExp _kindMatcher = new RegExp("// Kind of minified name: (.*)\n"); RegExp _nameMatcher = new RegExp("// Expected deobfuscated name: (.*)\n"); Future runTest(String code) async { var patternMatch = _patternMatcher.firstMatch(code); Expect.isNotNull(patternMatch, "Could not find the error pattern."); var pattern = new RegExp(patternMatch.group(1)); var kindMatch = _kindMatcher.firstMatch(code); Expect.isNotNull(kindMatch, "Could not find the expected minified kind."); var kind = kindMatch.group(1); // TODO(sigmund): add support for "other" when we encode symbol information // directly for each field and local variable. const validKinds = const ['global', 'instance']; Expect.isTrue(validKinds.contains(kind), "Invalid kind: $kind, please use one of $validKinds"); var nameMatch = _nameMatcher.firstMatch(code); Expect.isNotNull(nameMatch, "Could not find the expected deobfuscated name."); var expectedName = nameMatch.group(1); var test = new MinifiedNameTest(pattern, kind, expectedName, code); print('expectations: ${pattern.pattern} $kind $expectedName'); await checkExpectation(test, false); await checkExpectation(test, true); } checkExpectation(MinifiedNameTest test, bool minified) async { print('-- ${minified ? 'minified' : 'not-minified'}:'); var options = [ Flags.testMode, '--libraries-spec=$sdkLibrariesSpecificationUri', if (minified) Flags.minify, ]; D8Result result = await runWithD8( memorySourceFiles: {'main.dart': test.code}, options: options); String stdout = result.runResult.stdout; String error = _extractError(stdout); print(' error: $error'); Expect.isNotNull(error, 'Couldn\'t find the error message in $stdout'); var match = test.pattern.firstMatch(error); Expect.isNotNull( match, 'Error didn\'t match the test pattern' '\nerror: $error\npattern:${test.pattern}'); var name = match.group(1); print(' obfuscated-name: $name'); Expect.isNotNull(name, 'Error didn\'t contain a name\nerror: $error'); if (name.startsWith('minified:')) { name = name.substring(9); } var sourceMap = '${result.outputPath}.map'; var json = jsonDecode(await new File(sourceMap).readAsString()); var mapping = Dart2jsMapping.json(json); Expect.isTrue(mapping.globalNames.isNotEmpty, "Source-map doesn't contain minified-names"); var actualName; if (test.isGlobal) { actualName = mapping.globalNames[name]; Expect.isNotNull(actualName, "'$name' not in global name map"); } else if (test.isInstance) { // In non-minified mode some errors show the original name // rather than the selector name (e.g. m1 instead of m1$0 in a // NoSuchMethodError), and because of that the name might not be on the // table. // // TODO(sigmund): consider making all errors show the internal name, or // include a marker to make it easier to distinguish. actualName = mapping.instanceNames[name] ?? name; } else { Expect.fail('unexpected'); } print(' actual-name: $actualName'); Expect.equals(test.expectedName, actualName); print(' PASS!!'); } /// Returns the portion of the output that corresponds to the error message. /// /// Note: some errors can span multiple lines. String _extractError(String stdout) { var firstStackFrame = stdout.indexOf('\n at'); if (firstStackFrame == -1) return null; var errorMarker = stdout.indexOf('^') + 1; return stdout.substring(errorMarker, firstStackFrame).trim(); }