Version 2.12.0-132.0.dev
Merge commit 'c549ecf1e6900be9a49e7ba5931688a326666c40' into 'dev'
diff --git a/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart b/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart
index c4ff40d..5924d4a 100644
--- a/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/combined_member_signature.dart
@@ -301,7 +301,15 @@
"No member computed for index ${index} in ${members}");
candidateType = _computeMemberType(thisType, target);
if (!classBuilder.library.isNonNullableByDefault) {
- DartType legacyErasure = rawLegacyErasure(candidateType);
+ DartType legacyErasure;
+ if (target == hierarchy.coreTypes.objectEquals) {
+ // In legacy code we special case `Object.==` to infer `dynamic`
+ // instead `Object!`.
+ legacyErasure = new FunctionType([const DynamicType()],
+ hierarchy.coreTypes.boolLegacyRawType, Nullability.legacy);
+ } else {
+ legacyErasure = rawLegacyErasure(candidateType);
+ }
if (legacyErasure != null) {
_neededLegacyErasureIndices ??= {};
_neededLegacyErasureIndices.add(index);
@@ -611,11 +619,6 @@
for (int i = 0; i < function.positionalParameters.length; i++) {
VariableDeclaration parameter = function.positionalParameters[i];
DartType parameterType = functionType.positionalParameters[i];
- if (i == 0 && procedure == hierarchy.coreTypes.objectEquals) {
- // In legacy code we special case `Object.==` to infer `dynamic`
- // instead `Object!`.
- parameterType = const DynamicType();
- }
positionalParameters.add(new VariableDeclaration(parameter.name,
type: parameterType, isCovariant: parameter.isCovariant)
..isGenericCovariantImpl = parameter.isGenericCovariantImpl);
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart b/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart
index 8caaedc..f10419d 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_collection_builders.dart
@@ -53,6 +53,18 @@
entries = spread.entries;
} else if (spread is SetConstant) {
entries = spread.entries;
+ } else if (evaluator.backend.isLoweredListConstant(spread)) {
+ entries = <Constant>[];
+ evaluator.backend.forEachLoweredListConstantElement(spread,
+ (Constant element) {
+ entries.add(element);
+ });
+ } else if (evaluator.backend.isLoweredSetConstant(constant)) {
+ entries = <Constant>[];
+ evaluator.backend.forEachLoweredSetConstantElement(spread,
+ (Constant element) {
+ entries.add(element);
+ });
} else {
// Not list or set in spread
return evaluator.createErrorConstant(
@@ -176,26 +188,7 @@
// Fully evaluated
List<Constant> entries = parts.single;
SetConstant result = new SetConstant(elementType, entries);
- if (evaluator.desugarSets) {
- final List<ConstantMapEntry> mapEntries =
- new List<ConstantMapEntry>.filled(entries.length, null);
- for (int i = 0; i < entries.length; ++i) {
- mapEntries[i] =
- new ConstantMapEntry(entries[i], evaluator.nullConstant);
- }
- Constant map = evaluator.lowerMapConstant(
- new MapConstant(elementType, const NullType(), mapEntries));
- return evaluator.lower(
- result,
- new InstanceConstant(
- evaluator.unmodifiableSetMap.enclosingClass.reference, [
- elementType
- ], <Reference, Constant>{
- evaluator.unmodifiableSetMap.getterReference: map
- }));
- } else {
- return evaluator.lowerSetConstant(result);
- }
+ return evaluator.lowerSetConstant(result);
}
List<Expression> sets = <Expression>[];
for (Object part in parts) {
@@ -271,6 +264,13 @@
entry.key, entry.value, spreadExpression, spreadExpression);
if (error != null) return error;
}
+ } else if (evaluator.backend.isLoweredMapConstant(spread)) {
+ AbortConstant error;
+ evaluator.backend.forEachLoweredMapConstantEntry(spread,
+ (Constant key, Constant value) {
+ error ??= addConstant(key, value, spreadExpression, spreadExpression);
+ });
+ if (error != null) return error;
} else {
// Not map in spread
return evaluator.createErrorConstant(
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index c0c6a16..a92f302 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -97,7 +97,6 @@
transformLibraries(component.libraries, backend, environmentDefines,
typeEnvironment, errorReporter, evaluationMode,
- desugarSets: desugarSets,
enableTripleShift: enableTripleShift,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluateAnnotations: evaluateAnnotations);
@@ -112,18 +111,15 @@
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{bool evaluateAnnotations,
- bool desugarSets,
bool enableTripleShift,
bool errorOnUnevaluatedConstant}) {
assert(evaluateAnnotations != null);
- assert(desugarSets != null);
assert(enableTripleShift != null);
assert(errorOnUnevaluatedConstant != null);
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
backend,
environmentDefines,
evaluateAnnotations,
- desugarSets,
enableTripleShift,
errorOnUnevaluatedConstant,
typeEnvironment,
@@ -143,18 +139,15 @@
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{bool evaluateAnnotations: true,
- bool desugarSets: false,
bool enableTripleShift: false,
bool errorOnUnevaluatedConstant: false}) {
assert(evaluateAnnotations != null);
- assert(desugarSets != null);
assert(enableTripleShift != null);
assert(errorOnUnevaluatedConstant != null);
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
backend,
environmentDefines,
evaluateAnnotations,
- desugarSets,
enableTripleShift,
errorOnUnevaluatedConstant,
typeEnvironment,
@@ -326,7 +319,6 @@
StaticTypeContext _staticTypeContext;
final bool evaluateAnnotations;
- final bool desugarSets;
final bool enableTripleShift;
final bool errorOnUnevaluatedConstant;
@@ -334,7 +326,6 @@
this.backend,
Map<String, String> environmentDefines,
this.evaluateAnnotations,
- this.desugarSets,
this.enableTripleShift,
this.errorOnUnevaluatedConstant,
this.typeEnvironment,
@@ -342,7 +333,6 @@
EvaluationMode evaluationMode)
: constantEvaluator = new ConstantEvaluator(
backend, environmentDefines, typeEnvironment, errorReporter,
- desugarSets: desugarSets,
enableTripleShift: enableTripleShift,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluationMode: evaluationMode);
@@ -759,9 +749,6 @@
final ErrorReporter errorReporter;
final EvaluationMode evaluationMode;
- final bool desugarSets;
- final Field unmodifiableSetMap;
-
final bool enableTripleShift;
final bool Function(DartType) isInstantiated =
@@ -796,19 +783,14 @@
ConstantEvaluator(this.backend, this.environmentDefines, this.typeEnvironment,
this.errorReporter,
- {this.desugarSets = false,
- this.enableTripleShift = false,
+ {this.enableTripleShift = false,
this.errorOnUnevaluatedConstant = false,
this.evaluationMode: EvaluationMode.weak})
: numberSemantics = backend.numberSemantics,
coreTypes = typeEnvironment.coreTypes,
canonicalizationCache = <Constant, Constant>{},
nodeCache = <Node, Constant>{},
- env = new EvaluationEnvironment(),
- unmodifiableSetMap = desugarSets
- ? typeEnvironment.coreTypes.index
- .getMember('dart:collection', '_UnmodifiableSet', '_map')
- : null {
+ env = new EvaluationEnvironment() {
if (environmentDefines == null && !backend.supportsUnevaluatedConstants) {
throw new ArgumentError(
"No 'environmentDefines' passed to the constant evaluator but the "
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
index de75625..5f55924 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
@@ -1247,7 +1247,6 @@
new KernelConstantErrorReporter(loader),
evaluationMode,
evaluateAnnotations: true,
- desugarSets: !backendTarget.supportsSetLiterals,
enableTripleShift:
isExperimentEnabledGlobally(ExperimentalFlag.tripleShift),
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant);
@@ -1297,7 +1296,6 @@
new KernelConstantErrorReporter(loader),
evaluationMode,
evaluateAnnotations: true,
- desugarSets: !backendTarget.supportsSetLiterals,
enableTripleShift:
isExperimentEnabledGlobally(ExperimentalFlag.tripleShift),
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant);
diff --git a/pkg/front_end/test/constant_evaluator_benchmark.dart b/pkg/front_end/test/constant_evaluator_benchmark.dart
index c152aa2..f0e722c 100644
--- a/pkg/front_end/test/constant_evaluator_benchmark.dart
+++ b/pkg/front_end/test/constant_evaluator_benchmark.dart
@@ -99,7 +99,6 @@
new SilentErrorReporter(),
evaluationMode,
evaluateAnnotations: true,
- desugarSets: !target.backendTarget.supportsSetLiterals,
enableTripleShift: target
.isExperimentEnabledGlobally(ExperimentalFlag.tripleShift),
errorOnUnevaluatedConstant:
diff --git a/pkg/front_end/test/crashing_test_case_minimizer.dart b/pkg/front_end/test/crashing_test_case_minimizer.dart
index 8d0fcc6..ea8ac64 100644
--- a/pkg/front_end/test/crashing_test_case_minimizer.dart
+++ b/pkg/front_end/test/crashing_test_case_minimizer.dart
@@ -2,90 +2,11 @@
// 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:async' show Future, StreamSubscription;
+import 'dart:convert' show jsonDecode;
-import 'dart:convert' show JsonEncoder, jsonDecode, utf8;
+import 'dart:io' show File;
-import 'dart:io' show BytesBuilder, File, stdin, stdout;
-import 'dart:math' show max;
-
-import 'dart:typed_data' show Uint8List;
-
-import 'package:_fe_analyzer_shared/src/parser/parser.dart' show Parser;
-
-import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
- show ErrorToken, ScannerConfiguration, Token;
-
-import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
-
-import 'package:dev_compiler/src/kernel/target.dart' show DevCompilerTarget;
-
-import 'package:front_end/src/api_prototype/compiler_options.dart'
- show CompilerOptions, DiagnosticMessage;
-
-import 'package:front_end/src/api_prototype/experimental_flags.dart'
- show ExperimentalFlag;
-
-import 'package:front_end/src/api_prototype/file_system.dart'
- show FileSystem, FileSystemEntity, FileSystemException;
-
-import 'package:front_end/src/base/processed_options.dart'
- show ProcessedOptions;
-
-import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
-
-import 'package:front_end/src/fasta/incremental_compiler.dart'
- show IncrementalCompiler;
-
-import 'package:front_end/src/fasta/kernel/utils.dart' show ByteSink;
-import 'package:front_end/src/fasta/util/direct_parser_ast.dart';
-import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
-
-import 'package:front_end/src/fasta/util/textual_outline.dart'
- show textualOutline;
-
-import 'package:kernel/ast.dart' show Component;
-
-import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
-
-import 'package:kernel/target/targets.dart' show Target, TargetFlags;
-import 'package:package_config/package_config.dart';
-
-import "package:vm/target/flutter.dart" show FlutterTarget;
-
-import "package:vm/target/vm.dart" show VmTarget;
-
-import 'incremental_load_from_dill_suite.dart' show getOptions;
-
-import 'parser_test_listener.dart' show ParserTestListener;
-
-import 'parser_suite.dart' as parser_suite;
-
-final FakeFileSystem fs = new FakeFileSystem();
-Uri mainUri;
-Uri platformUri;
-bool noPlatform = false;
-bool nnbd = false;
-bool experimentalInvalidation = false;
-bool serialize = false;
-bool widgetTransformation = false;
-List<Uri> invalidate = [];
-String targetString = "VM";
-String expectedCrashLine;
-bool oldBlockDelete = false;
-bool lineDelete = false;
-bool byteDelete = false;
-bool askAboutRedirectCrashTarget = false;
-int stackTraceMatches = 1;
-Set<String> askedAboutRedirect = {};
-bool _quit = false;
-bool skip = false;
-
-Future<bool> shouldQuit() async {
- // allow some time for stdin.listen to process data.
- await new Future.delayed(new Duration(milliseconds: 5));
- return _quit;
-}
+import 'crashing_test_case_minimizer_impl.dart';
// TODO(jensj): Option to automatically find and search for _all_ crashes that
// it uncovers --- i.e. it currently has an option to ask if we want to search
@@ -93,1727 +14,94 @@
// for everything it sees. One can possibly just make a copy of the state of
// the file system and save that for later...
+// TODO(jensj): Add asserts or similar where - after each rewrite - we run the
+// parser on it and verifies that no syntax errors have been introduced.
+
main(List<String> arguments) async {
String filename;
- Uri loadFsJson;
+ Uri loadJson;
for (String arg in arguments) {
- if (arg.startsWith("--")) {
- if (arg == "--nnbd") {
- nnbd = true;
- } else if (arg == "--experimental-invalidation") {
- experimentalInvalidation = true;
- } else if (arg == "--serialize") {
- serialize = true;
- } else if (arg.startsWith("--fsJson=")) {
- String jsJson = arg.substring("--fsJson=".length);
- loadFsJson = Uri.base.resolve(jsJson);
- } else if (arg.startsWith("--platform=")) {
- String platform = arg.substring("--platform=".length);
- platformUri = Uri.base.resolve(platform);
- } else if (arg == "--no-platform") {
- noPlatform = true;
- } else if (arg.startsWith("--invalidate=")) {
- for (String s in arg.substring("--invalidate=".length).split(",")) {
- invalidate.add(Uri.base.resolve(s));
- }
- } else if (arg.startsWith("--widgetTransformation")) {
- widgetTransformation = true;
- } else if (arg.startsWith("--target=VM")) {
- targetString = "VM";
- } else if (arg.startsWith("--target=flutter")) {
- targetString = "flutter";
- } else if (arg.startsWith("--target=ddc")) {
- targetString = "ddc";
- } else if (arg == "--oldBlockDelete") {
- oldBlockDelete = true;
- } else if (arg == "--lineDelete") {
- lineDelete = true;
- } else if (arg == "--byteDelete") {
- byteDelete = true;
- } else if (arg == "--ask-redirect-target") {
- askAboutRedirectCrashTarget = true;
- } else if (arg.startsWith("--stack-matches=")) {
- String stackMatches = arg.substring("--stack-matches=".length);
- stackTraceMatches = int.parse(stackMatches);
- } else {
- throw "Unknown option $arg";
- }
- } else if (filename != null) {
- throw "Already got '$filename', '$arg' is also a filename; "
- "can only get one";
- } else {
- filename = arg;
- }
- }
- if (noPlatform) {
- int i = 0;
- while (platformUri == null || new File.fromUri(platformUri).existsSync()) {
- platformUri = Uri.base.resolve("nonexisting_$i");
- i++;
- }
- } else {
- if (platformUri == null) {
- throw "No platform given. Use --platform=/path/to/platform.dill";
- }
- if (!new File.fromUri(platformUri).existsSync()) {
- throw "The platform file '$platformUri' doesn't exist";
- }
- }
- if (filename == null) {
- throw "Need file to operate on";
- }
- File file = new File(filename);
- if (!file.existsSync()) throw "File $filename doesn't exist.";
- mainUri = file.absolute.uri;
-
- try {
- await tryToMinimize(loadFsJson);
- } catch (e) {
- print("\n\n\nABOUT TO CRASH. DUMPING FS.");
- dumpFsToJson();
- print("\n\n\nABOUT TO CRASH. FS DUMPED.");
- rethrow;
- }
-}
-
-Future tryToMinimize(Uri loadFsJson) async {
- // Set main to be basically empty up front.
- fs.data[mainUri] = utf8.encode("main() {}");
- Component initialComponent = await getInitialComponent();
- print("Compiled initially (without data)");
- // Remove fake cache.
- fs.data.remove(mainUri);
-
- if (loadFsJson != null) {
- File f = new File.fromUri(loadFsJson);
- fs.initializeFromJson((jsonDecode(f.readAsStringSync())));
- }
-
- // First assure it actually crash on the input.
- if (!await crashesOnCompile(initialComponent)) {
- throw "Input doesn't crash the compiler.";
- }
- print("Step #1: We did crash on the input!");
-
- // All file should now be cached.
- fs._redirectAndRecord = false;
-
- try {
- stdin.echoMode = false;
- stdin.lineMode = false;
- } catch (e) {
- print("error setting settings on stdin");
- }
- StreamSubscription<List<int>> stdinSubscription =
- stdin.listen((List<int> event) {
- if (event.length == 1 && event.single == "q".codeUnits.single) {
- print("\n\nGot told to quit!\n\n");
- _quit = true;
- } else if (event.length == 1 && event.single == "s".codeUnits.single) {
- print("\n\nGot told to skip!\n\n");
- skip = true;
- } else if (event.length == 1 && event.single == "i".codeUnits.single) {
- print("\n\n--- STATUS INFORMATION START ---\n\n");
- int totalFiles = 0;
- int emptyFiles = 0;
- int combinedSize = 0;
- for (Uri uri in fs.data.keys) {
- final Uint8List originalBytes = fs.data[uri];
- if (originalBytes == null) continue;
- totalFiles++;
- if (originalBytes.isEmpty) emptyFiles++;
- combinedSize += originalBytes.length;
- }
- print("Total files left: $totalFiles.");
- print("Of which empty: $emptyFiles.");
- print("Combined size left: $combinedSize bytes.");
- print("\n\n--- STATUS INFORMATION END ---\n\n");
- skip = true;
- } else {
- print("\n\nGot stdin input: $event\n\n");
- }
- });
-
- // For all dart files: Parse them as set their source as the parsed source
- // to "get around" any encoding issues when printing later.
- Map<Uri, Uint8List> copy = new Map.from(fs.data);
- for (Uri uri in fs.data.keys) {
- if (await shouldQuit()) break;
- String uriString = uri.toString();
- if (uriString.endsWith(".json") ||
- uriString.endsWith(".packages") ||
- uriString.endsWith(".dill") ||
- fs.data[uri] == null ||
- fs.data[uri].isEmpty) {
- // skip
- } else {
- try {
- String parsedString = getFileAsStringContent(fs.data[uri], nnbd);
- fs.data[uri] = utf8.encode(parsedString);
- } catch (e) {
- // crash in scanner/parser --- keep original file. This crash might
- // be what we're looking for!
- }
- }
- }
- if (!await crashesOnCompile(initialComponent)) {
- // Now - for whatever reason - we didn't crash. Restore.
- fs.data.clear();
- fs.data.addAll(copy);
- }
-
- // Operate on one file at a time: Try to delete all content in file.
- List<Uri> uris = new List<Uri>.from(fs.data.keys);
-
- // TODO(jensj): Can we "thread" this?
- bool changedSome = true;
- while (changedSome) {
- if (await shouldQuit()) break;
- while (changedSome) {
- if (await shouldQuit()) break;
- changedSome = false;
- for (int i = 0; i < uris.length; i++) {
- if (await shouldQuit()) break;
- Uri uri = uris[i];
- if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
- print("About to work on file $i of ${uris.length}");
- await deleteContent(uris, i, false, initialComponent);
- if (fs.data[uri] == null || fs.data[uri].isEmpty) changedSome = true;
- }
- }
-
- // Try to delete empty files.
- bool changedSome2 = true;
- while (changedSome2) {
- if (await shouldQuit()) break;
- changedSome2 = false;
- for (int i = 0; i < uris.length; i++) {
- if (await shouldQuit()) break;
- Uri uri = uris[i];
- if (fs.data[uri] == null || fs.data[uri].isNotEmpty) continue;
- print("About to work on file $i of ${uris.length}");
- await deleteContent(uris, i, false, initialComponent, deleteFile: true);
- if (fs.data[uri] == null) {
- changedSome = true;
- changedSome2 = true;
- }
- }
- }
-
- int left = 0;
- for (Uri uri in uris) {
- if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
- left++;
- }
- print("There's now $left files of ${fs.data.length} files left");
-
- // Operate on one file at a time.
- for (Uri uri in fs.data.keys) {
- if (await shouldQuit()) break;
- if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-
- print("Now working on $uri");
-
- int prevLength = fs.data[uri].length;
-
- await deleteBlocks(uri, initialComponent);
- await deleteEmptyLines(uri, initialComponent);
-
- if (oldBlockDelete) {
- // Try to delete blocks.
- await deleteBlocksOld(uri, initialComponent);
- }
-
- if (lineDelete) {
- // Try to delete lines.
- await deleteLines(uri, initialComponent);
- }
-
- print("We're now at ${fs.data[uri].length} bytes for $uri "
- "(was $prevLength).");
- if (prevLength != fs.data[uri].length) changedSome = true;
- if (fs.data[uri].isEmpty) continue;
-
- if (byteDelete) {
- // Now try to delete 'arbitrarily' (for any given start offset do an
- // exponential binary search).
- int prevLength = fs.data[uri].length;
- while (true) {
- if (await shouldQuit()) break;
- await binarySearchDeleteData(uri, initialComponent);
-
- if (fs.data[uri].length == prevLength) {
- // No progress.
- break;
- } else {
- print("We're now at ${fs.data[uri].length} bytes");
- prevLength = fs.data[uri].length;
- changedSome = true;
- }
- }
- }
- }
- for (Uri uri in fs.data.keys) {
- if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
- if (await shouldQuit()) break;
- if (await attemptInline(uri, initialComponent)) {
- changedSome = true;
- }
- }
- }
-
- if (await shouldQuit()) {
- print("\n\nASKED TO QUIT\n\n");
- } else {
- print("\n\nDONE\n\n");
- }
-
- Uri jsonFsOut = dumpFsToJson();
-
- await stdinSubscription.cancel();
-
- if (!await shouldQuit()) {
- // Test converting to incremental compiler yaml test.
- outputIncrementalCompilerYamlTest();
- print("\n\n\n");
-
- for (Uri uri in uris) {
- if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
- print("Uri $uri has this content:");
-
- try {
- String utfDecoded = utf8.decode(fs.data[uri], allowMalformed: true);
- print(utfDecoded);
- } catch (e) {
- print(fs.data[uri]);
- print("(which crashes when trying to decode as utf8)");
- }
- print("\n\n====================\n\n");
- }
-
- print("Wrote json file system to $jsonFsOut");
- }
-}
-
-Uri dumpFsToJson() {
- JsonEncoder jsonEncoder = new JsonEncoder.withIndent(" ");
- String jsonFs = jsonEncoder.convert(fs);
- int i = 0;
- Uri jsonFsOut;
- while (jsonFsOut == null || new File.fromUri(jsonFsOut).existsSync()) {
- jsonFsOut = Uri.base.resolve("crash_minimizer_result_$i");
- i++;
- }
- new File.fromUri(jsonFsOut).writeAsStringSync(jsonFs);
- print("Wrote json file system to $jsonFsOut");
- return jsonFsOut;
-}
-
-/// Attempts to inline small files in other files.
-/// Returns true if anything was changed, i.e. if at least one inlining was a
-/// success.
-Future<bool> attemptInline(Uri uri, Component initialComponent) async {
- // Don't attempt to inline the main uri --- that's our entry!
- if (uri == mainUri) return false;
-
- Uint8List inlineData = fs.data[uri];
- bool hasMultipleLines = false;
- for (int i = 0; i < inlineData.length; i++) {
- if (inlineData[i] == $LF) {
- hasMultipleLines = true;
+ if (arg.startsWith("--json=")) {
+ String json = arg.substring("--json=".length);
+ loadJson = Uri.base.resolve(json);
break;
}
}
- // TODO(jensj): Maybe inline slightly bigger files too?
- if (hasMultipleLines) {
- return false;
- }
+ TestMinimizerSettings settings = new TestMinimizerSettings();
- Uri inlinableUri = uri;
-
- int compileTry = 0;
- bool changed = false;
-
- for (Uri uri in fs.data.keys) {
- final Uint8List originalBytes = fs.data[uri];
- if (originalBytes == null || originalBytes.isEmpty) continue;
- DirectParserASTContentCompilationUnitEnd ast = getAST(originalBytes,
- includeBody: false,
- includeComments: false,
- enableExtensionMethods: true,
- enableNonNullable: nnbd);
- // Find all imports/exports of this file (if any).
- // If finding any:
- // * remove all of them, then
- // * find the end of the last import/export, and
- // * insert the content of the file there.
- // * if that *doesn't* work and we've inserted an export,
- // try converting that to an import instead.
- List<Replacement> replacements = [];
- for (DirectParserASTContentImportEnd import in ast.getImports()) {
- Token importUriToken = import.importKeyword.next;
- Uri importUri = _getUri(importUriToken, uri);
- if (inlinableUri == importUri) {
- replacements.add(new Replacement(
- import.importKeyword.offset - 1, import.semicolon.offset + 1));
- }
- }
- for (DirectParserASTContentExportEnd export in ast.getExports()) {
- Token exportUriToken = export.exportKeyword.next;
- Uri exportUri = _getUri(exportUriToken, uri);
- if (inlinableUri == exportUri) {
- replacements.add(new Replacement(
- export.exportKeyword.offset - 1, export.semicolon.offset + 1));
- }
- }
- if (replacements.isEmpty) continue;
-
- // Step 1: Remove all imports/exports of this file.
- Uint8List candidate = _replaceRange(replacements, originalBytes);
-
- // Step 2: Find the last import/export.
- int offsetOfLast = 0;
- ast = getAST(candidate,
- includeBody: false,
- includeComments: false,
- enableExtensionMethods: true,
- enableNonNullable: nnbd);
- for (DirectParserASTContentImportEnd import in ast.getImports()) {
- offsetOfLast = max(offsetOfLast, import.semicolon.offset + 1);
- }
- for (DirectParserASTContentExportEnd export in ast.getExports()) {
- offsetOfLast = max(offsetOfLast, export.semicolon.offset + 1);
- }
-
- // Step 3: Insert the content of the file there. Note, though,
- // that any imports/exports in _that_ file should be changed to be valid
- // in regards to the new placement.
- BytesBuilder builder = new BytesBuilder();
- for (int i = 0; i < offsetOfLast; i++) {
- builder.addByte(candidate[i]);
- }
- builder.addByte($LF);
- builder.add(_rewriteImportsExportsToUri(inlineData, uri, inlinableUri));
- builder.addByte($LF);
- for (int i = offsetOfLast; i < candidate.length; i++) {
- builder.addByte(candidate[i]);
- }
- candidate = builder.takeBytes();
-
- // Step 4: Try it out.
- if (await shouldQuit()) break;
- if (skip) {
- skip = false;
- break;
- }
- stdout.write(".");
- compileTry++;
- if (compileTry % 50 == 0) {
- stdout.write("(at $compileTry)\n");
- }
- fs.data[uri] = candidate;
- if (await crashesOnCompile(initialComponent)) {
- print("Could inline $inlinableUri into $uri.");
- changed = true;
- // File was already updated.
- } else {
- // Couldn't replace that.
- // Insert the original again.
- fs.data[uri] = originalBytes;
-
- // If we've inlined an export, try changing that to an import.
- builder = new BytesBuilder();
- for (int i = 0; i < offsetOfLast; i++) {
- builder.addByte(candidate[i]);
- }
- // TODO(jensj): Only try compile again, if export was actually converted
- // to import.
- builder.addByte($LF);
- builder.add(_rewriteImportsExportsToUri(inlineData, uri, inlinableUri,
- convertExportToImport: true));
- builder.addByte($LF);
- for (int i = offsetOfLast; i < candidate.length; i++) {
- builder.addByte(candidate[i]);
- }
- candidate = builder.takeBytes();
-
- // Step 4: Try it out.
- if (await shouldQuit()) break;
- if (skip) {
- skip = false;
- break;
- }
- stdout.write(".");
- compileTry++;
- if (compileTry % 50 == 0) {
- stdout.write("(at $compileTry)\n");
- }
- fs.data[uri] = candidate;
- if (await crashesOnCompile(initialComponent)) {
- print("Could inline $inlinableUri into $uri "
- "(by converting export to import).");
- changed = true;
- // File was already updated.
- } else {
- // Couldn't replace that.
- // Insert the original again.
- fs.data[uri] = originalBytes;
- }
- }
- }
-
- return changed;
-}
-
-Uint8List _rewriteImportsExportsToUri(Uint8List oldData, Uri newUri, Uri oldUri,
- {bool convertExportToImport: false}) {
- DirectParserASTContentCompilationUnitEnd ast = getAST(oldData,
- includeBody: false,
- includeComments: false,
- enableExtensionMethods: true,
- enableNonNullable: nnbd);
- List<Replacement> replacements = [];
- for (DirectParserASTContentImportEnd import in ast.getImports()) {
- _rewriteImportsExportsToUriInternal(
- import.importKeyword.next, oldUri, replacements, newUri);
- }
- for (DirectParserASTContentExportEnd export in ast.getExports()) {
- if (convertExportToImport) {
- replacements.add(new Replacement(
- export.exportKeyword.offset - 1,
- export.exportKeyword.offset + export.exportKeyword.length,
- nullOrReplacement: utf8.encode('import'),
- ));
- }
- _rewriteImportsExportsToUriInternal(
- export.exportKeyword.next, oldUri, replacements, newUri);
- }
- if (replacements.isNotEmpty) {
- Uint8List candidate = _replaceRange(replacements, oldData);
- return candidate;
- }
- return oldData;
-}
-
-void _rewriteImportsExportsToUriInternal(
- Token uriToken, Uri oldUri, List<Replacement> replacements, Uri newUri) {
- Uri tokenUri = _getUri(uriToken, oldUri, resolvePackage: false);
- if (tokenUri.scheme == "package" || tokenUri.scheme == "dart") return;
- Uri asPackageUri = _getImportUri(tokenUri);
- if (asPackageUri.scheme == "package") {
- // Just replace with this package uri.
- replacements.add(new Replacement(
- uriToken.offset - 1,
- uriToken.offset + uriToken.length,
- nullOrReplacement: utf8.encode('"${asPackageUri.toString()}"'),
- ));
+ if (loadJson != null) {
+ File f = new File.fromUri(loadJson);
+ settings.initializeFromJson((jsonDecode(f.readAsStringSync())));
} else {
- // TODO(jensj): Rewrite relative path to be correct.
- throw "Rewrite $oldUri importing/exporting $tokenUri as $uriToken "
- "for $newUri (notice $asPackageUri)";
- }
-}
-
-Uri _getUri(Token uriToken, Uri uri, {bool resolvePackage: true}) {
- String uriString = uriToken.lexeme;
- uriString = uriString.substring(1, uriString.length - 1);
- Uri uriTokenUri = uri.resolve(uriString);
- if (resolvePackage && uriTokenUri.scheme == "package") {
- Package package = _latestIncrementalCompiler
- .currentPackagesMap[uriTokenUri.pathSegments.first];
- uriTokenUri = package.packageUriRoot
- .resolve(uriTokenUri.pathSegments.skip(1).join("/"));
- }
- return uriTokenUri;
-}
-
-Uri _getImportUri(Uri uri) {
- return _latestIncrementalCompiler.userCode
- .getEntryPointUri(uri, issueProblem: false);
-}
-
-void outputIncrementalCompilerYamlTest() {
- int dartFiles = 0;
- for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
- if (entry.key.pathSegments.last.endsWith(".dart")) {
- if (entry.value != null) dartFiles++;
- }
- }
-
- print("------ Reproduction as semi-done incremental yaml test file ------");
-
- // TODO(jensj): don't use full uris.
- print("""
-# Copyright (c) 2020, 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.md file.
-
-# Reproduce a crash.
-
-type: newworld""");
- if (widgetTransformation) {
- print("trackWidgetCreation: true");
- print("target: DDC # basically needed for widget creation to be run");
- }
- print("""
-worlds:
- - entry: $mainUri""");
- if (experimentalInvalidation) {
- print(" experiments: alternative-invalidation-strategy");
- }
- print(" sources:");
- for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
- if (entry.value == null) continue;
- print(" ${entry.key}: |");
- String string = utf8.decode(entry.value);
- List<String> lines = string.split("\n");
- for (String line in lines) {
- print(" $line");
- }
- }
- print(" expectedLibraryCount: $dartFiles "
- "# with parts this is not right");
- print("");
-
- for (Uri uri in invalidate) {
- print(" - entry: $mainUri");
- if (experimentalInvalidation) {
- print(" experiments: alternative-invalidation-strategy");
- }
- print(" worldType: updated");
- print(" expectInitializeFromDill: false # or true?");
- print(" invalidate:");
- print(" - $uri");
- print(" expectedLibraryCount: $dartFiles "
- "# with parts this is not right");
- print(" expectsRebuildBodiesOnly: true # or false?");
- print("");
- }
-
- print("------------------------------------------------------------------");
-}
-
-Uint8List sublist(Uint8List data, int start, int end) {
- Uint8List result = new Uint8List(end - start);
- result.setRange(0, result.length, data, start);
- return result;
-}
-
-String dataToText(Uint8List data) {
- StringBuffer sb = new StringBuffer();
- String comma = "[";
- for (int i = 0; i < data.length; i++) {
- sb.write(comma);
- sb.write(data[i]);
- comma = ", ";
- if (i > 100) break;
- }
- if (data.length > 100) {
- sb.write("...");
- }
- sb.write("]");
- return sb.toString();
-}
-
-void binarySearchDeleteData(Uri uri, Component initialComponent) async {
- Uint8List latestCrashData = fs.data[uri];
- int offset = 0;
- while (offset < latestCrashData.length) {
- print("Working at offset $offset of ${latestCrashData.length}");
- BytesBuilder builder = new BytesBuilder();
- builder.add(sublist(latestCrashData, 0, offset));
- builder.add(sublist(latestCrashData, offset + 1, latestCrashData.length));
- Uint8List candidate = builder.takeBytes();
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- // Deleting 1 char didn't crash; don't try to delete anymore starting
- // here.
- offset++;
- continue;
- }
-
- // Find how long we can go.
- int crashingAt = 1;
- int noLongerCrashingAt;
- while (true) {
- int deleteChars = 2 * crashingAt;
- if (offset + deleteChars > latestCrashData.length) {
- deleteChars = latestCrashData.length - offset;
- }
- builder = new BytesBuilder();
- builder.add(sublist(latestCrashData, 0, offset));
- builder.add(sublist(
- latestCrashData, offset + deleteChars, latestCrashData.length));
- candidate = builder.takeBytes();
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- noLongerCrashingAt = deleteChars;
- break;
- }
- crashingAt = deleteChars;
- if (crashingAt + offset == latestCrashData.length) break;
- }
-
- if (noLongerCrashingAt == null) {
- // We can delete the rest.
- latestCrashData = candidate;
- continue;
- }
-
- // Binary search between [crashingAt] and [noLongerCrashingAt].
- while (crashingAt < noLongerCrashingAt) {
- int mid = noLongerCrashingAt -
- ((noLongerCrashingAt - crashingAt) >> 1); // Get middle, rounding up.
- builder = new BytesBuilder();
- builder.add(sublist(latestCrashData, 0, offset));
- builder
- .add(sublist(latestCrashData, offset + mid, latestCrashData.length));
- candidate = builder.takeBytes();
- fs.data[uri] = candidate;
- if (await crashesOnCompile(initialComponent)) {
- crashingAt = mid;
- } else {
- // [noLongerCrashingAt] might actually crash now.
- noLongerCrashingAt = mid - 1;
- }
- }
-
- // This is basically an assert.
- builder = new BytesBuilder();
- builder.add(sublist(latestCrashData, 0, offset));
- builder.add(
- sublist(latestCrashData, offset + crashingAt, latestCrashData.length));
- candidate = builder.takeBytes();
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- throw "Error in binary search.";
- }
- latestCrashData = candidate;
- }
-
- fs.data[uri] = latestCrashData;
-}
-
-void _tryToRemoveUnreferencedFileContent(Component initialComponent,
- {bool deleteFile: false}) async {
- // Check if there now are any unused files.
- if (_latestComponent == null) return;
- Set<Uri> neededUris = _latestComponent.uriToSource.keys.toSet();
- Map<Uri, Uint8List> copy = new Map.from(fs.data);
- bool removedSome = false;
- if (await shouldQuit()) return;
- for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
- if (entry.value == null || entry.value.isEmpty) continue;
- if (!entry.key.toString().endsWith(".dart")) continue;
- if (!neededUris.contains(entry.key) && fs.data[entry.key].length != 0) {
- if (deleteFile) {
- fs.data[entry.key] = null;
- } else {
- fs.data[entry.key] = new Uint8List(0);
- }
- print(" => Can probably also delete ${entry.key}");
- removedSome = true;
- }
- }
- if (removedSome) {
- if (await crashesOnCompile(initialComponent)) {
- print(" => Yes; Could remove those too!");
- } else {
- print(" => No; Couldn't remove those too!");
- fs.data.clear();
- fs.data.addAll(copy);
- }
- }
-}
-
-void deleteContent(
- List<Uri> uris, int uriIndex, bool limitTo1, Component initialComponent,
- {bool deleteFile: false}) async {
- String extraMessageText = "all content of ";
- if (deleteFile) extraMessageText = "";
-
- if (!limitTo1) {
- if (await shouldQuit()) return;
- Map<Uri, Uint8List> copy = new Map.from(fs.data);
- // Try to remove content of i and the next 9 (10 files in total).
- for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
- Uri uri = uris[j];
- if (deleteFile) {
- fs.data[uri] = null;
- } else {
- fs.data[uri] = new Uint8List(0);
- }
- }
- if (!await crashesOnCompile(initialComponent)) {
- // Couldn't delete all 10 files. Restore and try the single one.
- fs.data.clear();
- fs.data.addAll(copy);
- } else {
- for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
- Uri uri = uris[j];
- print("Can delete ${extraMessageText}file $uri");
- }
- await _tryToRemoveUnreferencedFileContent(initialComponent,
- deleteFile: deleteFile);
- return;
- }
- }
-
- if (await shouldQuit()) return;
- Uri uri = uris[uriIndex];
- Uint8List data = fs.data[uri];
- if (deleteFile) {
- fs.data[uri] = null;
- } else {
- fs.data[uri] = new Uint8List(0);
- }
- if (!await crashesOnCompile(initialComponent)) {
- print("Can't delete ${extraMessageText}file $uri -- keeping it (for now)");
- fs.data[uri] = data;
-
- // For dart files we can't truncate completely try to "outline" them
- // instead.
- if (uri.toString().endsWith(".dart")) {
- String textualOutlined =
- textualOutline(data)?.replaceAll(RegExp(r'\n+'), "\n");
-
- bool outlined = false;
- if (textualOutlined != null) {
- Uint8List candidate = utf8.encode(textualOutlined);
- if (candidate.length != fs.data[uri].length) {
- if (await shouldQuit()) return;
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- print("Can't outline the file $uri -- keeping it (for now)");
- fs.data[uri] = data;
- } else {
- outlined = true;
- print(
- "Can outline the file $uri (now ${fs.data[uri].length} bytes)");
+ for (String arg in arguments) {
+ if (arg.startsWith("--")) {
+ if (arg == "--experimental-invalidation") {
+ settings.experimentalInvalidation = true;
+ } else if (arg == "--serialize") {
+ settings.serialize = true;
+ } else if (arg.startsWith("--platform=")) {
+ String platform = arg.substring("--platform=".length);
+ settings.platformUri = Uri.base.resolve(platform);
+ } else if (arg == "--no-platform") {
+ settings.noPlatform = true;
+ } else if (arg.startsWith("--invalidate=")) {
+ for (String s in arg.substring("--invalidate=".length).split(",")) {
+ settings.invalidate.add(Uri.base.resolve(s));
}
- }
- }
- if (!outlined) {
- // We can probably at least remove all comments then...
- try {
- List<String> strings = utf8.decode(fs.data[uri]).split("\n");
- List<String> stringsLeft = [];
- for (String string in strings) {
- if (!string.trim().startsWith("//")) stringsLeft.add(string);
- }
-
- Uint8List candidate = utf8.encode(stringsLeft.join("\n"));
- if (candidate.length != fs.data[uri].length) {
- if (await shouldQuit()) return;
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- print("Can't remove comments for file $uri -- "
- "keeping it (for now)");
- fs.data[uri] = data;
- } else {
- print("Removed comments for the file $uri");
- }
- }
- } catch (e) {
- // crash in scanner/parser --- keep original file. This crash might
- // be what we're looking for!
- }
- }
- }
- } else {
- print("Can delete ${extraMessageText}file $uri");
- await _tryToRemoveUnreferencedFileContent(initialComponent);
- }
-}
-
-void deleteBlocksOld(Uri uri, Component initialComponent) async {
- if (uri.toString().endsWith(".json")) {
- // Try to find annoying
- //
- // },
- // {
- // }
- //
- // part of json and remove it.
- Uint8List data = fs.data[uri];
- String string = utf8.decode(data);
- List<String> lines = string.split("\n");
- for (int i = 0; i < lines.length - 2; i++) {
- if (lines[i].trim() == "}," &&
- lines[i + 1].trim() == "{" &&
- lines[i + 2].trim() == "}") {
- // This is the pattern we wanted to find. Remove it.
- lines.removeRange(i, i + 2);
- i--;
- }
- }
- string = lines.join("\n");
- fs.data[uri] = utf8.encode(string);
- if (!await crashesOnCompile(initialComponent)) {
- // For some reason that didn't work.
- fs.data[uri] = data;
- }
- }
- if (!uri.toString().endsWith(".dart")) return;
-
- Uint8List data = fs.data[uri];
- Uint8List latestCrashData = data;
-
- List<int> lineStarts = <int>[];
-
- Token firstToken = parser_suite.scanRawBytes(data,
- nnbd ? scannerConfiguration : scannerConfigurationNonNNBD, lineStarts);
-
- if (firstToken == null) {
- print("Got null token from scanner for $uri");
- return;
- }
-
- int compileTry = 0;
- Token token = firstToken;
- while (token is ErrorToken) {
- token = token.next;
- }
- List<Replacement> replacements = [];
- while (token != null && !token.isEof) {
- bool tryCompile = false;
- Token skipToToken = token;
- // Skip very small blocks (e.g. "{}" or "{\n}");
- if (token.endGroup != null && token.offset + 3 < token.endGroup.offset) {
- replacements.add(new Replacement(token.offset, token.endGroup.offset));
- tryCompile = true;
- skipToToken = token.endGroup;
- } else if (token.lexeme == "@") {
- if (token.next.next.endGroup != null) {
- int end = token.next.next.endGroup.offset;
- skipToToken = token.next.next.endGroup;
- replacements.add(new Replacement(token.offset - 1, end + 1));
- tryCompile = true;
- }
- } else if (token.lexeme == "assert") {
- if (token.next.endGroup != null) {
- int end = token.next.endGroup.offset;
- skipToToken = token.next.endGroup;
- if (token.next.endGroup.next.lexeme == ",") {
- end = token.next.endGroup.next.offset;
- skipToToken = token.next.endGroup.next;
- }
- // +/- 1 to not include the start and the end character.
- replacements.add(new Replacement(token.offset - 1, end + 1));
- tryCompile = true;
- }
- } else if ((token.lexeme == "abstract" && token.next.lexeme == "class") ||
- token.lexeme == "class" ||
- token.lexeme == "enum" ||
- token.lexeme == "mixin" ||
- token.lexeme == "static" ||
- token.next.lexeme == "get" ||
- token.next.lexeme == "set" ||
- token.next.next.lexeme == "(" ||
- (token.next.lexeme == "<" &&
- token.next.endGroup != null &&
- token.next.endGroup.next.next.lexeme == "(")) {
- // Try to find and remove the entire class/enum/mixin/
- // static procedure/getter/setter/simple procedure.
- Token bracket = token;
- for (int i = 0; i < 20; i++) {
- // Find "{", but only go a maximum of 20 tokens to do that.
- bracket = bracket.next;
- if (bracket.lexeme == "{" && bracket.endGroup != null) {
- break;
- } else if ((bracket.lexeme == "(" || bracket.lexeme == "<") &&
- bracket.endGroup != null) {
- bracket = bracket.endGroup;
- }
- }
- if (bracket.lexeme == "{" && bracket.endGroup != null) {
- int end = bracket.endGroup.offset;
- skipToToken = bracket.endGroup;
- // +/- 1 to not include the start and the end character.
- replacements.add(new Replacement(token.offset - 1, end + 1));
- tryCompile = true;
- }
- }
-
- if (tryCompile) {
- if (await shouldQuit()) break;
- if (skip) {
- skip = false;
- break;
- }
- stdout.write(".");
- compileTry++;
- if (compileTry % 50 == 0) {
- stdout.write("(at $compileTry)\n");
- }
- Uint8List candidate = _replaceRange(replacements, data);
- fs.data[uri] = candidate;
- if (await crashesOnCompile(initialComponent)) {
- print("Found block from "
- "${replacements.last.from} to "
- "${replacements.last.to} "
- "that can be removed.");
- latestCrashData = candidate;
- token = skipToToken;
- } else {
- // Couldn't delete that.
- replacements.removeLast();
- }
- }
- token = token.next;
- }
- fs.data[uri] = latestCrashData;
-}
-
-void deleteBlocks(final Uri uri, Component initialComponent) async {
- if (uri.toString().endsWith(".json")) {
- // Try to find annoying
- //
- // },
- // {
- // }
- //
- // part of json and remove it.
- Uint8List data = fs.data[uri];
- String string = utf8.decode(data);
- List<String> lines = string.split("\n");
- for (int i = 0; i < lines.length - 2; i++) {
- if (lines[i].trim() == "}," &&
- lines[i + 1].trim() == "{" &&
- lines[i + 2].trim() == "}") {
- // This is the pattern we wanted to find. Remove it.
- lines.removeRange(i, i + 2);
- i--;
- }
- }
- string = lines.join("\n");
- Uint8List candidate = utf8.encode(string);
- if (candidate.length != data.length) {
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- // For some reason that didn't work.
- fs.data[uri] = data;
- }
- }
-
- // Try to load json and remove blocks.
- try {
- Map json = jsonDecode(utf8.decode(data));
- Map jsonModified = new Map.from(json);
- List packages = json["packages"];
- List packagesModified = new List.from(packages);
- jsonModified["packages"] = packagesModified;
- int i = 0;
- print("Note there's ${packagesModified.length} packages in .json");
- JsonEncoder jsonEncoder = new JsonEncoder.withIndent(" ");
- while (i < packagesModified.length) {
- var oldEntry = packagesModified.removeAt(i);
- String jsonString = jsonEncoder.convert(jsonModified);
- candidate = utf8.encode(jsonString);
- Uint8List previous = fs.data[uri];
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- // Couldn't remove that part.
- fs.data[uri] = previous;
- packagesModified.insert(i, oldEntry);
- i++;
+ } else if (arg.startsWith("--widgetTransformation")) {
+ settings.widgetTransformation = true;
+ } else if (arg.startsWith("--target=VM")) {
+ settings.targetString = "VM";
+ } else if (arg.startsWith("--target=flutter")) {
+ settings.targetString = "flutter";
+ } else if (arg.startsWith("--target=ddc")) {
+ settings.targetString = "ddc";
+ } else if (arg == "--oldBlockDelete") {
+ settings.oldBlockDelete = true;
+ } else if (arg == "--lineDelete") {
+ settings.lineDelete = true;
+ } else if (arg == "--byteDelete") {
+ settings.byteDelete = true;
+ } else if (arg == "--ask-redirect-target") {
+ settings.askAboutRedirectCrashTarget = true;
+ } else if (arg == "--auto-uncover-all-crashes") {
+ settings.autoUncoverAllCrashes = true;
+ } else if (arg.startsWith("--stack-matches=")) {
+ String stackMatches = arg.substring("--stack-matches=".length);
+ settings.stackTraceMatches = int.parse(stackMatches);
} else {
- print(
- "Removed package from .json (${packagesModified.length} left).");
+ throw "Unknown option $arg";
}
- }
- } catch (e) {
- // Couldn't decode it, so don't try to do anything.
- }
- return;
- }
- if (!uri.toString().endsWith(".dart")) return;
-
- Uint8List data = fs.data[uri];
- DirectParserASTContentCompilationUnitEnd ast = getAST(data,
- includeBody: true,
- includeComments: false,
- enableExtensionMethods: true,
- enableNonNullable: nnbd);
-
- CompilationHelperClass helper = new CompilationHelperClass(data);
-
- // Try to remove top level things on at a time.
- for (DirectParserASTContent child in ast.children) {
- bool shouldCompile = false;
- String what = "";
- if (child.isClass()) {
- DirectParserASTContentClassDeclarationEnd cls = child.asClass();
- helper.replacements.add(
- new Replacement(cls.beginToken.offset - 1, cls.endToken.offset + 1));
- shouldCompile = true;
- what = "class";
- } else if (child.isMixinDeclaration()) {
- DirectParserASTContentMixinDeclarationEnd decl =
- child.asMixinDeclaration();
- helper.replacements.add(new Replacement(
- decl.mixinKeyword.offset - 1, decl.endToken.offset + 1));
- shouldCompile = true;
- what = "mixin";
- } else if (child.isNamedMixinDeclaration()) {
- DirectParserASTContentNamedMixinApplicationEnd decl =
- child.asNamedMixinDeclaration();
- helper.replacements.add(
- new Replacement(decl.begin.offset - 1, decl.endToken.offset + 1));
- shouldCompile = true;
- what = "named mixin";
- } else if (child.isExtension()) {
- DirectParserASTContentExtensionDeclarationEnd decl = child.asExtension();
- helper.replacements.add(new Replacement(
- decl.extensionKeyword.offset - 1, decl.endToken.offset + 1));
- shouldCompile = true;
- what = "extension";
- } else if (child.isTopLevelFields()) {
- DirectParserASTContentTopLevelFieldsEnd decl = child.asTopLevelFields();
- helper.replacements.add(new Replacement(
- decl.beginToken.offset - 1, decl.endToken.offset + 1));
- shouldCompile = true;
- what = "toplevel fields";
- } else if (child.isTopLevelMethod()) {
- DirectParserASTContentTopLevelMethodEnd decl = child.asTopLevelMethod();
- helper.replacements.add(new Replacement(
- decl.beginToken.offset - 1, decl.endToken.offset + 1));
- shouldCompile = true;
- what = "toplevel method";
- } else if (child.isEnum()) {
- DirectParserASTContentEnumEnd decl = child.asEnum();
- helper.replacements.add(new Replacement(
- decl.enumKeyword.offset - 1, decl.leftBrace.endGroup.offset + 1));
- shouldCompile = true;
- what = "enum";
- } else if (child.isTypedef()) {
- DirectParserASTContentFunctionTypeAliasEnd decl = child.asTypedef();
- helper.replacements.add(new Replacement(
- decl.typedefKeyword.offset - 1, decl.endToken.offset + 1));
- shouldCompile = true;
- what = "typedef";
- } else if (child.isMetadata()) {
- DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
- List<DirectParserASTContentMetadataEnd> metadata =
- decl.getMetadataEntries();
- if (metadata.isNotEmpty) {
- helper.replacements.add(new Replacement(
- metadata.first.beginToken.offset - 1,
- metadata.last.endToken.offset));
- shouldCompile = true;
- }
- what = "metadata";
- } else if (child.isImport()) {
- DirectParserASTContentImportEnd decl = child.asImport();
- helper.replacements.add(new Replacement(
- decl.importKeyword.offset - 1, decl.semicolon.offset + 1));
- shouldCompile = true;
- what = "import";
- } else if (child.isExport()) {
- DirectParserASTContentExportEnd decl = child.asExport();
- helper.replacements.add(new Replacement(
- decl.exportKeyword.offset - 1, decl.semicolon.offset + 1));
- shouldCompile = true;
- what = "export";
- } else if (child.isLibraryName()) {
- DirectParserASTContentLibraryNameEnd decl = child.asLibraryName();
- helper.replacements.add(new Replacement(
- decl.libraryKeyword.offset - 1, decl.semicolon.offset + 1));
- shouldCompile = true;
- what = "library name";
- } else if (child.isPart()) {
- DirectParserASTContentPartEnd decl = child.asPart();
- helper.replacements.add(new Replacement(
- decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
- shouldCompile = true;
- what = "part";
- } else if (child.isPartOf()) {
- DirectParserASTContentPartOfEnd decl = child.asPartOf();
- helper.replacements.add(new Replacement(
- decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
- shouldCompile = true;
- what = "part of";
- } else if (child.isScript()) {
- var decl = child.asScript();
- helper.replacements.add(new Replacement(
- decl.token.offset - 1, decl.token.offset + decl.token.length));
- shouldCompile = true;
- what = "script";
- }
-
- if (shouldCompile) {
- bool success =
- await _tryReplaceAndCompile(helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- if (!success) {
- if (child.isClass()) {
- // Also try to remove all content of the class.
- DirectParserASTContentClassDeclarationEnd decl = child.asClass();
- DirectParserASTContentClassOrMixinBodyEnd body =
- decl.getClassOrMixinBody();
- if (body.beginToken.offset + 2 < body.endToken.offset) {
- helper.replacements.add(
- new Replacement(body.beginToken.offset, body.endToken.offset));
- what = "class body";
- success = await _tryReplaceAndCompile(
- helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- }
-
- if (!success) {
- // Also try to remove members one at a time.
- for (DirectParserASTContent child in body.children) {
- shouldCompile = false;
- if (child is DirectParserASTContentMemberEnd) {
- if (child.isClassConstructor()) {
- DirectParserASTContentClassConstructorEnd memberDecl =
- child.getClassConstructor();
- helper.replacements.add(new Replacement(
- memberDecl.beginToken.offset - 1,
- memberDecl.endToken.offset + 1));
- what = "class constructor";
- shouldCompile = true;
- } else if (child.isClassFields()) {
- DirectParserASTContentClassFieldsEnd memberDecl =
- child.getClassFields();
- helper.replacements.add(new Replacement(
- memberDecl.beginToken.offset - 1,
- memberDecl.endToken.offset + 1));
- what = "class fields";
- shouldCompile = true;
- } else if (child.isClassMethod()) {
- DirectParserASTContentClassMethodEnd memberDecl =
- child.getClassMethod();
- helper.replacements.add(new Replacement(
- memberDecl.beginToken.offset - 1,
- memberDecl.endToken.offset + 1));
- what = "class method";
- shouldCompile = true;
- } else if (child.isClassFactoryMethod()) {
- DirectParserASTContentClassFactoryMethodEnd memberDecl =
- child.getClassFactoryMethod();
- helper.replacements.add(new Replacement(
- memberDecl.beginToken.offset - 1,
- memberDecl.endToken.offset + 1));
- what = "class factory method";
- shouldCompile = true;
- } else {
- // throw "$child --- ${child.children}";
- continue;
- }
- } else if (child.isMetadata()) {
- DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
- List<DirectParserASTContentMetadataEnd> metadata =
- decl.getMetadataEntries();
- if (metadata.isNotEmpty) {
- helper.replacements.add(new Replacement(
- metadata.first.beginToken.offset - 1,
- metadata.last.endToken.offset));
- shouldCompile = true;
- }
- what = "metadata";
- }
- if (shouldCompile) {
- success = await _tryReplaceAndCompile(
- helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- if (!success) {
- DirectParserASTContentBlockFunctionBodyEnd decl;
- if (child is DirectParserASTContentMemberEnd) {
- if (child.isClassMethod()) {
- decl = child.getClassMethod().getBlockFunctionBody();
- } else if (child.isClassConstructor()) {
- decl = child.getClassConstructor().getBlockFunctionBody();
- }
- }
- if (decl != null &&
- decl.beginToken.offset + 2 < decl.endToken.offset) {
- helper.replacements.add(new Replacement(
- decl.beginToken.offset, decl.endToken.offset));
- what = "class member content";
- await _tryReplaceAndCompile(
- helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- }
- }
- }
- }
- }
-
- // Try to remove "extends", "implements" etc.
- if (decl.getClassExtends().extendsKeyword != null) {
- helper.replacements.add(new Replacement(
- decl.getClassExtends().extendsKeyword.offset - 1,
- body.beginToken.offset));
- what = "class extends";
- success = await _tryReplaceAndCompile(
- helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- }
- if (decl.getClassImplements().implementsKeyword != null) {
- helper.replacements.add(new Replacement(
- decl.getClassImplements().implementsKeyword.offset - 1,
- body.beginToken.offset));
- what = "class implements";
- success = await _tryReplaceAndCompile(
- helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- }
- if (decl.getClassWithClause() != null) {
- helper.replacements.add(new Replacement(
- decl.getClassWithClause().withKeyword.offset - 1,
- body.beginToken.offset));
- what = "class with clause";
- success = await _tryReplaceAndCompile(
- helper, uri, initialComponent, what);
- if (helper.shouldQuit) return;
- }
- }
- }
- }
- }
-}
-
-class CompilationHelperClass {
- int compileTry = 0;
- bool shouldQuit = false;
- List<Replacement> replacements = [];
- Uint8List latestCrashData;
- final Uint8List originalData;
-
- CompilationHelperClass(this.originalData) : latestCrashData = originalData;
-}
-
-Future<bool> _tryReplaceAndCompile(CompilationHelperClass data, Uri uri,
- Component initialComponent, String what) async {
- if (await shouldQuit()) {
- data.shouldQuit = true;
- return false;
- }
- stdout.write(".");
- data.compileTry++;
- if (data.compileTry % 50 == 0) {
- stdout.write("(at ${data.compileTry})\n");
- }
- Uint8List candidate = _replaceRange(data.replacements, data.originalData);
-
- fs.data[uri] = candidate;
- if (await crashesOnCompile(initialComponent)) {
- print("Found $what from "
- "${data.replacements.last.from} to "
- "${data.replacements.last.to} "
- "that can be removed.");
- data.latestCrashData = candidate;
- return true;
- } else {
- // Couldn't delete that.
- data.replacements.removeLast();
- fs.data[uri] = data.latestCrashData;
- return false;
- }
-}
-
-class Replacement implements Comparable<Replacement> {
- final int from;
- final int to;
- final Uint8List nullOrReplacement;
-
- Replacement(this.from, this.to, {this.nullOrReplacement});
-
- @override
- int compareTo(Replacement other) {
- return from - other.from;
- }
-}
-
-Uint8List _replaceRange(
- List<Replacement> unsortedReplacements, Uint8List data) {
- // The below assumes these are sorted.
- List<Replacement> sortedReplacements =
- new List<Replacement>.from(unsortedReplacements)..sort();
- final BytesBuilder builder = new BytesBuilder();
- int prev = 0;
- for (int i = 0; i < sortedReplacements.length; i++) {
- Replacement replacement = sortedReplacements[i];
- for (int j = prev; j <= replacement.from; j++) {
- builder.addByte(data[j]);
- }
- if (replacement.nullOrReplacement != null) {
- builder.add(replacement.nullOrReplacement);
- }
- prev = replacement.to;
- }
- for (int j = prev; j < data.length; j++) {
- builder.addByte(data[j]);
- }
- Uint8List candidate = builder.takeBytes();
- return candidate;
-}
-
-const int $LF = 10;
-
-void deleteEmptyLines(Uri uri, Component initialComponent) async {
- Uint8List data = fs.data[uri];
- List<Uint8List> lines = [];
- int start = 0;
- for (int i = 0; i < data.length; i++) {
- if (data[i] == $LF) {
- if (i - start > 0) {
- lines.add(sublist(data, start, i));
- }
- start = i + 1;
- }
- }
- if (data.length - start > 0) {
- lines.add(sublist(data, start, data.length));
- }
-
- final BytesBuilder builder = new BytesBuilder();
- for (int j = 0; j < lines.length; j++) {
- if (builder.isNotEmpty) {
- builder.addByte($LF);
- }
- builder.add(lines[j]);
- }
- Uint8List candidate = builder.takeBytes();
- if (candidate.length == data.length) return;
-
- if (await shouldQuit()) return;
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- // For some reason the empty lines are important.
- fs.data[uri] = data;
- } else {
- print("\nDeleted empty lines.");
- }
-}
-
-void deleteLines(Uri uri, Component initialComponent) async {
- // Try to delete "lines".
- Uint8List data = fs.data[uri];
- List<Uint8List> lines = [];
- int start = 0;
- for (int i = 0; i < data.length; i++) {
- if (data[i] == $LF) {
- lines.add(sublist(data, start, i));
- start = i + 1;
- }
- }
- lines.add(sublist(data, start, data.length));
- List<bool> include = new List.filled(lines.length, true);
- Uint8List latestCrashData = data;
- int length = 1;
- int i = 0;
- while (i < lines.length) {
- if (await shouldQuit()) break;
- if (skip) {
- skip = false;
- break;
- }
- stdout.write(".");
- if (i % 50 == 0) {
- stdout.write("(at $i of ${lines.length})\n");
- }
- if (i + length > lines.length) {
- length = lines.length - i;
- }
- for (int j = i; j < i + length; j++) {
- include[j] = false;
- }
- final BytesBuilder builder = new BytesBuilder();
- for (int j = 0; j < lines.length; j++) {
- if (include[j]) {
- if (builder.isNotEmpty) {
- builder.addByte($LF);
- }
- builder.add(lines[j]);
- }
- }
- Uint8List candidate = builder.takeBytes();
- fs.data[uri] = candidate;
- if (!await crashesOnCompile(initialComponent)) {
- // Didn't crash => Can't remove line i-j.
- for (int j = i; j < i + length; j++) {
- include[j] = true;
- }
- if (length > 2) {
- // Retry with length 2 at same i.
- // The idea here is that for instance formatted json might have lines
- // looking like
- // {
- // }
- // where deleting just one line makes it invalid.
- length = 2;
- } else if (length > 1) {
- // Retry with length 1 at same i.
- length = 1;
+ } else if (filename != null) {
+ throw "Already got '$filename', '$arg' is also a filename; "
+ "can only get one";
} else {
- // Couldn't with length 1 either.
+ filename = arg;
+ }
+ }
+ if (settings.noPlatform) {
+ int i = 0;
+ while (settings.platformUri == null ||
+ new File.fromUri(settings.platformUri).existsSync()) {
+ settings.platformUri = Uri.base.resolve("nonexisting_$i");
i++;
}
} else {
- print("\nCan delete line $i (inclusive) - ${i + length} (exclusive) "
- "(of ${lines.length})");
- latestCrashData = candidate;
- i += length;
- length *= 2;
- }
- }
- fs.data[uri] = latestCrashData;
-}
-
-Component _latestComponent;
-IncrementalCompiler _latestIncrementalCompiler;
-
-Future<bool> crashesOnCompile(Component initialComponent) async {
- IncrementalCompiler incrementalCompiler;
- if (noPlatform) {
- incrementalCompiler = new IncrementalCompiler(setupCompilerContext());
- } else {
- incrementalCompiler = new IncrementalCompiler.fromComponent(
- setupCompilerContext(), initialComponent);
- }
- _latestIncrementalCompiler = incrementalCompiler;
- incrementalCompiler.invalidate(mainUri);
- try {
- _latestComponent = await incrementalCompiler.computeDelta();
- if (serialize) {
- ByteSink sink = new ByteSink();
- BinaryPrinter printer = new BinaryPrinter(sink);
- printer.writeComponentFile(_latestComponent);
- sink.builder.takeBytes();
- }
- for (Uri uri in invalidate) {
- incrementalCompiler.invalidate(uri);
- Component delta = await incrementalCompiler.computeDelta();
- if (serialize) {
- ByteSink sink = new ByteSink();
- BinaryPrinter printer = new BinaryPrinter(sink);
- printer.writeComponentFile(delta);
- sink.builder.takeBytes();
+ if (settings.platformUri == null) {
+ throw "No platform given. Use --platform=/path/to/platform.dill";
+ }
+ if (!new File.fromUri(settings.platformUri).existsSync()) {
+ throw "The platform file '${settings.platformUri}' doesn't exist";
}
}
- _latestComponent = null; // if it didn't crash this isn't relevant.
- return false;
- } catch (e, st) {
- // Find line with #0 in it.
- String eWithSt = "$e\n\n$st";
- List<String> lines = eWithSt.split("\n");
- String foundLine = "";
- int lookFor = 0;
- for (String line in lines) {
- if (line.startsWith("#$lookFor")) {
- foundLine += line;
- lookFor++;
- if (lookFor >= stackTraceMatches) {
- break;
- } else {
- foundLine += "\n";
- }
- }
+ if (filename == null) {
+ throw "Need file to operate on";
}
- if (foundLine == null) throw "Unexpected crash without stacktrace: $e";
- if (expectedCrashLine == null) {
- print("Got '$foundLine'");
- expectedCrashLine = foundLine;
- return true;
- } else if (foundLine == expectedCrashLine) {
- return true;
- } else {
- if (askAboutRedirectCrashTarget &&
- !askedAboutRedirect.contains(foundLine)) {
- print("Crashed, but another place: $foundLine");
- while (true) {
- askedAboutRedirect.add(foundLine);
- print(eWithSt);
- print("Should we redirect to searching for that? (y/n)");
- String answer = stdin.readLineSync();
- if (answer == "yes" || answer == "y") {
- expectedCrashLine = foundLine;
- return true;
- } else if (answer == "no" || answer == "n") {
- break;
- } else {
- print("Didn't get that answer. "
- "Please answer 'yes, 'y', 'no' or 'n'");
- }
- }
- }
- return false;
- }
- }
-}
-
-Future<Component> getInitialComponent() async {
- IncrementalCompiler incrementalCompiler =
- new IncrementalCompiler(setupCompilerContext());
- Component originalComponent = await incrementalCompiler.computeDelta();
- return originalComponent;
-}
-
-CompilerContext setupCompilerContext() {
- CompilerOptions options = getOptions();
-
- if (nnbd) {
- options.explicitExperimentalFlags = {ExperimentalFlag.nonNullable: true};
- }
- if (experimentalInvalidation) {
- options.explicitExperimentalFlags ??= {};
- options.explicitExperimentalFlags[
- ExperimentalFlag.alternativeInvalidationStrategy] = true;
+ File file = new File(filename);
+ if (!file.existsSync()) throw "File $filename doesn't exist.";
+ settings.mainUri = file.absolute.uri;
}
- TargetFlags targetFlags = new TargetFlags(
- enableNullSafety: nnbd, trackWidgetCreation: widgetTransformation);
- Target target;
- switch (targetString) {
- case "VM":
- target = new VmTarget(targetFlags);
- break;
- case "flutter":
- target = new FlutterTarget(targetFlags);
- break;
- case "ddc":
- target = new DevCompilerTarget(targetFlags);
- break;
- default:
- throw "Unknown target '$target'";
- }
- options.target = target;
- options.fileSystem = fs;
- options.sdkRoot = null;
- options.sdkSummary = platformUri;
- options.omitPlatform = false;
- options.onDiagnostic = (DiagnosticMessage message) {
- // don't care.
- };
- if (noPlatform) {
- options.librariesSpecificationUri = null;
- }
-
- CompilerContext compilerContext = new CompilerContext(
- new ProcessedOptions(options: options, inputs: [mainUri]));
- return compilerContext;
-}
-
-String getFileAsStringContent(Uint8List rawBytes, bool nnbd) {
- List<int> lineStarts = <int>[];
-
- Token firstToken = parser_suite.scanRawBytes(rawBytes,
- nnbd ? scannerConfiguration : scannerConfigurationNonNNBD, lineStarts);
-
- if (firstToken == null) {
- throw "Got null token from scanner";
- }
-
- ParserTestListener parserTestListener = new ParserTestListener(false);
- Parser parser = new Parser(parserTestListener);
- parser.parseUnit(firstToken);
- String parsedString =
- parser_suite.tokenStreamToString(firstToken, lineStarts).toString();
- return parsedString;
-}
-
-ScannerConfiguration scannerConfiguration = new ScannerConfiguration(
- enableTripleShift: true,
- enableExtensionMethods: true,
- enableNonNullable: true);
-
-ScannerConfiguration scannerConfigurationNonNNBD = new ScannerConfiguration(
- enableTripleShift: true,
- enableExtensionMethods: true,
- enableNonNullable: false);
-
-class FakeFileSystem extends FileSystem {
- bool _redirectAndRecord = true;
- bool _initialized = false;
- final Map<Uri, Uint8List> data = {};
-
- @override
- FileSystemEntity entityForUri(Uri uri) {
- return new FakeFileSystemEntity(this, uri);
- }
-
- initializeFromJson(Map<String, dynamic> json) {
- _initialized = true;
- _redirectAndRecord = json['_redirectAndRecord'];
- data.clear();
- List tmp = json['data'];
- for (int i = 0; i < tmp.length; i += 2) {
- Uri key = tmp[i] == null ? null : Uri.parse(tmp[i]);
- if (tmp[i + 1] == null) {
- data[key] = null;
- } else if (tmp[i + 1] is String) {
- data[key] = utf8.encode(tmp[i + 1]);
- } else {
- data[key] = Uint8List.fromList(new List<int>.from(tmp[i + 1]));
- }
- }
- }
-
- Map<String, dynamic> toJson() {
- List tmp = [];
- for (var entry in data.entries) {
- if (entry.value == null) continue;
- tmp.add(entry.key == null ? null : entry.key.toString());
- dynamic out = entry.value;
- if (entry.value != null && entry.value.isNotEmpty) {
- try {
- String string = utf8.decode(entry.value);
- out = string;
- } catch (e) {
- // not a string...
- }
- }
- tmp.add(out);
- }
- return {
- '_redirectAndRecord': _redirectAndRecord,
- 'data': tmp,
- };
- }
-}
-
-class FakeFileSystemEntity extends FileSystemEntity {
- final FakeFileSystem fs;
- final Uri uri;
- FakeFileSystemEntity(this.fs, this.uri);
-
- void _ensureCachedIfOk() {
- if (fs.data.containsKey(uri)) return;
- if (fs._initialized) return;
- if (!fs._redirectAndRecord) {
- throw "Asked for file in non-recording mode that wasn't known";
- }
- File f = new File.fromUri(uri);
- if (!f.existsSync()) {
- fs.data[uri] = null;
- return;
- }
- fs.data[uri] = f.readAsBytesSync();
- }
-
- @override
- Future<bool> exists() {
- _ensureCachedIfOk();
- Uint8List data = fs.data[uri];
- if (data == null) return Future.value(false);
- return Future.value(true);
- }
-
- @override
- Future<List<int>> readAsBytes() {
- _ensureCachedIfOk();
- Uint8List data = fs.data[uri];
- if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
- return Future.value(data);
- }
-
- @override
- Future<String> readAsString() {
- _ensureCachedIfOk();
- Uint8List data = fs.data[uri];
- if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
- return Future.value(utf8.decode(data));
- }
+ TestMinimizer testMinimizer = new TestMinimizer(settings);
+ await testMinimizer.tryToMinimize();
}
diff --git a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
new file mode 100644
index 0000000..8bda7f4
--- /dev/null
+++ b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
@@ -0,0 +1,2102 @@
+// Copyright (c) 2020, 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:async' show Future, StreamSubscription;
+
+import 'dart:convert' show JsonEncoder, jsonDecode, utf8;
+
+import 'dart:io' show BytesBuilder, File, stdin, stdout;
+
+import 'dart:math' show max;
+
+import 'dart:typed_data' show Uint8List;
+
+import 'package:_fe_analyzer_shared/src/util/relativize.dart'
+ show relativizeUri, isWindows;
+
+import 'package:_fe_analyzer_shared/src/parser/parser.dart'
+ show Listener, Parser;
+
+import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
+ show ErrorToken, ScannerConfiguration, Token;
+
+import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
+
+import 'package:dev_compiler/src/kernel/target.dart' show DevCompilerTarget;
+
+import 'package:front_end/src/api_prototype/compiler_options.dart'
+ show CompilerOptions, DiagnosticMessage;
+
+import 'package:front_end/src/api_prototype/experimental_flags.dart'
+ show ExperimentalFlag;
+
+import 'package:front_end/src/api_prototype/file_system.dart'
+ show FileSystem, FileSystemEntity, FileSystemException;
+
+import 'package:front_end/src/base/processed_options.dart'
+ show ProcessedOptions;
+import 'package:front_end/src/fasta/builder/library_builder.dart';
+
+import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
+
+import 'package:front_end/src/fasta/incremental_compiler.dart'
+ show IncrementalCompiler;
+
+import 'package:front_end/src/fasta/kernel/utils.dart' show ByteSink;
+import 'package:front_end/src/fasta/messages.dart' show Message;
+import 'package:front_end/src/fasta/util/direct_parser_ast.dart';
+import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
+
+import 'package:front_end/src/fasta/util/textual_outline.dart'
+ show textualOutline;
+
+import 'package:kernel/ast.dart' show Component;
+
+import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
+
+import 'package:kernel/target/targets.dart' show Target, TargetFlags;
+import 'package:package_config/package_config.dart';
+
+import "package:vm/target/flutter.dart" show FlutterTarget;
+
+import "package:vm/target/vm.dart" show VmTarget;
+
+import 'incremental_load_from_dill_suite.dart' show getOptions;
+
+import 'parser_test_listener.dart' show ParserTestListener;
+
+import 'parser_suite.dart' as parser_suite;
+
+class TestMinimizerSettings {
+ final _FakeFileSystem _fsInitial = new _FakeFileSystem();
+ final _FakeFileSystem _fsNotInitial = new _FakeFileSystem();
+ _FakeFileSystem get _fs {
+ if (_useInitialFs) return _fsInitial;
+ return _fsNotInitial;
+ }
+
+ bool _useInitialFs = true;
+ Uri mainUri;
+ Uri platformUri;
+ bool noPlatform = false;
+ bool experimentalInvalidation = false;
+ bool serialize = false;
+ bool widgetTransformation = false;
+ final List<Uri> invalidate = [];
+ String targetString = "VM";
+ bool oldBlockDelete = false;
+ bool lineDelete = false;
+ bool byteDelete = false;
+ bool askAboutRedirectCrashTarget = false;
+ bool autoUncoverAllCrashes = false;
+ int stackTraceMatches = 1;
+ final Set<String> askedAboutRedirect = {};
+ final List<Map<String, dynamic>> fileSystems = [];
+ final Set<String> allAutoRedirects = {};
+
+ void goToFileSystem(int i) {
+ Map<String, dynamic> fileSystem = fileSystems[i];
+ fileSystems[i] = _fsNotInitial.toJson();
+ _fsNotInitial.initializeFromJson(fileSystem);
+ }
+
+ Map<String, dynamic> toJson() {
+ List<Map<String, dynamic>> fileSystems =
+ new List<Map<String, dynamic>>.from(this.fileSystems);
+ fileSystems.add(_fsNotInitial.toJson());
+ return {
+ 'mainUri': mainUri.toString(),
+ 'platformUri': platformUri.toString(),
+ 'noPlatform': noPlatform,
+ 'experimentalInvalidation': experimentalInvalidation,
+ 'serialize': serialize,
+ 'widgetTransformation': widgetTransformation,
+ 'invalidate': invalidate.map((uri) => uri.toString()).toList(),
+ 'targetString': targetString,
+ 'oldBlockDelete': oldBlockDelete,
+ 'lineDelete': lineDelete,
+ 'byteDelete': byteDelete,
+ 'askAboutRedirectCrashTarget': askAboutRedirectCrashTarget,
+ 'autoUncoverAllCrashes': autoUncoverAllCrashes,
+ 'stackTraceMatches': stackTraceMatches,
+ 'askedAboutRedirect': askedAboutRedirect.toList(),
+ 'fileSystems': fileSystems,
+ 'allAutoRedirects': allAutoRedirects.toList(),
+ };
+ }
+
+ initializeFromJson(Map<String, dynamic> json) {
+ mainUri = Uri.parse(json["mainUri"]);
+ platformUri = Uri.parse(json["platformUri"]);
+ noPlatform = json["noPlatform"];
+ experimentalInvalidation = json["experimentalInvalidation"];
+ serialize = json["serialize"];
+ widgetTransformation = json["widgetTransformation"];
+ invalidate.clear();
+ invalidate.addAll(
+ (json["invalidate"] as List).map((uriString) => Uri.parse(uriString)));
+ targetString = json["targetString"];
+ oldBlockDelete = json["oldBlockDelete"];
+ lineDelete = json["lineDelete"];
+ byteDelete = json["byteDelete"];
+ askAboutRedirectCrashTarget = json["askAboutRedirectCrashTarget"];
+ autoUncoverAllCrashes = json["autoUncoverAllCrashes"];
+ stackTraceMatches = json["stackTraceMatches"];
+ askedAboutRedirect.clear();
+ askedAboutRedirect.addAll((json["askedAboutRedirect"] as List).cast());
+ fileSystems.clear();
+ fileSystems.addAll((json["fileSystems"] as List).cast());
+ allAutoRedirects.clear();
+ allAutoRedirects.addAll((json["allAutoRedirects"] as List).cast());
+
+ _fsNotInitial.initializeFromJson(fileSystems.removeLast());
+ }
+}
+
+class TestMinimizer {
+ final TestMinimizerSettings _settings;
+ _FakeFileSystem get _fs => _settings._fs;
+ Uri get _mainUri => _settings.mainUri;
+ String _expectedCrashLine;
+ bool _quit = false;
+ bool _skip = false;
+ bool _check = false;
+ int _currentFsNum = -1;
+
+ Component _latestComponent;
+ IncrementalCompiler _latestCrashingIncrementalCompiler;
+ StreamSubscription<List<int>> _stdinSubscription;
+
+ static const int _$LF = 10;
+
+ TestMinimizer(this._settings);
+
+ void _setupStdin() {
+ try {
+ stdin.echoMode = false;
+ stdin.lineMode = false;
+ } catch (e) {
+ print("error setting settings on stdin");
+ }
+ _stdinSubscription = stdin.listen((List<int> event) {
+ if (event.length == 1 && event.single == "q".codeUnits.single) {
+ print("\n\nGot told to quit!\n\n");
+ _quit = true;
+ } else if (event.length == 1 && event.single == "s".codeUnits.single) {
+ print("\n\nGot told to skip!\n\n");
+ _skip = true;
+ } else if (event.length == 1 && event.single == "c".codeUnits.single) {
+ print("\n\nGot told to check!\n\n");
+ _check = true;
+ } else if (event.length == 1 && event.single == "i".codeUnits.single) {
+ print("\n\n--- STATUS INFORMATION START ---\n\n");
+ print("Currently looking for this crash: $_expectedCrashLine\n\n");
+ print("Currently on filesystem #$_currentFsNum out of "
+ "${_settings.fileSystems.length}\n\n");
+
+ int totalFiles = 0;
+ int emptyFiles = 0;
+ int combinedSize = 0;
+ for (Uri uri in _fs.data.keys) {
+ final Uint8List originalBytes = _fs.data[uri];
+ if (originalBytes == null) continue;
+ totalFiles++;
+ if (originalBytes.isEmpty) emptyFiles++;
+ combinedSize += originalBytes.length;
+ }
+ print("Total files left: $totalFiles.");
+ print("Of which empty: $emptyFiles.");
+ print("Combined size left: $combinedSize bytes.");
+ print("\n\n--- STATUS INFORMATION END ---\n\n");
+ } else {
+ print("\n\nGot stdin input: $event\n\n");
+ }
+ });
+ }
+
+ Future tryToMinimize() async {
+ _setupStdin();
+ while (_currentFsNum < _settings.fileSystems.length) {
+ try {
+ if (_currentFsNum >= 0) {
+ print("Replacing filesystem!");
+ _settings.goToFileSystem(_currentFsNum);
+ _expectedCrashLine = null;
+ _latestComponent = null;
+ _latestCrashingIncrementalCompiler = null;
+ }
+ await _tryToMinimizeImpl();
+ if (_currentFsNum + 1 < _settings.fileSystems.length) {
+ // We have more to do --- but we just printed something the user might
+ // want to read. So wait a little before continuing.
+ print("Waiting for 5 seconds before continuing.");
+ await Future.delayed(new Duration(seconds: 5));
+ }
+ } catch (e) {
+ if (e is _DoesntCrashOnInput) {
+ print("Currently doesn't crash (or no longer crashes) the compiler.");
+ } else {
+ print("About to crash. Dumping settings including the filesystem so "
+ "we can (hopefully) continue later.");
+ _dumpToJson();
+ rethrow;
+ }
+ }
+ _currentFsNum++;
+ }
+
+ await _stdinSubscription.cancel();
+ }
+
+ Future _tryToMinimizeImpl() async {
+ // Set main to be basically empty up front.
+ _settings._useInitialFs = true;
+ _fs.data[_mainUri] = utf8.encode("main() {}");
+ Component initialComponent = await _getInitialComponent();
+ print("Compiled initially (without data)");
+ // Remove fake cache.
+ _fs.data.remove(_mainUri);
+ _settings._useInitialFs = false;
+
+ // First assure it actually crash on the input.
+ if (!await _crashesOnCompile(initialComponent)) {
+ throw new _DoesntCrashOnInput();
+ }
+ print("Step #1: We did crash on the input!");
+
+ // All file should now be cached.
+ _fs._redirectAndRecord = false;
+
+ // For all dart files: Parse them as set their source as the parsed source
+ // to "get around" any encoding issues when printing later.
+ Map<Uri, Uint8List> copy = new Map.from(_fs.data);
+ for (Uri uri in _fs.data.keys) {
+ if (await _shouldQuit()) break;
+ String uriString = uri.toString();
+ if (uriString.endsWith(".json") ||
+ uriString.endsWith(".packages") ||
+ uriString.endsWith(".dill") ||
+ _fs.data[uri] == null ||
+ _fs.data[uri].isEmpty) {
+ // skip
+ } else {
+ try {
+ if (_knownByCompiler(uri)) {
+ String parsedString =
+ _getFileAsStringContent(_fs.data[uri], _isUriNnbd(uri));
+ _fs.data[uri] = utf8.encode(parsedString);
+ }
+ } catch (e) {
+ // crash in scanner/parser --- keep original file. This crash might
+ // be what we're looking for!
+ }
+ }
+ }
+ if (!await _crashesOnCompile(initialComponent)) {
+ // Now - for whatever reason - we didn't crash. Restore.
+ _fs.data.clear();
+ _fs.data.addAll(copy);
+ }
+
+ // Operate on one file at a time: Try to delete all content in file.
+ List<Uri> uris = new List<Uri>.from(_fs.data.keys);
+
+ // TODO(jensj): Can we "thread" this?
+ bool changedSome = true;
+ while (changedSome) {
+ if (await _shouldQuit()) break;
+ while (changedSome) {
+ if (await _shouldQuit()) break;
+ changedSome = false;
+ for (int i = 0; i < uris.length; i++) {
+ if (await _shouldQuit()) break;
+ Uri uri = uris[i];
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ print("About to work on file $i of ${uris.length}");
+ await _deleteContent(uris, i, false, initialComponent);
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) {
+ changedSome = true;
+ }
+ }
+ }
+
+ // Try to delete empty files.
+ bool changedSome2 = true;
+ while (changedSome2) {
+ if (await _shouldQuit()) break;
+ changedSome2 = false;
+ for (int i = 0; i < uris.length; i++) {
+ if (await _shouldQuit()) break;
+ Uri uri = uris[i];
+ if (_fs.data[uri] == null || _fs.data[uri].isNotEmpty) continue;
+ print("About to work on file $i of ${uris.length}");
+ await _deleteContent(uris, i, false, initialComponent,
+ deleteFile: true);
+ if (_fs.data[uri] == null) {
+ changedSome = true;
+ changedSome2 = true;
+ }
+ }
+ }
+
+ int left = 0;
+ for (Uri uri in uris) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ left++;
+ }
+ print("There's now $left files of ${_fs.data.length} files left");
+
+ // Operate on one file at a time.
+ for (Uri uri in _fs.data.keys) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ if (await _shouldQuit()) break;
+
+ if (await _tryRemoveIfNotKnownByCompiler(uri, initialComponent)) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ if (await _shouldQuit()) break;
+ }
+
+ if (_check) {
+ _check = false;
+ if (!await _crashesOnCompile(initialComponent)) {
+ throw "Check revealed that the current file system doesn't crash.";
+ } else {
+ print("Check OK!");
+ }
+ }
+
+ print("Now working on $uri");
+
+ int prevLength = _fs.data[uri].length;
+
+ await _deleteBlocks(uri, initialComponent);
+ await _deleteEmptyLines(uri, initialComponent);
+
+ if (_settings.oldBlockDelete) {
+ // Try to delete blocks.
+ await _deleteBlocksOld(uri, initialComponent);
+ }
+
+ if (_settings.lineDelete) {
+ // Try to delete lines.
+ await _deleteLines(uri, initialComponent);
+ }
+
+ print("We're now at ${_fs.data[uri].length} bytes for $uri "
+ "(was $prevLength).");
+ if (prevLength != _fs.data[uri].length) changedSome = true;
+ if (_fs.data[uri].isEmpty) continue;
+
+ if (_settings.byteDelete) {
+ // Now try to delete 'arbitrarily' (for any given start offset do an
+ // exponential binary search).
+ int prevLength = _fs.data[uri].length;
+ while (true) {
+ if (await _shouldQuit()) break;
+ await _binarySearchDeleteData(uri, initialComponent);
+
+ if (_fs.data[uri].length == prevLength) {
+ // No progress.
+ break;
+ } else {
+ print("We're now at ${_fs.data[uri].length} bytes");
+ prevLength = _fs.data[uri].length;
+ changedSome = true;
+ }
+ }
+ }
+ }
+ for (Uri uri in _fs.data.keys) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ if (await _shouldQuit()) break;
+
+ if (await _tryRemoveIfNotKnownByCompiler(uri, initialComponent)) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ if (await _shouldQuit()) break;
+ }
+
+ if (await _attemptInline(uri, initialComponent)) {
+ changedSome = true;
+
+ if (await _tryRemoveIfNotKnownByCompiler(uri, initialComponent)) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ if (await _shouldQuit()) break;
+ }
+ }
+ }
+ }
+
+ if (await _shouldQuit()) {
+ print("\n\nASKED TO QUIT\n\n");
+ } else {
+ print("\n\nDONE\n\n");
+ }
+
+ Uri jsonOut = _dumpToJson();
+
+ if (!await _shouldQuit()) {
+ // Test converting to incremental compiler yaml test.
+ _outputIncrementalCompilerYamlTest();
+ print("\n\n\n");
+
+ for (Uri uri in uris) {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) continue;
+ print("Uri $uri has this content:");
+
+ try {
+ String utfDecoded = utf8.decode(_fs.data[uri], allowMalformed: true);
+ print(utfDecoded);
+ } catch (e) {
+ print(_fs.data[uri]);
+ print("(which crashes when trying to decode as utf8)");
+ }
+ print("\n\n====================\n\n");
+ }
+
+ print("Wrote json dump to $jsonOut");
+ }
+ }
+
+ Future<bool> _shouldQuit() async {
+ // allow some time for stdin.listen to process data.
+ await new Future.delayed(new Duration(milliseconds: 5));
+ return _quit;
+ }
+
+ Uri _dumpToJson() {
+ JsonEncoder jsonEncoder = new JsonEncoder.withIndent(" ");
+ String json = jsonEncoder.convert(_settings);
+ int i = 0;
+ Uri jsonOut;
+ while (jsonOut == null || new File.fromUri(jsonOut).existsSync()) {
+ jsonOut = Uri.base.resolve("crash_minimizer_result_$i");
+ i++;
+ }
+ new File.fromUri(jsonOut).writeAsStringSync(json);
+ print("Wrote json dump to $jsonOut");
+ return jsonOut;
+ }
+
+ /// Attempts to inline small files in other files.
+ /// Returns true if anything was changed, i.e. if at least one inlining was a
+ /// success.
+ Future<bool> _attemptInline(Uri uri, Component initialComponent) async {
+ // Don't attempt to inline the main uri --- that's our entry!
+ if (uri == _mainUri) return false;
+
+ Uint8List inlineData = _fs.data[uri];
+ bool hasMultipleLines = false;
+ for (int i = 0; i < inlineData.length; i++) {
+ if (inlineData[i] == _$LF) {
+ hasMultipleLines = true;
+ break;
+ }
+ }
+ // TODO(jensj): Maybe inline slightly bigger files too?
+ if (hasMultipleLines) {
+ return false;
+ }
+
+ Uri inlinableUri = uri;
+
+ int compileTry = 0;
+ bool changed = false;
+
+ for (Uri uri in _fs.data.keys) {
+ if (!uri.toString().endsWith(".dart")) continue;
+ if (inlinableUri == uri) continue;
+ final Uint8List originalBytes = _fs.data[uri];
+ if (originalBytes == null || originalBytes.isEmpty) continue;
+ DirectParserASTContentCompilationUnitEnd ast = getAST(originalBytes,
+ includeBody: false,
+ includeComments: false,
+ enableExtensionMethods: true,
+ enableNonNullable: _isUriNnbd(uri));
+ // Find all imports/exports of this file (if any).
+ // If finding any:
+ // * remove all of them, then
+ // * find the end of the last import/export, and
+ // * insert the content of the file there.
+ // * if that *doesn't* work and we've inserted an export,
+ // try converting that to an import instead.
+ List<_Replacement> replacements = [];
+ for (DirectParserASTContentImportEnd import in ast.getImports()) {
+ Token importUriToken = import.importKeyword.next;
+ Uri importUri = _getUri(importUriToken, uri);
+ if (inlinableUri == importUri) {
+ replacements.add(new _Replacement(
+ import.importKeyword.offset - 1, import.semicolon.offset + 1));
+ }
+ }
+ for (DirectParserASTContentExportEnd export in ast.getExports()) {
+ Token exportUriToken = export.exportKeyword.next;
+ Uri exportUri = _getUri(exportUriToken, uri);
+ if (inlinableUri == exportUri) {
+ replacements.add(new _Replacement(
+ export.exportKeyword.offset - 1, export.semicolon.offset + 1));
+ }
+ }
+ if (replacements.isEmpty) continue;
+
+ // Step 1: Remove all imports/exports *of* this file (the inlinable file).
+ final Uint8List withoutInlineable =
+ _replaceRange(replacements, originalBytes);
+
+ // Step 2: Find the last import/export.
+ int offsetOfLast = 0;
+ ast = getAST(withoutInlineable,
+ includeBody: false,
+ includeComments: false,
+ enableExtensionMethods: true,
+ enableNonNullable: _isUriNnbd(uri));
+ for (DirectParserASTContentImportEnd import in ast.getImports()) {
+ offsetOfLast = max(offsetOfLast, import.semicolon.offset + 1);
+ }
+ for (DirectParserASTContentExportEnd export in ast.getExports()) {
+ offsetOfLast = max(offsetOfLast, export.semicolon.offset + 1);
+ }
+
+ // Step 3: Insert the content of the file there. Note, though,
+ // that any imports/exports in _that_ file should be changed to be valid
+ // in regards to the new placement.
+ final String withoutInlineableString = utf8.decode(withoutInlineable);
+ StringBuffer builder = new StringBuffer();
+ for (int i = 0; i < offsetOfLast; i++) {
+ builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+ }
+ builder.write("\n");
+ builder.write(utf8.decode(_rewriteImportsExportsToUri(
+ inlineData, uri, inlinableUri, _isUriNnbd(inlinableUri))));
+ builder.write("\n");
+ for (int i = offsetOfLast; i < withoutInlineableString.length; i++) {
+ builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+ }
+ final Uint8List inlinedWithoutChange = utf8.encode(builder.toString());
+
+ if (!_parsesWithoutError(inlinedWithoutChange, _isUriNnbd(uri))) {
+ print("WARNING: Parser error after stuff at ${StackTrace.current}");
+ }
+
+ // Step 4: Try it out.
+ if (await _shouldQuit()) break;
+ if (_skip) {
+ _skip = false;
+ break;
+ }
+ stdout.write(".");
+ compileTry++;
+ if (compileTry % 50 == 0) {
+ stdout.write("(at $compileTry)\n");
+ }
+ _fs.data[uri] = inlinedWithoutChange;
+ if (await _crashesOnCompile(initialComponent)) {
+ print("Could inline $inlinableUri into $uri.");
+ changed = true;
+ // File was already updated.
+ } else {
+ // Couldn't replace that.
+ // Insert the original again.
+ _fs.data[uri] = originalBytes;
+
+ // If we've inlined an export, try changing that to an import.
+ builder = new StringBuffer();
+ for (int i = 0; i < offsetOfLast; i++) {
+ builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+ }
+ builder.write("\n");
+ builder.write(utf8.decode(_rewriteImportsExportsToUri(
+ inlineData, uri, inlinableUri, _isUriNnbd(inlinableUri),
+ convertExportToImport: true)));
+ builder.write("\n");
+ for (int i = offsetOfLast; i < withoutInlineableString.length; i++) {
+ builder.writeCharCode(withoutInlineableString.codeUnitAt(i));
+ }
+ Uint8List inlinedWithChange = utf8.encode(builder.toString());
+
+ if (!_parsesWithoutError(inlinedWithChange, _isUriNnbd(uri))) {
+ print("WARNING: Parser error after stuff at ${StackTrace.current}");
+ }
+
+ // Step 4: Try it out.
+ if (await _shouldQuit()) break;
+ if (_skip) {
+ _skip = false;
+ break;
+ }
+ stdout.write(".");
+ compileTry++;
+ if (compileTry % 50 == 0) {
+ stdout.write("(at $compileTry)\n");
+ }
+ _fs.data[uri] = inlinedWithChange;
+ if (await _crashesOnCompile(initialComponent)) {
+ print("Could inline $inlinableUri into $uri "
+ "(by converting export to import).");
+ changed = true;
+ // File was already updated.
+ } else {
+ // Couldn't replace that.
+ // Insert the original again.
+ _fs.data[uri] = originalBytes;
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ Uint8List _rewriteImportsExportsToUri(
+ Uint8List oldData, Uri newUri, Uri oldUri, bool nnbd,
+ {bool convertExportToImport: false}) {
+ DirectParserASTContentCompilationUnitEnd ast = getAST(oldData,
+ includeBody: false,
+ includeComments: false,
+ enableExtensionMethods: true,
+ enableNonNullable: nnbd);
+ List<_Replacement> replacements = [];
+ for (DirectParserASTContentImportEnd import in ast.getImports()) {
+ _rewriteImportsExportsToUriInternal(
+ import.importKeyword.next, oldUri, replacements, newUri);
+ }
+ for (DirectParserASTContentExportEnd export in ast.getExports()) {
+ if (convertExportToImport) {
+ replacements.add(new _Replacement(
+ export.exportKeyword.offset - 1,
+ export.exportKeyword.offset + export.exportKeyword.length,
+ nullOrReplacement: "import",
+ ));
+ }
+ _rewriteImportsExportsToUriInternal(
+ export.exportKeyword.next, oldUri, replacements, newUri);
+ }
+ if (replacements.isNotEmpty) {
+ Uint8List candidate = _replaceRange(replacements, oldData);
+ return candidate;
+ }
+ return oldData;
+ }
+
+ void _outputIncrementalCompilerYamlTest() {
+ int dartFiles = 0;
+ for (MapEntry<Uri, Uint8List> entry in _fs.data.entries) {
+ if (entry.key.pathSegments.last.endsWith(".dart")) {
+ if (entry.value != null) dartFiles++;
+ }
+ }
+
+ print("------ Reproduction as semi-done incremental yaml test file ------");
+
+ // TODO(jensj): don't use full uris.
+ print("""
+ # Copyright (c) 2020, 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.md file.
+
+ # Reproduce a crash.
+
+ type: newworld""");
+ if (_settings.widgetTransformation) {
+ print("trackWidgetCreation: true");
+ print("target: DDC # basically needed for widget creation to be run");
+ }
+ print("""
+ worlds:
+ - entry: $_mainUri""");
+ if (_settings.experimentalInvalidation) {
+ print(" experiments: alternative-invalidation-strategy");
+ }
+ print(" sources:");
+ for (MapEntry<Uri, Uint8List> entry in _fs.data.entries) {
+ if (entry.value == null) continue;
+ print(" ${entry.key}: |");
+ String string = utf8.decode(entry.value);
+ List<String> lines = string.split("\n");
+ for (String line in lines) {
+ print(" $line");
+ }
+ }
+ print(" expectedLibraryCount: $dartFiles "
+ "# with parts this is not right");
+ print("");
+
+ for (Uri uri in _settings.invalidate) {
+ print(" - entry: $_mainUri");
+ if (_settings.experimentalInvalidation) {
+ print(" experiments: alternative-invalidation-strategy");
+ }
+ print(" worldType: updated");
+ print(" expectInitializeFromDill: false # or true?");
+ print(" invalidate:");
+ print(" - $uri");
+ print(" expectedLibraryCount: $dartFiles "
+ "# with parts this is not right");
+ print(" expectsRebuildBodiesOnly: true # or false?");
+ print("");
+ }
+
+ print("------------------------------------------------------------------");
+ }
+
+ void _rewriteImportsExportsToUriInternal(
+ Token uriToken, Uri oldUri, List<_Replacement> replacements, Uri newUri) {
+ Uri tokenUri = _getUri(uriToken, oldUri, resolvePackage: false);
+ if (tokenUri.scheme == "package" || tokenUri.scheme == "dart") return;
+ Uri asPackageUri = _getImportUri(tokenUri);
+ if (asPackageUri.scheme == "package") {
+ // Just replace with this package uri.
+ replacements.add(new _Replacement(
+ uriToken.offset - 1,
+ uriToken.offset + uriToken.length,
+ nullOrReplacement: '"${asPackageUri.toString()}"',
+ ));
+ } else {
+ String relative = relativizeUri(newUri, tokenUri, isWindows);
+ // TODO(jensj): Maybe if the relative uri becomes too long or has to many
+ // "../../" etc we should just use the absolute uri instead.
+ replacements.add(new _Replacement(
+ uriToken.offset - 1,
+ uriToken.offset + uriToken.length,
+ nullOrReplacement: '"${relative}"',
+ ));
+ }
+ }
+
+ Uri _getUri(Token uriToken, Uri uri, {bool resolvePackage: true}) {
+ String uriString = uriToken.lexeme;
+ uriString = uriString.substring(1, uriString.length - 1);
+ Uri uriTokenUri = uri.resolve(uriString);
+ if (resolvePackage && uriTokenUri.scheme == "package") {
+ Package package = _latestCrashingIncrementalCompiler
+ .currentPackagesMap[uriTokenUri.pathSegments.first];
+ uriTokenUri = package.packageUriRoot
+ .resolve(uriTokenUri.pathSegments.skip(1).join("/"));
+ }
+ return uriTokenUri;
+ }
+
+ Uri _getImportUri(Uri uri) {
+ return _latestCrashingIncrementalCompiler.userCode
+ .getEntryPointUri(uri, issueProblem: false);
+ }
+
+ Uint8List _sublist(Uint8List data, int start, int end) {
+ Uint8List result = new Uint8List(end - start);
+ result.setRange(0, result.length, data, start);
+ return result;
+ }
+
+ void _binarySearchDeleteData(Uri uri, Component initialComponent) async {
+ Uint8List latestCrashData = _fs.data[uri];
+ int offset = 0;
+ while (offset < latestCrashData.length) {
+ print("Working at offset $offset of ${latestCrashData.length}");
+ BytesBuilder builder = new BytesBuilder();
+ builder.add(_sublist(latestCrashData, 0, offset));
+ builder
+ .add(_sublist(latestCrashData, offset + 1, latestCrashData.length));
+ Uint8List candidate = builder.takeBytes();
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ // Deleting 1 char didn't crash; don't try to delete anymore starting
+ // here.
+ offset++;
+ continue;
+ }
+
+ // Find how long we can go.
+ int crashingAt = 1;
+ int noLongerCrashingAt;
+ while (true) {
+ int deleteChars = 2 * crashingAt;
+ if (offset + deleteChars > latestCrashData.length) {
+ deleteChars = latestCrashData.length - offset;
+ }
+ builder = new BytesBuilder();
+ builder.add(_sublist(latestCrashData, 0, offset));
+ builder.add(_sublist(
+ latestCrashData, offset + deleteChars, latestCrashData.length));
+ candidate = builder.takeBytes();
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ noLongerCrashingAt = deleteChars;
+ break;
+ }
+ crashingAt = deleteChars;
+ if (crashingAt + offset == latestCrashData.length) break;
+ }
+
+ if (noLongerCrashingAt == null) {
+ // We can delete the rest.
+ latestCrashData = candidate;
+ continue;
+ }
+
+ // Binary search between [crashingAt] and [noLongerCrashingAt].
+ while (crashingAt < noLongerCrashingAt) {
+ int mid = noLongerCrashingAt -
+ ((noLongerCrashingAt - crashingAt) >>
+ 1); // Get middle, rounding up.
+ builder = new BytesBuilder();
+ builder.add(_sublist(latestCrashData, 0, offset));
+ builder.add(
+ _sublist(latestCrashData, offset + mid, latestCrashData.length));
+ candidate = builder.takeBytes();
+ _fs.data[uri] = candidate;
+ if (await _crashesOnCompile(initialComponent)) {
+ crashingAt = mid;
+ } else {
+ // [noLongerCrashingAt] might actually crash now.
+ noLongerCrashingAt = mid - 1;
+ }
+ }
+
+ // This is basically an assert.
+ builder = new BytesBuilder();
+ builder.add(_sublist(latestCrashData, 0, offset));
+ builder.add(_sublist(
+ latestCrashData, offset + crashingAt, latestCrashData.length));
+ candidate = builder.takeBytes();
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ throw "Error in binary search.";
+ }
+ latestCrashData = candidate;
+ }
+
+ _fs.data[uri] = latestCrashData;
+ }
+
+ void _tryToRemoveUnreferencedFileContent(Component initialComponent,
+ {bool deleteFile: false}) async {
+ // Check if there now are any unused files.
+ if (_latestComponent == null) return;
+ Set<Uri> neededUris = _latestComponent.uriToSource.keys.toSet();
+ Map<Uri, Uint8List> copy = new Map.from(_fs.data);
+ bool removedSome = false;
+ if (await _shouldQuit()) return;
+ for (MapEntry<Uri, Uint8List> entry in _fs.data.entries) {
+ if (entry.value == null || entry.value.isEmpty) continue;
+ if (!entry.key.toString().endsWith(".dart")) continue;
+ if (!neededUris.contains(entry.key) && _fs.data[entry.key].length != 0) {
+ if (deleteFile) {
+ _fs.data[entry.key] = null;
+ } else {
+ _fs.data[entry.key] = new Uint8List(0);
+ }
+ print(" => Can probably also delete ${entry.key}");
+ removedSome = true;
+ }
+ }
+ if (removedSome) {
+ if (await _crashesOnCompile(initialComponent)) {
+ print(" => Yes; Could remove those too!");
+ } else {
+ print(" => No; Couldn't remove those too!");
+ _fs.data.clear();
+ _fs.data.addAll(copy);
+ }
+ }
+ }
+
+ void _deleteContent(
+ List<Uri> uris, int uriIndex, bool limitTo1, Component initialComponent,
+ {bool deleteFile: false}) async {
+ String extraMessageText = "all content of ";
+ if (deleteFile) extraMessageText = "";
+
+ if (!limitTo1) {
+ if (await _shouldQuit()) return;
+ Map<Uri, Uint8List> copy = new Map.from(_fs.data);
+ // Try to remove content of i and the next 9 (10 files in total).
+ for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
+ Uri uri = uris[j];
+ if (deleteFile) {
+ _fs.data[uri] = null;
+ } else {
+ _fs.data[uri] = new Uint8List(0);
+ }
+ }
+ if (!await _crashesOnCompile(initialComponent)) {
+ // Couldn't delete all 10 files. Restore and try the single one.
+ _fs.data.clear();
+ _fs.data.addAll(copy);
+ } else {
+ for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
+ Uri uri = uris[j];
+ print("Can delete ${extraMessageText}file $uri");
+ }
+ await _tryToRemoveUnreferencedFileContent(initialComponent,
+ deleteFile: deleteFile);
+ return;
+ }
+ }
+
+ if (await _shouldQuit()) return;
+ Uri uri = uris[uriIndex];
+ Uint8List data = _fs.data[uri];
+ if (deleteFile) {
+ _fs.data[uri] = null;
+ } else {
+ _fs.data[uri] = new Uint8List(0);
+ }
+ if (!await _crashesOnCompile(initialComponent)) {
+ print(
+ "Can't delete ${extraMessageText}file $uri -- keeping it (for now)");
+ _fs.data[uri] = data;
+
+ // For dart files we can't truncate completely try to "outline" them
+ // instead.
+ if (uri.toString().endsWith(".dart")) {
+ String textualOutlined =
+ textualOutline(data)?.replaceAll(RegExp(r'\n+'), "\n");
+
+ bool outlined = false;
+ if (textualOutlined != null) {
+ Uint8List candidate = utf8.encode(textualOutlined);
+ // Because textual outline doesn't do the right thing for nnbd, only
+ // replace if it's syntactically valid.
+ if (candidate.length != _fs.data[uri].length &&
+ _parsesWithoutError(candidate, _isUriNnbd(uri))) {
+ if (await _shouldQuit()) return;
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ print("Can't outline the file $uri -- keeping it (for now)");
+ _fs.data[uri] = data;
+ } else {
+ outlined = true;
+ print("Can outline the file $uri "
+ "(now ${_fs.data[uri].length} bytes)");
+ }
+ }
+ }
+ if (!outlined) {
+ // We can probably at least remove all comments then...
+ try {
+ List<String> strings = utf8.decode(_fs.data[uri]).split("\n");
+ List<String> stringsLeft = [];
+ for (String string in strings) {
+ if (!string.trim().startsWith("//")) stringsLeft.add(string);
+ }
+
+ Uint8List candidate = utf8.encode(stringsLeft.join("\n"));
+ if (candidate.length != _fs.data[uri].length) {
+ if (await _shouldQuit()) return;
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ print("Can't remove comments for file $uri -- "
+ "keeping it (for now)");
+ _fs.data[uri] = data;
+ } else {
+ print("Removed comments for the file $uri");
+ }
+ }
+ } catch (e) {
+ // crash in scanner/parser --- keep original file. This crash might
+ // be what we're looking for!
+ }
+ }
+ }
+ } else {
+ print("Can delete ${extraMessageText}file $uri");
+ await _tryToRemoveUnreferencedFileContent(initialComponent);
+ }
+ }
+
+ void _deleteBlocksOld(Uri uri, Component initialComponent) async {
+ if (uri.toString().endsWith(".json")) {
+ // Try to find annoying
+ //
+ // },
+ // {
+ // }
+ //
+ // part of json and remove it.
+ Uint8List data = _fs.data[uri];
+ String string = utf8.decode(data);
+ List<String> lines = string.split("\n");
+ for (int i = 0; i < lines.length - 2; i++) {
+ if (lines[i].trim() == "}," &&
+ lines[i + 1].trim() == "{" &&
+ lines[i + 2].trim() == "}") {
+ // This is the pattern we wanted to find. Remove it.
+ lines.removeRange(i, i + 2);
+ i--;
+ }
+ }
+ string = lines.join("\n");
+ _fs.data[uri] = utf8.encode(string);
+ if (!await _crashesOnCompile(initialComponent)) {
+ // For some reason that didn't work.
+ _fs.data[uri] = data;
+ }
+ }
+ if (!uri.toString().endsWith(".dart")) return;
+
+ Uint8List data = _fs.data[uri];
+ Uint8List latestCrashData = data;
+
+ List<int> lineStarts = [];
+
+ Token firstToken = parser_suite.scanRawBytes(
+ data,
+ _isUriNnbd(uri) ? _scannerConfiguration : _scannerConfigurationNonNNBD,
+ lineStarts);
+
+ if (firstToken == null) {
+ print("Got null token from scanner for $uri");
+ return;
+ }
+
+ int compileTry = 0;
+ Token token = firstToken;
+ while (token is ErrorToken) {
+ token = token.next;
+ }
+ List<_Replacement> replacements = [];
+ while (token != null && !token.isEof) {
+ bool tryCompile = false;
+ Token skipToToken = token;
+ // Skip very small blocks (e.g. "{}" or "{\n}");
+ if (token.endGroup != null && token.offset + 3 < token.endGroup.offset) {
+ replacements.add(new _Replacement(token.offset, token.endGroup.offset));
+ tryCompile = true;
+ skipToToken = token.endGroup;
+ } else if (token.lexeme == "@") {
+ if (token.next.next.endGroup != null) {
+ int end = token.next.next.endGroup.offset;
+ skipToToken = token.next.next.endGroup;
+ replacements.add(new _Replacement(token.offset - 1, end + 1));
+ tryCompile = true;
+ }
+ } else if (token.lexeme == "assert") {
+ if (token.next.endGroup != null) {
+ int end = token.next.endGroup.offset;
+ skipToToken = token.next.endGroup;
+ if (token.next.endGroup.next.lexeme == ",") {
+ end = token.next.endGroup.next.offset;
+ skipToToken = token.next.endGroup.next;
+ }
+ // +/- 1 to not include the start and the end character.
+ replacements.add(new _Replacement(token.offset - 1, end + 1));
+ tryCompile = true;
+ }
+ } else if ((token.lexeme == "abstract" && token.next.lexeme == "class") ||
+ token.lexeme == "class" ||
+ token.lexeme == "enum" ||
+ token.lexeme == "mixin" ||
+ token.lexeme == "static" ||
+ token.next.lexeme == "get" ||
+ token.next.lexeme == "set" ||
+ token.next.next.lexeme == "(" ||
+ (token.next.lexeme == "<" &&
+ token.next.endGroup != null &&
+ token.next.endGroup.next.next.lexeme == "(")) {
+ // Try to find and remove the entire class/enum/mixin/
+ // static procedure/getter/setter/simple procedure.
+ Token bracket = token;
+ for (int i = 0; i < 20; i++) {
+ // Find "{", but only go a maximum of 20 tokens to do that.
+ bracket = bracket.next;
+ if (bracket.lexeme == "{" && bracket.endGroup != null) {
+ break;
+ } else if ((bracket.lexeme == "(" || bracket.lexeme == "<") &&
+ bracket.endGroup != null) {
+ bracket = bracket.endGroup;
+ }
+ }
+ if (bracket.lexeme == "{" && bracket.endGroup != null) {
+ int end = bracket.endGroup.offset;
+ skipToToken = bracket.endGroup;
+ // +/- 1 to not include the start and the end character.
+ replacements.add(new _Replacement(token.offset - 1, end + 1));
+ tryCompile = true;
+ }
+ }
+
+ if (tryCompile) {
+ if (await _shouldQuit()) break;
+ if (_skip) {
+ _skip = false;
+ break;
+ }
+ stdout.write(".");
+ compileTry++;
+ if (compileTry % 50 == 0) {
+ stdout.write("(at $compileTry)\n");
+ }
+ Uint8List candidate = _replaceRange(replacements, data);
+ _fs.data[uri] = candidate;
+ if (await _crashesOnCompile(initialComponent)) {
+ print("Found block from "
+ "${replacements.last.from} to "
+ "${replacements.last.to} "
+ "that can be removed.");
+ latestCrashData = candidate;
+ token = skipToToken;
+ } else {
+ // Couldn't delete that.
+ replacements.removeLast();
+ }
+ }
+ token = token.next;
+ }
+ _fs.data[uri] = latestCrashData;
+ }
+
+ void _deleteBlocks(final Uri uri, Component initialComponent) async {
+ if (uri.toString().endsWith(".json")) {
+ // Try to find annoying
+ //
+ // },
+ // {
+ // }
+ //
+ // part of json and remove it.
+ Uint8List data = _fs.data[uri];
+ String string = utf8.decode(data);
+ List<String> lines = string.split("\n");
+ for (int i = 0; i < lines.length - 2; i++) {
+ if (lines[i].trim() == "}," &&
+ lines[i + 1].trim() == "{" &&
+ lines[i + 2].trim() == "}") {
+ // This is the pattern we wanted to find. Remove it.
+ lines.removeRange(i, i + 2);
+ i--;
+ }
+ }
+ string = lines.join("\n");
+ Uint8List candidate = utf8.encode(string);
+ if (candidate.length != data.length) {
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ // For some reason that didn't work.
+ _fs.data[uri] = data;
+ }
+ }
+
+ // Try to load json and remove blocks.
+ try {
+ Map json = jsonDecode(utf8.decode(data));
+ Map jsonModified = new Map.from(json);
+ List packages = json["packages"];
+ List packagesModified = new List.from(packages);
+ jsonModified["packages"] = packagesModified;
+ int i = 0;
+ print("Note there's ${packagesModified.length} packages in .json");
+ JsonEncoder jsonEncoder = new JsonEncoder.withIndent(" ");
+ while (i < packagesModified.length) {
+ var oldEntry = packagesModified.removeAt(i);
+ String jsonString = jsonEncoder.convert(jsonModified);
+ candidate = utf8.encode(jsonString);
+ Uint8List previous = _fs.data[uri];
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ // Couldn't remove that part.
+ _fs.data[uri] = previous;
+ packagesModified.insert(i, oldEntry);
+ i++;
+ } else {
+ print("Removed package from .json "
+ "(${packagesModified.length} left).");
+ }
+ }
+ } catch (e) {
+ // Couldn't decode it, so don't try to do anything.
+ }
+ return;
+ }
+ if (!uri.toString().endsWith(".dart")) return;
+
+ Uint8List data = _fs.data[uri];
+ DirectParserASTContentCompilationUnitEnd ast = getAST(data,
+ includeBody: true,
+ includeComments: false,
+ enableExtensionMethods: true,
+ enableNonNullable: _isUriNnbd(uri));
+
+ _CompilationHelperClass helper = new _CompilationHelperClass(data);
+
+ // Try to remove top level things one at a time.
+ for (DirectParserASTContent child in ast.children) {
+ bool shouldCompile = false;
+ String what = "";
+ if (child.isClass()) {
+ DirectParserASTContentClassDeclarationEnd cls = child.asClass();
+ helper.replacements.add(new _Replacement(
+ cls.beginToken.offset - 1, cls.endToken.offset + 1));
+ shouldCompile = true;
+ what = "class";
+ } else if (child.isMixinDeclaration()) {
+ DirectParserASTContentMixinDeclarationEnd decl =
+ child.asMixinDeclaration();
+ helper.replacements.add(new _Replacement(
+ decl.mixinKeyword.offset - 1, decl.endToken.offset + 1));
+ shouldCompile = true;
+ what = "mixin";
+ } else if (child.isNamedMixinDeclaration()) {
+ DirectParserASTContentNamedMixinApplicationEnd decl =
+ child.asNamedMixinDeclaration();
+ helper.replacements.add(
+ new _Replacement(decl.begin.offset - 1, decl.endToken.offset + 1));
+ shouldCompile = true;
+ what = "named mixin";
+ } else if (child.isExtension()) {
+ DirectParserASTContentExtensionDeclarationEnd decl =
+ child.asExtension();
+ helper.replacements.add(new _Replacement(
+ decl.extensionKeyword.offset - 1, decl.endToken.offset + 1));
+ shouldCompile = true;
+ what = "extension";
+ } else if (child.isTopLevelFields()) {
+ DirectParserASTContentTopLevelFieldsEnd decl = child.asTopLevelFields();
+ helper.replacements.add(new _Replacement(
+ decl.beginToken.offset - 1, decl.endToken.offset + 1));
+ shouldCompile = true;
+ what = "toplevel fields";
+ } else if (child.isTopLevelMethod()) {
+ DirectParserASTContentTopLevelMethodEnd decl = child.asTopLevelMethod();
+ helper.replacements.add(new _Replacement(
+ decl.beginToken.offset - 1, decl.endToken.offset + 1));
+ shouldCompile = true;
+ what = "toplevel method";
+ } else if (child.isEnum()) {
+ DirectParserASTContentEnumEnd decl = child.asEnum();
+ helper.replacements.add(new _Replacement(
+ decl.enumKeyword.offset - 1, decl.leftBrace.endGroup.offset + 1));
+ shouldCompile = true;
+ what = "enum";
+ } else if (child.isTypedef()) {
+ DirectParserASTContentFunctionTypeAliasEnd decl = child.asTypedef();
+ helper.replacements.add(new _Replacement(
+ decl.typedefKeyword.offset - 1, decl.endToken.offset + 1));
+ shouldCompile = true;
+ what = "typedef";
+ } else if (child.isMetadata()) {
+ DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
+ List<DirectParserASTContentMetadataEnd> metadata =
+ decl.getMetadataEntries();
+ if (metadata.isNotEmpty) {
+ helper.replacements.add(new _Replacement(
+ metadata.first.beginToken.offset - 1,
+ metadata.last.endToken.offset));
+ shouldCompile = true;
+ }
+ what = "metadata";
+ } else if (child.isImport()) {
+ DirectParserASTContentImportEnd decl = child.asImport();
+ helper.replacements.add(new _Replacement(
+ decl.importKeyword.offset - 1, decl.semicolon.offset + 1));
+ shouldCompile = true;
+ what = "import";
+ } else if (child.isExport()) {
+ DirectParserASTContentExportEnd decl = child.asExport();
+ helper.replacements.add(new _Replacement(
+ decl.exportKeyword.offset - 1, decl.semicolon.offset + 1));
+ shouldCompile = true;
+ what = "export";
+ } else if (child.isLibraryName()) {
+ DirectParserASTContentLibraryNameEnd decl = child.asLibraryName();
+ helper.replacements.add(new _Replacement(
+ decl.libraryKeyword.offset - 1, decl.semicolon.offset + 1));
+ shouldCompile = true;
+ what = "library name";
+ } else if (child.isPart()) {
+ DirectParserASTContentPartEnd decl = child.asPart();
+ helper.replacements.add(new _Replacement(
+ decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
+ shouldCompile = true;
+ what = "part";
+ } else if (child.isPartOf()) {
+ DirectParserASTContentPartOfEnd decl = child.asPartOf();
+ helper.replacements.add(new _Replacement(
+ decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
+ shouldCompile = true;
+ what = "part of";
+ } else if (child.isScript()) {
+ var decl = child.asScript();
+ helper.replacements.add(new _Replacement(
+ decl.token.offset - 1, decl.token.offset + decl.token.length));
+ shouldCompile = true;
+ what = "script";
+ }
+
+ if (shouldCompile) {
+ bool success =
+ await _tryReplaceAndCompile(helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ if (!success) {
+ if (child.isClass()) {
+ // Also try to remove all content of the class.
+ DirectParserASTContentClassDeclarationEnd decl = child.asClass();
+ DirectParserASTContentClassOrMixinBodyEnd body =
+ decl.getClassOrMixinBody();
+ if (body.beginToken.offset + 2 < body.endToken.offset) {
+ helper.replacements.add(new _Replacement(
+ body.beginToken.offset, body.endToken.offset));
+ what = "class body";
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+
+ if (!success) {
+ // Also try to remove members one at a time.
+ for (DirectParserASTContent child in body.children) {
+ shouldCompile = false;
+ if (child is DirectParserASTContentMemberEnd) {
+ if (child.isClassConstructor()) {
+ DirectParserASTContentClassConstructorEnd memberDecl =
+ child.getClassConstructor();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "class constructor";
+ shouldCompile = true;
+ } else if (child.isClassFields()) {
+ DirectParserASTContentClassFieldsEnd memberDecl =
+ child.getClassFields();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "class fields";
+ shouldCompile = true;
+ } else if (child.isClassMethod()) {
+ DirectParserASTContentClassMethodEnd memberDecl =
+ child.getClassMethod();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "class method";
+ shouldCompile = true;
+ } else if (child.isClassFactoryMethod()) {
+ DirectParserASTContentClassFactoryMethodEnd memberDecl =
+ child.getClassFactoryMethod();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "class factory method";
+ shouldCompile = true;
+ } else {
+ // throw "$child --- ${child.children}";
+ continue;
+ }
+ } else if (child.isMetadata()) {
+ DirectParserASTContentMetadataStarEnd decl =
+ child.asMetadata();
+ List<DirectParserASTContentMetadataEnd> metadata =
+ decl.getMetadataEntries();
+ if (metadata.isNotEmpty) {
+ helper.replacements.add(new _Replacement(
+ metadata.first.beginToken.offset - 1,
+ metadata.last.endToken.offset));
+ shouldCompile = true;
+ }
+ what = "metadata";
+ }
+ if (shouldCompile) {
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ if (!success) {
+ DirectParserASTContentBlockFunctionBodyEnd decl;
+ if (child is DirectParserASTContentMemberEnd) {
+ if (child.isClassMethod()) {
+ decl = child.getClassMethod().getBlockFunctionBody();
+ } else if (child.isClassConstructor()) {
+ decl =
+ child.getClassConstructor().getBlockFunctionBody();
+ }
+ }
+ if (decl != null &&
+ decl.beginToken.offset + 2 < decl.endToken.offset) {
+ helper.replacements.add(new _Replacement(
+ decl.beginToken.offset, decl.endToken.offset));
+ what = "class member content";
+ await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+ }
+ }
+ }
+ }
+
+ // Try to remove "extends", "implements" etc.
+ if (decl.getClassExtends().extendsKeyword != null) {
+ helper.replacements.add(new _Replacement(
+ decl.getClassExtends().extendsKeyword.offset - 1,
+ body.beginToken.offset));
+ what = "class extends";
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+ if (decl.getClassImplements().implementsKeyword != null) {
+ helper.replacements.add(new _Replacement(
+ decl.getClassImplements().implementsKeyword.offset - 1,
+ body.beginToken.offset));
+ what = "class implements";
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+ if (decl.getClassWithClause() != null) {
+ helper.replacements.add(new _Replacement(
+ decl.getClassWithClause().withKeyword.offset - 1,
+ body.beginToken.offset));
+ what = "class with clause";
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+ } else if (child.isMixinDeclaration()) {
+ // Also try to remove all content of the mixin.
+ DirectParserASTContentMixinDeclarationEnd decl =
+ child.asMixinDeclaration();
+ DirectParserASTContentClassOrMixinBodyEnd body =
+ decl.getClassOrMixinBody();
+ if (body.beginToken.offset + 2 < body.endToken.offset) {
+ helper.replacements.add(new _Replacement(
+ body.beginToken.offset, body.endToken.offset));
+ what = "mixin body";
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+
+ if (!success) {
+ // Also try to remove members one at a time.
+ for (DirectParserASTContent child in body.children) {
+ shouldCompile = false;
+ if (child is DirectParserASTContentMemberEnd) {
+ if (child.isMixinConstructor()) {
+ DirectParserASTContentMixinConstructorEnd memberDecl =
+ child.getMixinConstructor();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "mixin constructor";
+ shouldCompile = true;
+ } else if (child.isMixinFields()) {
+ DirectParserASTContentMixinFieldsEnd memberDecl =
+ child.getMixinFields();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "mixin fields";
+ shouldCompile = true;
+ } else if (child.isMixinMethod()) {
+ DirectParserASTContentMixinMethodEnd memberDecl =
+ child.getMixinMethod();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "mixin method";
+ shouldCompile = true;
+ } else if (child.isMixinFactoryMethod()) {
+ DirectParserASTContentMixinFactoryMethodEnd memberDecl =
+ child.getMixinFactoryMethod();
+ helper.replacements.add(new _Replacement(
+ memberDecl.beginToken.offset - 1,
+ memberDecl.endToken.offset + 1));
+ what = "mixin factory method";
+ shouldCompile = true;
+ } else {
+ // throw "$child --- ${child.children}";
+ continue;
+ }
+ } else if (child.isMetadata()) {
+ DirectParserASTContentMetadataStarEnd decl =
+ child.asMetadata();
+ List<DirectParserASTContentMetadataEnd> metadata =
+ decl.getMetadataEntries();
+ if (metadata.isNotEmpty) {
+ helper.replacements.add(new _Replacement(
+ metadata.first.beginToken.offset - 1,
+ metadata.last.endToken.offset));
+ shouldCompile = true;
+ }
+ what = "metadata";
+ }
+ if (shouldCompile) {
+ success = await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ if (!success) {
+ DirectParserASTContentBlockFunctionBodyEnd decl;
+ if (child is DirectParserASTContentMemberEnd) {
+ if (child.isClassMethod()) {
+ decl = child.getClassMethod().getBlockFunctionBody();
+ } else if (child.isClassConstructor()) {
+ decl =
+ child.getClassConstructor().getBlockFunctionBody();
+ }
+ }
+ if (decl != null &&
+ decl.beginToken.offset + 2 < decl.endToken.offset) {
+ helper.replacements.add(new _Replacement(
+ decl.beginToken.offset, decl.endToken.offset));
+ what = "class member content";
+ await _tryReplaceAndCompile(
+ helper, uri, initialComponent, what);
+ if (helper.shouldQuit) return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Future<bool> _tryReplaceAndCompile(_CompilationHelperClass data, Uri uri,
+ Component initialComponent, String what) async {
+ if (await _shouldQuit()) {
+ data.shouldQuit = true;
+ return false;
+ }
+ stdout.write(".");
+ data.compileTry++;
+ if (data.compileTry % 50 == 0) {
+ stdout.write("(at ${data.compileTry})\n");
+ }
+ Uint8List candidate = _replaceRange(data.replacements, data.originalData);
+
+ if (!_parsesWithoutError(candidate, _isUriNnbd(uri))) {
+ print("WARNING: Parser error after stuff at ${StackTrace.current}");
+ _parsesWithoutError(candidate, _isUriNnbd(uri));
+ _parsesWithoutError(data.originalData, _isUriNnbd(uri));
+ }
+
+ _fs.data[uri] = candidate;
+ if (await _crashesOnCompile(initialComponent)) {
+ print("Found $what from "
+ "${data.replacements.last.from} to "
+ "${data.replacements.last.to} "
+ "that can be removed.");
+ data.latestCrashData = candidate;
+ return true;
+ } else {
+ // Couldn't delete that.
+ data.replacements.removeLast();
+ _fs.data[uri] = data.latestCrashData;
+ return false;
+ }
+ }
+
+ void _deleteEmptyLines(Uri uri, Component initialComponent) async {
+ Uint8List data = _fs.data[uri];
+ List<Uint8List> lines = [];
+ int start = 0;
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == _$LF) {
+ if (i - start > 0) {
+ lines.add(_sublist(data, start, i));
+ }
+ start = i + 1;
+ }
+ }
+ if (data.length - start > 0) {
+ lines.add(_sublist(data, start, data.length));
+ }
+
+ final BytesBuilder builder = new BytesBuilder();
+ for (int j = 0; j < lines.length; j++) {
+ if (builder.isNotEmpty) {
+ builder.addByte(_$LF);
+ }
+ builder.add(lines[j]);
+ }
+ Uint8List candidate = builder.takeBytes();
+ if (candidate.length == data.length) return;
+
+ if (!_parsesWithoutError(candidate, _isUriNnbd(uri))) {
+ print("WARNING: Parser error after stuff at ${StackTrace.current}");
+ }
+
+ if (await _shouldQuit()) return;
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ // For some reason the empty lines are important.
+ _fs.data[uri] = data;
+ } else {
+ print("\nDeleted empty lines.");
+ }
+ }
+
+ void _deleteLines(Uri uri, Component initialComponent) async {
+ // Try to delete "lines".
+ Uint8List data = _fs.data[uri];
+ List<Uint8List> lines = [];
+ int start = 0;
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == _$LF) {
+ lines.add(_sublist(data, start, i));
+ start = i + 1;
+ }
+ }
+ lines.add(_sublist(data, start, data.length));
+ List<bool> include = new List.filled(lines.length, true);
+ Uint8List latestCrashData = data;
+ int length = 1;
+ int i = 0;
+ while (i < lines.length) {
+ if (await _shouldQuit()) break;
+ if (_skip) {
+ _skip = false;
+ break;
+ }
+ stdout.write(".");
+ if (i % 50 == 0) {
+ stdout.write("(at $i of ${lines.length})\n");
+ }
+ if (i + length > lines.length) {
+ length = lines.length - i;
+ }
+ for (int j = i; j < i + length; j++) {
+ include[j] = false;
+ }
+ final BytesBuilder builder = new BytesBuilder();
+ for (int j = 0; j < lines.length; j++) {
+ if (include[j]) {
+ if (builder.isNotEmpty) {
+ builder.addByte(_$LF);
+ }
+ builder.add(lines[j]);
+ }
+ }
+ Uint8List candidate = builder.takeBytes();
+ _fs.data[uri] = candidate;
+ if (!await _crashesOnCompile(initialComponent)) {
+ // Didn't crash => Can't remove line i-j.
+ for (int j = i; j < i + length; j++) {
+ include[j] = true;
+ }
+ if (length > 2) {
+ // Retry with length 2 at same i.
+ // The idea here is that for instance formatted json might have lines
+ // looking like
+ // {
+ // }
+ // where deleting just one line makes it invalid.
+ length = 2;
+ } else if (length > 1) {
+ // Retry with length 1 at same i.
+ length = 1;
+ } else {
+ // Couldn't with length 1 either.
+ i++;
+ }
+ } else {
+ print("\nCan delete line $i (inclusive) - ${i + length} (exclusive) "
+ "(of ${lines.length})");
+ latestCrashData = candidate;
+ i += length;
+ length *= 2;
+ }
+ }
+ _fs.data[uri] = latestCrashData;
+ }
+
+ Future<bool> _tryRemoveIfNotKnownByCompiler(Uri uri, initialComponent) async {
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) return false;
+ if (!uri.toString().endsWith(".dart")) return false;
+
+ if (_knownByCompiler(uri)) return false;
+
+ // Compiler might not know this. Can we delete it?
+ await _deleteContent([uri], 0, true, initialComponent);
+ if (_fs.data[uri] == null || _fs.data[uri].isEmpty) {
+ await _deleteContent([uri], 0, true, initialComponent, deleteFile: true);
+ return true;
+ }
+
+ return false;
+ }
+
+ bool _knownByCompiler(Uri uri) {
+ LibraryBuilder libraryBuilder = _latestCrashingIncrementalCompiler
+ .userCode.loader.builders[_getImportUri(uri)];
+ if (libraryBuilder != null) {
+ return true;
+ }
+ // TODO(jensj): Parts.
+ return false;
+ }
+
+ bool _isUriNnbd(Uri uri) {
+ LibraryBuilder libraryBuilder = _latestCrashingIncrementalCompiler
+ .userCode.loader.builders[_getImportUri(uri)];
+ if (libraryBuilder != null) {
+ return libraryBuilder.isNonNullableByDefault;
+ }
+ print("Couldn't lookup $uri");
+ for (LibraryBuilder libraryBuilder
+ in _latestCrashingIncrementalCompiler.userCode.loader.builders.values) {
+ if (libraryBuilder.importUri == uri) {
+ print("Found $uri as ${libraryBuilder.importUri} "
+ "(!= ${_getImportUri(uri)})");
+ return libraryBuilder.isNonNullableByDefault;
+ }
+ }
+ // This might be parts?
+ throw "Couldn't lookup $uri at all!";
+ }
+
+ Future<bool> _crashesOnCompile(Component initialComponent) async {
+ IncrementalCompiler incrementalCompiler;
+ if (_settings.noPlatform) {
+ incrementalCompiler = new IncrementalCompiler(_setupCompilerContext());
+ } else {
+ incrementalCompiler = new IncrementalCompiler.fromComponent(
+ _setupCompilerContext(), initialComponent);
+ }
+ incrementalCompiler.invalidate(_mainUri);
+ try {
+ _latestComponent = await incrementalCompiler.computeDelta();
+ if (_settings.serialize) {
+ ByteSink sink = new ByteSink();
+ BinaryPrinter printer = new BinaryPrinter(sink);
+ printer.writeComponentFile(_latestComponent);
+ sink.builder.takeBytes();
+ }
+ for (Uri uri in _settings.invalidate) {
+ incrementalCompiler.invalidate(uri);
+ Component delta = await incrementalCompiler.computeDelta();
+ if (_settings.serialize) {
+ ByteSink sink = new ByteSink();
+ BinaryPrinter printer = new BinaryPrinter(sink);
+ printer.writeComponentFile(delta);
+ sink.builder.takeBytes();
+ }
+ }
+ _latestComponent = null; // if it didn't crash this isn't relevant.
+ return false;
+ } catch (e, st) {
+ // Find line with #0 in it.
+ String eWithSt = "$e\n\n$st";
+ List<String> lines = eWithSt.split("\n");
+ String foundLine = "";
+ int lookFor = 0;
+ for (String line in lines) {
+ if (line.startsWith("#$lookFor")) {
+ foundLine += line;
+ lookFor++;
+ if (lookFor >= _settings.stackTraceMatches) {
+ break;
+ } else {
+ foundLine += "\n";
+ }
+ }
+ }
+ if (foundLine == null) throw "Unexpected crash without stacktrace: $e";
+ if (_expectedCrashLine == null) {
+ print("Got '$foundLine'");
+ _expectedCrashLine = foundLine;
+ _latestCrashingIncrementalCompiler = incrementalCompiler;
+ return true;
+ } else if (foundLine == _expectedCrashLine) {
+ _latestCrashingIncrementalCompiler = incrementalCompiler;
+ return true;
+ } else {
+ if (_settings.autoUncoverAllCrashes &&
+ !_settings.allAutoRedirects.contains(foundLine)) {
+ print("Crashed, but another place: $foundLine");
+ print(" ==> Adding to auto redirects!");
+ // Add the current one too, so we don't rediscover that one once we
+ // try minimizing the new ones.
+ _settings.allAutoRedirects.add(_expectedCrashLine);
+ _settings.allAutoRedirects.add(foundLine);
+ _settings.fileSystems.add(_fs.toJson());
+ } else if (_settings.askAboutRedirectCrashTarget &&
+ !_settings.askedAboutRedirect.contains(foundLine)) {
+ print("Crashed, but another place: $foundLine");
+ while (true) {
+ // Add the current one too, so we don't rediscover that again
+ // and asks about going back to it.
+ _settings.askedAboutRedirect.add(_expectedCrashLine);
+ _settings.askedAboutRedirect.add(foundLine);
+ print(eWithSt);
+ print("Should we redirect to searching for that? (y/n)");
+ String answer = stdin.readLineSync();
+ if (answer == "yes" || answer == "y") {
+ _expectedCrashLine = foundLine;
+ _latestCrashingIncrementalCompiler = incrementalCompiler;
+ return true;
+ } else if (answer == "no" || answer == "n") {
+ break;
+ } else {
+ print("Didn't get that answer. "
+ "Please answer 'yes, 'y', 'no' or 'n'");
+ }
+ }
+ }
+ return false;
+ }
+ }
+ }
+
+ Future<Component> _getInitialComponent() async {
+ IncrementalCompiler incrementalCompiler =
+ new IncrementalCompiler(_setupCompilerContext());
+ Component originalComponent = await incrementalCompiler.computeDelta();
+ return originalComponent;
+ }
+
+ CompilerContext _setupCompilerContext() {
+ CompilerOptions options = getOptions();
+
+ if (_settings.experimentalInvalidation) {
+ options.explicitExperimentalFlags ??= {};
+ options.explicitExperimentalFlags[
+ ExperimentalFlag.alternativeInvalidationStrategy] = true;
+ }
+
+ TargetFlags targetFlags = new TargetFlags(
+ enableNullSafety: false,
+ trackWidgetCreation: _settings.widgetTransformation);
+ Target target;
+ switch (_settings.targetString) {
+ case "VM":
+ target = new VmTarget(targetFlags);
+ break;
+ case "flutter":
+ target = new FlutterTarget(targetFlags);
+ break;
+ case "ddc":
+ target = new DevCompilerTarget(targetFlags);
+ break;
+ default:
+ throw "Unknown target '$target'";
+ }
+ options.target = target;
+ options.fileSystem = _fs;
+ options.sdkRoot = null;
+ options.sdkSummary = _settings.platformUri;
+ options.omitPlatform = false;
+ options.onDiagnostic = (DiagnosticMessage message) {
+ // don't care.
+ };
+ if (_settings.noPlatform) {
+ options.librariesSpecificationUri = null;
+ }
+
+ CompilerContext compilerContext = new CompilerContext(
+ new ProcessedOptions(options: options, inputs: [_mainUri]));
+ return compilerContext;
+ }
+
+ String _getFileAsStringContent(Uint8List rawBytes, bool nnbd) {
+ List<int> lineStarts = [];
+
+ Token firstToken = parser_suite.scanRawBytes(
+ rawBytes,
+ nnbd ? _scannerConfiguration : _scannerConfigurationNonNNBD,
+ lineStarts);
+
+ if (firstToken == null) {
+ throw "Got null token from scanner";
+ }
+
+ ParserTestListener parserTestListener = new ParserTestListener(false);
+ Parser parser = new Parser(parserTestListener);
+ parser.parseUnit(firstToken);
+ String parsedString =
+ parser_suite.tokenStreamToString(firstToken, lineStarts).toString();
+ return parsedString;
+ }
+
+ bool _parsesWithoutError(Uint8List rawBytes, bool nnbd) {
+ Token firstToken = parser_suite.scanRawBytes(rawBytes,
+ nnbd ? _scannerConfiguration : _scannerConfigurationNonNNBD, null);
+
+ if (firstToken == null) {
+ return false;
+ }
+
+ ParserErrorListener parserErrorListener = new ParserErrorListener();
+ Parser parser = new Parser(parserErrorListener);
+ parser.parseUnit(firstToken);
+ return !parserErrorListener.gotError;
+ }
+
+ ScannerConfiguration _scannerConfiguration = new ScannerConfiguration(
+ enableTripleShift: true,
+ enableExtensionMethods: true,
+ enableNonNullable: true);
+
+ ScannerConfiguration _scannerConfigurationNonNNBD = new ScannerConfiguration(
+ enableTripleShift: true,
+ enableExtensionMethods: true,
+ enableNonNullable: false);
+
+ List<int> _dataCache;
+ String _dataCacheString;
+ Uint8List _replaceRange(
+ List<_Replacement> unsortedReplacements, Uint8List rawData) {
+ // The offsets are character offsets, not byte offsets, so for non-ascii
+ // they are not the same so we need to work on the string, not the bytes.
+ if (identical(rawData, _dataCache)) {
+ // cache up to date.
+ } else {
+ _dataCache = rawData;
+ _dataCacheString = utf8.decode(rawData);
+ }
+
+ // The below assumes these are sorted.
+ List<_Replacement> sortedReplacements =
+ new List<_Replacement>.from(unsortedReplacements)..sort();
+ final StringBuffer builder = new StringBuffer();
+ int prev = 0;
+ for (int i = 0; i < sortedReplacements.length; i++) {
+ _Replacement replacement = sortedReplacements[i];
+ for (int j = prev; j <= replacement.from; j++) {
+ builder.writeCharCode(_dataCacheString.codeUnitAt(j));
+ }
+ if (replacement.nullOrReplacement != null) {
+ builder.write(replacement.nullOrReplacement);
+ }
+ prev = replacement.to;
+ }
+ for (int j = prev; j < _dataCacheString.length; j++) {
+ builder.writeCharCode(_dataCacheString.codeUnitAt(j));
+ }
+
+ Uint8List candidate = utf8.encode(builder.toString());
+ return candidate;
+ }
+}
+
+class ParserErrorListener extends Listener {
+ bool gotError = false;
+ List<Message> messages = [];
+ void handleRecoverableError(
+ Message message, Token startToken, Token endToken) {
+ gotError = true;
+ messages.add(message);
+ }
+}
+
+class _CompilationHelperClass {
+ int compileTry = 0;
+ bool shouldQuit = false;
+ List<_Replacement> replacements = [];
+ Uint8List latestCrashData;
+ final Uint8List originalData;
+
+ _CompilationHelperClass(this.originalData) : latestCrashData = originalData;
+}
+
+class _Replacement implements Comparable<_Replacement> {
+ final int from;
+ final int to;
+ final String nullOrReplacement;
+
+ _Replacement(this.from, this.to, {this.nullOrReplacement});
+
+ @override
+ int compareTo(_Replacement other) {
+ return from - other.from;
+ }
+}
+
+class _FakeFileSystem extends FileSystem {
+ bool _redirectAndRecord = true;
+ bool _initialized = false;
+ final Map<Uri, Uint8List> data = {};
+
+ @override
+ FileSystemEntity entityForUri(Uri uri) {
+ return new _FakeFileSystemEntity(this, uri);
+ }
+
+ initializeFromJson(Map<String, dynamic> json) {
+ _initialized = true;
+ _redirectAndRecord = json['_redirectAndRecord'];
+ data.clear();
+ List tmp = json['data'];
+ for (int i = 0; i < tmp.length; i += 2) {
+ Uri key = tmp[i] == null ? null : Uri.parse(tmp[i]);
+ if (tmp[i + 1] == null) {
+ data[key] = null;
+ } else if (tmp[i + 1] is String) {
+ data[key] = utf8.encode(tmp[i + 1]);
+ } else {
+ data[key] = Uint8List.fromList(new List<int>.from(tmp[i + 1]));
+ }
+ }
+ }
+
+ Map<String, dynamic> toJson() {
+ List tmp = [];
+ for (var entry in data.entries) {
+ if (entry.value == null) continue;
+ tmp.add(entry.key == null ? null : entry.key.toString());
+ dynamic out = entry.value;
+ if (entry.value != null && entry.value.isNotEmpty) {
+ try {
+ String string = utf8.decode(entry.value);
+ out = string;
+ } catch (e) {
+ // not a string...
+ }
+ }
+ tmp.add(out);
+ }
+ return {
+ '_redirectAndRecord': _redirectAndRecord,
+ 'data': tmp,
+ };
+ }
+}
+
+class _FakeFileSystemEntity extends FileSystemEntity {
+ final _FakeFileSystem fs;
+ final Uri uri;
+ _FakeFileSystemEntity(this.fs, this.uri);
+
+ void _ensureCachedIfOk() {
+ if (fs.data.containsKey(uri)) return;
+ if (fs._initialized) return;
+ if (!fs._redirectAndRecord) {
+ throw "Asked for file in non-recording mode that wasn't known";
+ }
+ File f = new File.fromUri(uri);
+ if (!f.existsSync()) {
+ fs.data[uri] = null;
+ return;
+ }
+ fs.data[uri] = f.readAsBytesSync();
+ }
+
+ @override
+ Future<bool> exists() {
+ _ensureCachedIfOk();
+ Uint8List data = fs.data[uri];
+ if (data == null) return Future.value(false);
+ return Future.value(true);
+ }
+
+ @override
+ Future<List<int>> readAsBytes() {
+ _ensureCachedIfOk();
+ Uint8List data = fs.data[uri];
+ if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
+ return Future.value(data);
+ }
+
+ @override
+ Future<String> readAsString() {
+ _ensureCachedIfOk();
+ Uint8List data = fs.data[uri];
+ if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
+ return Future.value(utf8.decode(data));
+ }
+}
+
+class _DoesntCrashOnInput {
+ _DoesntCrashOnInput();
+}
diff --git a/pkg/front_end/test/fasta/testing/suite.dart b/pkg/front_end/test/fasta/testing/suite.dart
index 64099f8..44c9145 100644
--- a/pkg/front_end/test/fasta/testing/suite.dart
+++ b/pkg/front_end/test/fasta/testing/suite.dart
@@ -838,13 +838,11 @@
EvaluationMode evaluationMode) {
constantEvaluator = new ConstantEvaluator(
backend, environmentDefines, typeEnvironment, this,
- desugarSets: desugarSets,
enableTripleShift: enableTripleShift,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluationMode: evaluationMode);
constantEvaluatorWithEmptyEnvironment = new ConstantEvaluator(
backend, {}, typeEnvironment, this,
- desugarSets: desugarSets,
enableTripleShift: enableTripleShift,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluationMode: evaluationMode);
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index ef0b0df..49cac17 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -349,6 +349,7 @@
indents
initializer2
inlinable
+inlineable
instance2
insufficient
intdiv
@@ -451,6 +452,7 @@
micro
minimize
minimizer
+minimizing
mintty
minutes
mismatched
@@ -559,6 +561,7 @@
recompiles
redir
redirections
+rediscover
reducer
referring
reflectee
@@ -583,6 +586,7 @@
retainingpath
retains
rev
+revealed
risky
rk
row
@@ -702,6 +706,7 @@
unawaited
unbreak
unconverted
+uncover
uncovers
underline
unpacked
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart b/pkg/front_end/testcases/general/constants/from_lib/main.dart
new file mode 100644
index 0000000..b301857
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, 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 'main_lib.dart' as a;
+
+const map = <int, String>{
+ ...a.map,
+};
+
+const set = <int>{
+ ...a.set,
+ ...a.list,
+};
+
+const list = <int>[
+ ...a.list,
+ ...a.set,
+];
+
+main() {}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.outline.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.outline.expect
new file mode 100644
index 0000000..280c0fe
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.outline.expect
@@ -0,0 +1,40 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart" as a;
+
+static const field core::Map<core::int*, core::String*>* map = mai::map;
+static const field core::Set<core::int*>* set = mai::set + mai::list;
+static const field core::List<core::int*>* list = mai::list + mai::set;
+static method main() → dynamic
+ ;
+
+library;
+import self as mai;
+import "dart:core" as core;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C9;
+static const field core::List<core::int*>* list = #C11;
+
+constants {
+ #C1 = 1
+ #C2 = "a"
+ #C3 = <dynamic>[#C1, #C2]
+ #C4 = core::_ImmutableMap<core::int*, core::String*> {_kvPairs:#C3}
+ #C5 = 2
+ #C6 = null
+ #C7 = <dynamic>[#C5, #C6]
+ #C8 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C7}
+ #C9 = col::_UnmodifiableSet<core::int*> {_map:#C8}
+ #C10 = 3
+ #C11 = <core::int*>[#C10]
+}
+
+Extra constant evaluation status:
+Evaluated: MapConcatenation @ org-dartlang-testcase:///main.dart:7:7 -> InstanceConstant(const _ImmutableMap<int*, String*>{_ImmutableMap._kvPairs: const <dynamic>[1, "a"]})
+Evaluated: SetConcatenation @ org-dartlang-testcase:///main.dart:11:18 -> InstanceConstant(const _UnmodifiableSet<int*>{_UnmodifiableSet._map: const _ImmutableMap<int*, Null>{_ImmutableMap._kvPairs: const <dynamic>[2, null, 3, null]}})
+Evaluated: ListConcatenation @ org-dartlang-testcase:///main.dart:16:19 -> ListConstant(const <int*>[3, 2])
+Extra constant evaluation: evaluated: 3, effectively constant: 3
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.expect
new file mode 100644
index 0000000..c76837e
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.expect
@@ -0,0 +1,36 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///main_lib.dart" as a;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C10;
+static const field core::List<core::int*>* list = #C11;
+static method main() → dynamic {}
+
+library;
+import self as self2;
+import "dart:core" as core;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C14;
+static const field core::List<core::int*>* list = #C15;
+
+constants {
+ #C1 = 1
+ #C2 = "a"
+ #C3 = <dynamic>[#C1, #C2]
+ #C4 = core::_ImmutableMap<core::int*, core::String*> {_kvPairs:#C3}
+ #C5 = 2
+ #C6 = null
+ #C7 = 3
+ #C8 = <dynamic>[#C5, #C6, #C7, #C6]
+ #C9 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C8}
+ #C10 = col::_UnmodifiableSet<core::int*> {_map:#C9}
+ #C11 = <core::int*>[#C7, #C5]
+ #C12 = <dynamic>[#C5, #C6]
+ #C13 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C12}
+ #C14 = col::_UnmodifiableSet<core::int*> {_map:#C13}
+ #C15 = <core::int*>[#C7]
+}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.transformed.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.transformed.expect
new file mode 100644
index 0000000..c76837e
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.strong.transformed.expect
@@ -0,0 +1,36 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///main_lib.dart" as a;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C10;
+static const field core::List<core::int*>* list = #C11;
+static method main() → dynamic {}
+
+library;
+import self as self2;
+import "dart:core" as core;
+
+static const field core::Map<core::int*, core::String*>* map = #C4;
+static const field core::Set<core::int*>* set = #C14;
+static const field core::List<core::int*>* list = #C15;
+
+constants {
+ #C1 = 1
+ #C2 = "a"
+ #C3 = <dynamic>[#C1, #C2]
+ #C4 = core::_ImmutableMap<core::int*, core::String*> {_kvPairs:#C3}
+ #C5 = 2
+ #C6 = null
+ #C7 = 3
+ #C8 = <dynamic>[#C5, #C6, #C7, #C6]
+ #C9 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C8}
+ #C10 = col::_UnmodifiableSet<core::int*> {_map:#C9}
+ #C11 = <core::int*>[#C7, #C5]
+ #C12 = <dynamic>[#C5, #C6]
+ #C13 = core::_ImmutableMap<core::int*, Null> {_kvPairs:#C12}
+ #C14 = col::_UnmodifiableSet<core::int*> {_map:#C13}
+ #C15 = <core::int*>[#C7]
+}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline.expect
new file mode 100644
index 0000000..ecc271e
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline.expect
@@ -0,0 +1,14 @@
+import 'main_lib.dart' as a;
+
+const map = <int, String>{
+ ...a.map,
+};
+const set = <int>{
+ ...a.set,
+ ...a.list,
+};
+const list = <int>[
+ ...a.list,
+ ...a.set,
+];
+main() {}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..b817c1c
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main.dart.textual_outline_modelled.expect
@@ -0,0 +1,14 @@
+import 'main_lib.dart' as a;
+
+const list = <int>[
+ ...a.list,
+ ...a.set,
+];
+const map = <int, String>{
+ ...a.map,
+};
+const set = <int>{
+ ...a.set,
+ ...a.list,
+};
+main() {}
diff --git a/pkg/front_end/testcases/general/constants/from_lib/main_lib.dart b/pkg/front_end/testcases/general/constants/from_lib/main_lib.dart
new file mode 100644
index 0000000..eccfc0b
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/main_lib.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.
+
+const map = <int, String>{
+ 1: 'a',
+};
+
+const set = <int>{
+ 2,
+};
+
+const list = <int>[
+ 3,
+];
diff --git a/pkg/front_end/testcases/general/constants/from_lib/test.options b/pkg/front_end/testcases/general/constants/from_lib/test.options
new file mode 100644
index 0000000..bfe6dc8
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/from_lib/test.options
@@ -0,0 +1 @@
+main_lib.dart
\ No newline at end of file
diff --git a/pkg/front_end/testcases/general/infer_equals.dart b/pkg/front_end/testcases/general/infer_equals.dart
new file mode 100644
index 0000000..5ef9b93
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, 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.6
+
+class Class {
+ var field;
+
+ operator ==(o) {
+ if (o is! Class) return false;
+ return field == o.field;
+ }
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.outline.expect b/pkg/front_end/testcases/general/infer_equals.dart.outline.expect
new file mode 100644
index 0000000..1e4df77
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.outline.expect
@@ -0,0 +1,22 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Class extends core::Object {
+ field dynamic field;
+ synthetic constructor •() → self::Class*
+ ;
+ operator ==(dynamic o) → core::bool*
+ ;
+ abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+ abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+ abstract member-signature method toString() → core::String*; -> core::Object::toString
+ abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+static method main() → dynamic
+ ;
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.strong.expect b/pkg/front_end/testcases/general/infer_equals.dart.strong.expect
new file mode 100644
index 0000000..f1fea95
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.strong.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Class extends core::Object {
+ field dynamic field = null;
+ synthetic constructor •() → self::Class*
+ : super core::Object::•()
+ ;
+ operator ==(dynamic o) → core::bool* {
+ if(!(o is self::Class*))
+ return false;
+ return this.{self::Class::field}.{core::Object::==}(o.field);
+ }
+ abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+ abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+ abstract member-signature method toString() → core::String*; -> core::Object::toString
+ abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.strong.transformed.expect b/pkg/front_end/testcases/general/infer_equals.dart.strong.transformed.expect
new file mode 100644
index 0000000..f1fea95
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.strong.transformed.expect
@@ -0,0 +1,25 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+class Class extends core::Object {
+ field dynamic field = null;
+ synthetic constructor •() → self::Class*
+ : super core::Object::•()
+ ;
+ operator ==(dynamic o) → core::bool* {
+ if(!(o is self::Class*))
+ return false;
+ return this.{self::Class::field}.{core::Object::==}(o.field);
+ }
+ abstract member-signature get _identityHashCode() → core::int*; -> core::Object::_identityHashCode
+ abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → core::bool*; -> core::Object::_instanceOf
+ abstract member-signature method _simpleInstanceOf(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOf
+ abstract member-signature method _simpleInstanceOfTrue(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfTrue
+ abstract member-signature method _simpleInstanceOfFalse(dynamic type) → core::bool*; -> core::Object::_simpleInstanceOfFalse
+ abstract member-signature get hashCode() → core::int*; -> core::Object::hashCode
+ abstract member-signature method toString() → core::String*; -> core::Object::toString
+ abstract member-signature method noSuchMethod(core::Invocation* invocation) → dynamic; -> core::Object::noSuchMethod
+ abstract member-signature get runtimeType() → core::Type*; -> core::Object::runtimeType
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.textual_outline.expect b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline.expect
new file mode 100644
index 0000000..f3b19e7
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+// @dart = 2.6
+class Class {
+ var field;
+ operator ==(o) {}
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/infer_equals.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..00f7d50
--- /dev/null
+++ b/pkg/front_end/testcases/general/infer_equals.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+// @dart = 2.6
+class Class {
+ operator ==(o) {}
+ var field;
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/outline.status b/pkg/front_end/testcases/outline.status
index 8ddb02d..259a2e3 100644
--- a/pkg/front_end/testcases/outline.status
+++ b/pkg/front_end/testcases/outline.status
@@ -14,7 +14,6 @@
general_nnbd_opt_out/override_setter_with_field: TypeCheckError
general/abstract_members: TypeCheckError
general/bug30695: TypeCheckError
-general/constants/const_collections: TypeCheckError
general/covariant_field: TypeCheckError
general/getter_vs_setter_type: TypeCheckError
general/infer_field_from_multiple: TypeCheckError
diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart
index 62f7def..be01ba0 100644
--- a/pkg/kernel/lib/target/targets.dart
+++ b/pkg/kernel/lib/target/targets.dart
@@ -90,12 +90,42 @@
/// Lowering of a list constant to a backend-specific representation.
Constant lowerListConstant(ListConstant constant) => constant;
+ /// Returns `true` if [constant] is lowered list constant created by
+ /// [lowerListConstant].
+ bool isLoweredListConstant(Constant constant) => false;
+
+ /// Calls `f` for each element in the lowered list [constant].
+ ///
+ /// This assumes that `isLoweredListConstant(constant)` is true.
+ void forEachLoweredListConstantElement(
+ Constant constant, void Function(Constant element) f) {}
+
/// Lowering of a set constant to a backend-specific representation.
Constant lowerSetConstant(SetConstant constant) => constant;
+ /// Returns `true` if [constant] is lowered set constant created by
+ /// [lowerSetConstant].
+ bool isLoweredSetConstant(Constant constant) => false;
+
+ /// Calls `f` for each element in the lowered set [constant].
+ ///
+ /// This assumes that `isLoweredSetConstant(constant)` is true.
+ void forEachLoweredSetConstantElement(
+ Constant constant, void Function(Constant element) f) {}
+
/// Lowering of a map constant to a backend-specific representation.
Constant lowerMapConstant(MapConstant constant) => constant;
+ /// Returns `true` if [constant] is lowered map constant created by
+ /// [lowerMapConstant].
+ bool isLoweredMapConstant(Constant constant) => false;
+
+ /// Calls `f` for each key/value pair in the lowered map [constant].
+ ///
+ /// This assumes that `lowerMapConstant(constant)` is true.
+ void forEachLoweredMapConstantEntry(
+ Constant constant, void Function(Constant key, Constant value) f) {}
+
/// Number semantics to use for this backend.
NumberSemantics get numberSemantics => NumberSemantics.vm;
diff --git a/pkg/kernel/lib/type_checker.dart b/pkg/kernel/lib/type_checker.dart
index 1b79258..0e24e6f 100644
--- a/pkg/kernel/lib/type_checker.dart
+++ b/pkg/kernel/lib/type_checker.dart
@@ -706,7 +706,7 @@
@override
DartType visitListConcatenation(ListConcatenation node) {
DartType type =
- environment.listType(node.typeArgument, currentLibrary.nonNullable);
+ environment.iterableType(node.typeArgument, currentLibrary.nonNullable);
for (Expression part in node.lists) {
DartType partType = visitExpression(part);
checkAssignable(node, type, partType);
@@ -717,7 +717,7 @@
@override
DartType visitSetConcatenation(SetConcatenation node) {
DartType type =
- environment.setType(node.typeArgument, currentLibrary.nonNullable);
+ environment.iterableType(node.typeArgument, currentLibrary.nonNullable);
for (Expression part in node.sets) {
DartType partType = visitExpression(part);
checkAssignable(node, type, partType);
diff --git a/pkg/kernel/lib/vm/constants_native_effects.dart b/pkg/kernel/lib/vm/constants_native_effects.dart
index 444c44b..8bb1bac 100644
--- a/pkg/kernel/lib/vm/constants_native_effects.dart
+++ b/pkg/kernel/lib/vm/constants_native_effects.dart
@@ -10,8 +10,11 @@
class VmConstantsBackend extends ConstantsBackend {
final Class immutableMapClass;
+ final Class unmodifiableSetClass;
+ final Field unmodifiableSetMap;
- VmConstantsBackend._(this.immutableMapClass);
+ VmConstantsBackend._(this.immutableMapClass, this.unmodifiableSetMap,
+ this.unmodifiableSetClass);
/// If [defines] is not `null` it will be used for handling
/// `const {bool,...}.fromEnvironment()` otherwise the current VM's values
@@ -21,8 +24,11 @@
final Class immutableMapClass = coreLibrary.classes
.firstWhere((Class klass) => klass.name == '_ImmutableMap');
assert(immutableMapClass != null);
+ Field unmodifiableSetMap = coreTypes.index
+ .getMember('dart:collection', '_UnmodifiableSet', '_map');
- return new VmConstantsBackend._(immutableMapClass);
+ return new VmConstantsBackend._(immutableMapClass, unmodifiableSetMap,
+ unmodifiableSetMap.enclosingClass);
}
@override
@@ -50,4 +56,63 @@
kvPairListField.getterReference: kvListConstant,
});
}
+
+ @override
+ bool isLoweredMapConstant(Constant constant) {
+ return constant is InstanceConstant &&
+ constant.classNode == immutableMapClass;
+ }
+
+ @override
+ void forEachLoweredMapConstantEntry(
+ Constant constant, void Function(Constant key, Constant value) f) {
+ assert(isLoweredMapConstant(constant));
+ final InstanceConstant instance = constant;
+ assert(immutableMapClass.fields.length == 1);
+ final Field kvPairListField = immutableMapClass.fields[0];
+ final ListConstant kvListConstant =
+ instance.fieldValues[kvPairListField.getterReference];
+ assert(kvListConstant.entries.length % 2 == 0);
+ for (int index = 0; index < kvListConstant.entries.length; index += 2) {
+ f(kvListConstant.entries[index], kvListConstant.entries[index + 1]);
+ }
+ }
+
+ @override
+ Constant lowerSetConstant(SetConstant constant) {
+ final DartType elementType = constant.typeArgument;
+ final List<Constant> entries = constant.entries;
+ final List<ConstantMapEntry> mapEntries =
+ new List<ConstantMapEntry>.filled(entries.length, null);
+ for (int i = 0; i < entries.length; ++i) {
+ mapEntries[i] = new ConstantMapEntry(entries[i], new NullConstant());
+ }
+ Constant map = lowerMapConstant(
+ new MapConstant(elementType, const NullType(), mapEntries));
+ return new InstanceConstant(unmodifiableSetClass.reference, [elementType],
+ <Reference, Constant>{unmodifiableSetMap.getterReference: map});
+ }
+
+ @override
+ bool isLoweredSetConstant(Constant constant) {
+ if (constant is InstanceConstant &&
+ constant.classNode == unmodifiableSetClass) {
+ InstanceConstant instance = constant;
+ return isLoweredMapConstant(
+ instance.fieldValues[unmodifiableSetMap.getterReference]);
+ }
+ return false;
+ }
+
+ @override
+ void forEachLoweredSetConstantElement(
+ Constant constant, void Function(Constant element) f) {
+ assert(isLoweredSetConstant(constant));
+ final InstanceConstant instance = constant;
+ final Constant mapConstant =
+ instance.fieldValues[unmodifiableSetMap.getterReference];
+ forEachLoweredMapConstantEntry(mapConstant, (Constant key, Constant value) {
+ f(key);
+ });
+ }
}
diff --git a/pkg/vm/test/incremental_compiler_test.dart b/pkg/vm/test/incremental_compiler_test.dart
index 00a1a1c..179c37b 100644
--- a/pkg/vm/test/incremental_compiler_test.dart
+++ b/pkg/vm/test/incremental_compiler_test.dart
@@ -134,7 +134,9 @@
/// If [getAllSources] is false it will ask specifically for report
/// (and thus hits) for "lib1.dart" only.
Future<Set<int>> collectAndCheckCoverageData(int port, bool getAllSources,
- {bool resume: true}) async {
+ {bool resume: true,
+ bool onGetAllVerifyCount: true,
+ Set<int> coverageForLines}) async {
RemoteVm remoteVm = new RemoteVm(port);
// Wait for the script to have finished.
@@ -189,7 +191,7 @@
}
i++;
}
- if (getAllSources) {
+ if (getAllSources && onGetAllVerifyCount) {
expect(scriptIdToIndex.length >= 2, isTrue);
}
@@ -226,12 +228,16 @@
}
}
for (int pos in coverage["misses"]) positions.add(pos);
- for (int pos in range["possibleBreakpoints"]) positions.add(pos);
+ if (range["possibleBreakpoints"] != null) {
+ for (int pos in range["possibleBreakpoints"]) positions.add(pos);
+ }
Map script = scriptIndexToScript[range["scriptIndex"]];
Set<int> knownPositions = new Set<int>();
+ Map<int, int> tokenPosToLine = {};
if (script["tokenPosTable"] != null) {
for (List tokenPosTableLine in script["tokenPosTable"]) {
for (int i = 1; i < tokenPosTableLine.length; i += 2) {
+ tokenPosToLine[tokenPosTableLine[i]] = tokenPosTableLine[0];
knownPositions.add(tokenPosTableLine[i]);
}
}
@@ -244,6 +250,14 @@
"line and column.");
}
}
+
+ if (coverageForLines != null) {
+ for (int pos in coverage["hits"]) {
+ if (lib1scriptIndices.contains(range["scriptIndex"])) {
+ coverageForLines.add(tokenPosToLine[pos]);
+ }
+ }
+ }
}
}
}
@@ -486,6 +500,243 @@
});
});
+ group('multiple kernels constant coverage', () {
+ Directory mytest;
+ File main;
+ File lib1;
+ int lineForUnnamedConstructor;
+ int lineForNamedConstructor;
+ Process vm;
+ setUpAll(() {
+ mytest = Directory.systemTemp.createTempSync('incremental');
+ main = new File('${mytest.path}/main.dart')..createSync();
+ main.writeAsStringSync("""
+ // This file - combined with the lib - should have coverage for both
+ // constructors of Foo.
+ import 'lib1.dart' as lib1;
+
+ void testFunction() {
+ const foo = lib1.Foo.named();
+ const foo2 = lib1.Foo.named();
+ if (!identical(foo, foo2)) throw "what?";
+ }
+
+ main() {
+ lib1.testFunction();
+ testFunction();
+ print("main");
+ }
+ """);
+ lib1 = new File('${mytest.path}/lib1.dart')..createSync();
+ lib1.writeAsStringSync("""
+ // Compiling this file should mark the default constructor - but not the
+ // named constructor - as having coverage.
+ class Foo {
+ final int x;
+ const Foo([int? x]) : this.x = x ?? 42;
+ const Foo.named([int? x]) : this.x = x ?? 42;
+ }
+
+ void testFunction() {
+ const foo = Foo();
+ const foo2 = Foo();
+ if (!identical(foo, foo2)) throw "what?";
+ }
+
+ main() {
+ testFunction();
+ print("lib1");
+ }
+ """);
+ lineForUnnamedConstructor = 5;
+ lineForNamedConstructor = 6;
+ });
+
+ tearDownAll(() {
+ try {
+ mytest.deleteSync(recursive: true);
+ } catch (_) {
+ // Ignore errors;
+ }
+ try {
+ vm.kill();
+ } catch (_) {
+ // Ignore errors;
+ }
+ });
+
+ Future<Set<int>> runAndGetLineCoverage(
+ File list, String expectStdoutContains) async {
+ vm = await Process.start(Platform.resolvedExecutable, <String>[
+ "--pause-isolates-on-exit",
+ "--enable-vm-service:0",
+ "--disable-service-auth-codes",
+ "--disable-dart-dev",
+ list.path
+ ]);
+
+ const kObservatoryListening = 'Observatory listening on ';
+ final RegExp observatoryPortRegExp =
+ new RegExp("Observatory listening on http://127.0.0.1:\([0-9]*\)");
+ int port;
+ final splitter = new LineSplitter();
+ Completer<String> portLineCompleter = new Completer<String>();
+ Set<int> coverageLines = {};
+ bool foundExpectedString = false;
+ vm.stdout
+ .transform(utf8.decoder)
+ .transform(splitter)
+ .listen((String s) async {
+ if (s == expectStdoutContains) {
+ foundExpectedString = true;
+ }
+ if (s.startsWith(kObservatoryListening)) {
+ expect(observatoryPortRegExp.hasMatch(s), isTrue);
+ final match = observatoryPortRegExp.firstMatch(s);
+ port = int.parse(match.group(1));
+ await collectAndCheckCoverageData(port, true,
+ onGetAllVerifyCount: false, coverageForLines: coverageLines);
+ if (!portLineCompleter.isCompleted) {
+ portLineCompleter.complete("done");
+ }
+ }
+ print("vm stdout: $s");
+ });
+ vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
+ print("vm stderr: $s");
+ });
+ await portLineCompleter.future;
+ print("Compiler terminated with ${await vm.exitCode} exit code");
+ expect(foundExpectedString, isTrue);
+ return coverageLines;
+ }
+
+ test('compile seperatly, check coverage', () async {
+ Directory dir = mytest.createTempSync();
+
+ // First compile lib, run and verify coverage (un-named constructor
+ // covered, but not the named constructor).
+ // Note that it's called 'lib1' to match with expectations from coverage
+ // collector helper in this file.
+ File libDill = File(p.join(dir.path, p.basename(lib1.path + ".dill")));
+ IncrementalCompiler compiler = new IncrementalCompiler(options, lib1.uri);
+ Component component = await compiler.compile();
+ expect(component.libraries.length, equals(1));
+ expect(component.libraries.single.fileUri, equals(lib1.uri));
+ IOSink sink = libDill.openWrite();
+ BinaryPrinter printer = new BinaryPrinter(sink);
+ printer.writeComponentFile(component);
+ await sink.flush();
+ await sink.close();
+ File list = new File(p.join(dir.path, 'dill.list'))..createSync();
+ list.writeAsStringSync("#@dill\n${libDill.path}\n");
+ Set<int> lineCoverage = await runAndGetLineCoverage(list, "lib1");
+ // Expect coverage for unnamed constructor but not for the named one.
+ expect(
+ lineCoverage.intersection(
+ {lineForUnnamedConstructor, lineForNamedConstructor}),
+ equals({lineForUnnamedConstructor}));
+
+ try {
+ vm.kill();
+ } catch (_) {
+ // Ignore errors;
+ }
+ // Accept the compile to not include the lib again.
+ compiler.accept();
+
+ // Then compile lib, run and verify coverage (un-named constructor
+ // covered, and the named constructor coveraged too).
+ File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
+ component = await compiler.compile(entryPoint: main.uri);
+ expect(component.libraries.length, equals(1));
+ expect(component.libraries.single.fileUri, equals(main.uri));
+ sink = mainDill.openWrite();
+ printer = new BinaryPrinter(sink);
+ printer.writeComponentFile(component);
+ await sink.flush();
+ await sink.close();
+ list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
+ lineCoverage = await runAndGetLineCoverage(list, "main");
+
+ // Expect coverage for both unnamed constructor and for the named one.
+ expect(
+ lineCoverage.intersection(
+ {lineForUnnamedConstructor, lineForNamedConstructor}),
+ equals({lineForUnnamedConstructor, lineForNamedConstructor}));
+
+ try {
+ vm.kill();
+ } catch (_) {
+ // Ignore errors;
+ }
+ // Accept the compile to not include the lib again.
+ compiler.accept();
+
+ // Finally, change lib to shift the constructors so the old line numbers
+ // doesn't match. Compile lib by itself, compile lib, run with the old
+ // main and verify coverage is still correct (both un-named constructor
+ // and named constructor (at new line numbers) are covered, and the old
+ // line numbers are not coverage.
+
+ lib1.writeAsStringSync("""
+ //
+ // Shift lines down by five
+ // lines so the original
+ // lines can't be coverred
+ //
+ class Foo {
+ final int x;
+ const Foo([int? x]) : this.x = x ?? 42;
+ const Foo.named([int? x]) : this.x = x ?? 42;
+ }
+
+ void testFunction() {
+ const foo = Foo();
+ const foo2 = Foo();
+ if (!identical(foo, foo2)) throw "what?";
+ }
+
+ main() {
+ testFunction();
+ print("lib1");
+ }
+ """);
+ int newLineForUnnamedConstructor = 8;
+ int newLineForNamedConstructor = 9;
+ compiler.invalidate(lib1.uri);
+ component = await compiler.compile(entryPoint: lib1.uri);
+ expect(component.libraries.length, equals(1));
+ expect(component.libraries.single.fileUri, equals(lib1.uri));
+ sink = libDill.openWrite();
+ printer = new BinaryPrinter(sink);
+ printer.writeComponentFile(component);
+ await sink.flush();
+ await sink.close();
+ list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
+ lineCoverage = await runAndGetLineCoverage(list, "main");
+
+ // Expect coverage for both unnamed constructor and for the named one on
+ // the new positions, but no coverage on the old positions.
+ expect(
+ lineCoverage.intersection({
+ lineForUnnamedConstructor,
+ lineForNamedConstructor,
+ newLineForUnnamedConstructor,
+ newLineForNamedConstructor
+ }),
+ equals({newLineForUnnamedConstructor, newLineForNamedConstructor}));
+
+ try {
+ vm.kill();
+ } catch (_) {
+ // Ignore errors;
+ }
+ // Accept the compile to not include the lib again.
+ compiler.accept();
+ });
+ });
+
group('multiple kernels 2', () {
Directory mytest;
File main;
diff --git a/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart b/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart
new file mode 100644
index 0000000..b0f078d
--- /dev/null
+++ b/runtime/observatory/tests/service/get_source_report_const_coverage_lib.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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 "get_source_report_const_coverage_test.dart";
+
+void testFunction() {
+ const namedFoo = Foo.named3();
+ const namedFoo2 = Foo.named3();
+ const namedIdentical = identical(namedFoo, namedFoo2);
+ print("namedIdentical: $namedIdentical");
+}
diff --git a/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart b/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart
new file mode 100644
index 0000000..32cb035
--- /dev/null
+++ b/runtime/observatory/tests/service/get_source_report_const_coverage_test.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, 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 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+import 'service_test_common.dart';
+import 'dart:developer';
+
+import 'get_source_report_const_coverage_lib.dart' as lib;
+
+const String filename = "get_source_report_const_coverage_test";
+const Set<int> expectedLinesHit = {20, 22, 26};
+const Set<int> expectedLinesNotHit = {24};
+
+class Foo {
+ final int x;
+ // Expect this constructor to be coverage by coverage.
+ const Foo([int? x]) : this.x = x ?? 42;
+ // Expect this constructor to be coverage by coverage too.
+ const Foo.named1([int? x]) : this.x = x ?? 42;
+ // Expect this constructor to *NOT* be coverage by coverage.
+ const Foo.named2([int? x]) : this.x = x ?? 42;
+ // Expect this constructor to be coverage by coverage too (from lib).
+ const Foo.named3([int? x]) : this.x = x ?? 42;
+}
+
+void testFunction() {
+ const foo = Foo();
+ const foo2 = Foo();
+ const fooIdentical = identical(foo, foo2);
+ print(fooIdentical);
+
+ const namedFoo = Foo.named1();
+ const namedFoo2 = Foo.named1();
+ const namedIdentical = identical(namedFoo, namedFoo2);
+ print(fooIdentical);
+
+ debugger();
+
+ // That this is called after (or at all) is not relevent for the code
+ // coverage of constants.
+ lib.testFunction();
+
+ print("Done");
+}
+
+var tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ (Isolate isolate) async {
+ final stack = await isolate.getStack();
+
+ // Make sure we are in the right place.
+ expect(stack.type, equals('Stack'));
+ expect(stack['frames'].length, greaterThanOrEqualTo(1));
+ expect(stack['frames'][0].function.name, equals('testFunction'));
+
+ final List<Script> scripts = await isolate.getScripts();
+ Script? foundScript;
+ for (Script script in scripts) {
+ if (script.uri.contains(filename)) {
+ foundScript = script;
+ break;
+ }
+ }
+
+ Set<int> hits;
+ {
+ // Get report for everything; then collect for this library.
+ final Map<String, Object> params = {
+ 'reports': ['Coverage'],
+ };
+ final coverage =
+ await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+ hits = getHitsFor(coverage, filename);
+ await foundScript!.load();
+ final Set<int> lines = {};
+ for (int hit in hits) {
+ // We expect every hit to be translatable to line
+ // (i.e. tokenToLine to return non-null).
+ lines.add(foundScript.tokenToLine(hit)!);
+ }
+ print("Token position hits: $hits --- line hits: $lines");
+ expect(lines.intersection(expectedLinesHit), equals(expectedLinesHit));
+ expect(lines.intersection(expectedLinesNotHit), isEmpty);
+ }
+ {
+ // Now get report for the this file only.
+ final Map<String, Object> params = {
+ 'reports': ['Coverage'],
+ 'scriptId': foundScript.id!
+ };
+ final coverage =
+ await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+ final Set<int> localHits = getHitsFor(coverage, filename);
+ expect(localHits.length, equals(hits.length));
+ expect(hits.toList()..sort(), equals(localHits.toList()..sort()));
+ print(localHits);
+ }
+ },
+];
+
+Set<int> getHitsFor(Map coverage, String uriContains) {
+ final List scripts = coverage["scripts"];
+ final Set<int> scriptIdsWanted = {};
+ for (int i = 0; i < scripts.length; i++) {
+ final Map script = scripts[i];
+ final String scriptUri = script["uri"];
+ if (scriptUri.contains(uriContains)) {
+ scriptIdsWanted.add(i);
+ }
+ }
+ final List ranges = coverage["ranges"];
+ final Set<int> hits = {};
+ for (int i = 0; i < ranges.length; i++) {
+ final Map range = ranges[i];
+ if (scriptIdsWanted.contains(range["scriptIndex"])) {
+ if (range["coverage"] != null) {
+ for (int hit in range["coverage"]["hits"]) {
+ hits.add(hit);
+ }
+ }
+ }
+ }
+ return hits;
+}
+
+main(args) => runIsolateTests(args, tests, testeeConcurrent: testFunction);
diff --git a/runtime/observatory/tests/service/get_source_report_test.dart b/runtime/observatory/tests/service/get_source_report_test.dart
index adea481..a1d5206 100644
--- a/runtime/observatory/tests/service/get_source_report_test.dart
+++ b/runtime/observatory/tests/service/get_source_report_test.dart
@@ -91,7 +91,7 @@
final numRanges = coverage['ranges'].length;
expect(coverage['type'], equals('SourceReport'));
- expect(numRanges, equals(10));
+ expect(numRanges, equals(11));
expect(coverage['ranges'][0], equals(expectedRange));
expect(coverage['scripts'].length, 1);
expect(
@@ -106,7 +106,7 @@
};
coverage = await isolate.invokeRpcNoUpgrade('getSourceReport', params);
expect(coverage['type'], equals('SourceReport'));
- expect(coverage['ranges'].length, 11);
+ expect(coverage['ranges'].length, 12);
expect(allRangesCompiled(coverage), isTrue);
// One function
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index b62a6e9..59145d2 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -109,6 +109,7 @@
get_allocation_samples_test: SkipByDesign # Debugger is disabled in AOT mode.
get_isolate_after_language_error_test: CompileTimeError
get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
+get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
get_source_report_with_mixin_test: SkipByDesign # Debugger is disabled in AOT mode.
get_stack_limit_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart
new file mode 100644
index 0000000..b0f078d
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_lib.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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 "get_source_report_const_coverage_test.dart";
+
+void testFunction() {
+ const namedFoo = Foo.named3();
+ const namedFoo2 = Foo.named3();
+ const namedIdentical = identical(namedFoo, namedFoo2);
+ print("namedIdentical: $namedIdentical");
+}
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart
new file mode 100644
index 0000000..7c6af16
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_source_report_const_coverage_test.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, 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 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+import 'service_test_common.dart';
+import 'dart:developer';
+
+import 'get_source_report_const_coverage_lib.dart' as lib;
+
+const String filename = "get_source_report_const_coverage_test";
+const Set<int> expectedLinesHit = {20, 22, 26};
+const Set<int> expectedLinesNotHit = {24};
+
+class Foo {
+ final int x;
+ // Expect this constructor to be coverage by coverage.
+ const Foo([int x]) : this.x = x ?? 42;
+ // Expect this constructor to be coverage by coverage too.
+ const Foo.named1([int x]) : this.x = x ?? 42;
+ // Expect this constructor to *NOT* be coverage by coverage.
+ const Foo.named2([int x]) : this.x = x ?? 42;
+ // Expect this constructor to be coverage by coverage too (from lib).
+ const Foo.named3([int x]) : this.x = x ?? 42;
+}
+
+void testFunction() {
+ const foo = Foo();
+ const foo2 = Foo();
+ const fooIdentical = identical(foo, foo2);
+ print(fooIdentical);
+
+ const namedFoo = Foo.named1();
+ const namedFoo2 = Foo.named1();
+ const namedIdentical = identical(namedFoo, namedFoo2);
+ print(fooIdentical);
+
+ debugger();
+
+ // That this is called after (or at all) is not relevent for the code
+ // coverage of constants.
+ lib.testFunction();
+
+ print("Done");
+}
+
+var tests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ (Isolate isolate) async {
+ final stack = await isolate.getStack();
+
+ // Make sure we are in the right place.
+ expect(stack.type, equals('Stack'));
+ expect(stack['frames'].length, greaterThanOrEqualTo(1));
+ expect(stack['frames'][0].function.name, equals('testFunction'));
+
+ final List<Script> scripts = await isolate.getScripts();
+ Script foundScript;
+ for (Script script in scripts) {
+ if (script.uri.contains(filename)) {
+ foundScript = script;
+ break;
+ }
+ }
+
+ Set<int> hits;
+ {
+ // Get report for everything; then collect for this library.
+ final Map<String, Object> params = {
+ 'reports': ['Coverage'],
+ };
+ final coverage =
+ await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+ hits = getHitsFor(coverage, filename);
+ await foundScript.load();
+ final Set<int> lines = {};
+ for (int hit in hits) {
+ // We expect every hit to be translatable to line
+ // (i.e. tokenToLine to return non-null).
+ lines.add(foundScript.tokenToLine(hit));
+ }
+ print("Token position hits: $hits --- line hits: $lines");
+ expect(lines.intersection(expectedLinesHit), equals(expectedLinesHit));
+ expect(lines.intersection(expectedLinesNotHit), isEmpty);
+ }
+ {
+ // Now get report for the this file only.
+ final Map<String, Object> params = {
+ 'reports': ['Coverage'],
+ 'scriptId': foundScript.id
+ };
+ final coverage =
+ await isolate.invokeRpcNoUpgrade('getSourceReport', params);
+ final Set<int> localHits = getHitsFor(coverage, filename);
+ expect(localHits.length, equals(hits.length));
+ expect(hits.toList()..sort(), equals(localHits.toList()..sort()));
+ print(localHits);
+ }
+ },
+];
+
+Set<int> getHitsFor(Map coverage, String uriContains) {
+ final List scripts = coverage["scripts"];
+ final Set<int> scriptIdsWanted = {};
+ for (int i = 0; i < scripts.length; i++) {
+ final Map script = scripts[i];
+ final String scriptUri = script["uri"];
+ if (scriptUri.contains(uriContains)) {
+ scriptIdsWanted.add(i);
+ }
+ }
+ final List ranges = coverage["ranges"];
+ final Set<int> hits = {};
+ for (int i = 0; i < ranges.length; i++) {
+ final Map range = ranges[i];
+ if (scriptIdsWanted.contains(range["scriptIndex"])) {
+ if (range["coverage"] != null) {
+ for (int hit in range["coverage"]["hits"]) {
+ hits.add(hit);
+ }
+ }
+ }
+ }
+ return hits;
+}
+
+main(args) => runIsolateTests(args, tests, testeeConcurrent: testFunction);
diff --git a/runtime/observatory_2/tests/service_2/get_source_report_test.dart b/runtime/observatory_2/tests/service_2/get_source_report_test.dart
index 97606de..78ea471 100644
--- a/runtime/observatory_2/tests/service_2/get_source_report_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_source_report_test.dart
@@ -91,7 +91,7 @@
final numRanges = coverage['ranges'].length;
expect(coverage['type'], equals('SourceReport'));
- expect(numRanges, equals(10));
+ expect(numRanges, equals(11));
expect(coverage['ranges'][0], equals(expectedRange));
expect(coverage['scripts'].length, 1);
expect(
@@ -106,7 +106,7 @@
};
coverage = await isolate.invokeRpcNoUpgrade('getSourceReport', params);
expect(coverage['type'], equals('SourceReport'));
- expect(coverage['ranges'].length, 11);
+ expect(coverage['ranges'].length, 12);
expect(allRangesCompiled(coverage), isTrue);
// One function
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index 2b6e18b..5522831 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -109,6 +109,7 @@
get_allocation_samples_test: SkipByDesign # Debugger is disabled in AOT mode.
get_isolate_after_language_error_test: CompileTimeError
get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
+get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
get_source_report_with_mixin_test: SkipByDesign # Debugger is disabled in AOT mode.
get_stack_limit_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index a90b40d..794f917 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -2807,6 +2807,33 @@
return H.DartString(reader_.BufferAt(ReaderOffset()), size, Heap::kOld);
}
+ExternalTypedDataPtr KernelReaderHelper::GetConstantCoverageFor(
+ intptr_t index) {
+ AlternativeReadingScope alt(&reader_);
+ SetOffset(GetOffsetForSourceInfo(index));
+ SkipBytes(ReadUInt()); // skip uri.
+ SkipBytes(ReadUInt()); // skip source.
+ const intptr_t line_start_count = ReadUInt(); // read number of line start
+ // entries.
+ for (intptr_t i = 0; i < line_start_count; ++i) {
+ ReadUInt();
+ }
+
+ SkipBytes(ReadUInt()); // skip import uri.
+
+ intptr_t start_offset = ReaderOffset();
+
+ // Read past "constant coverage constructors".
+ const intptr_t constant_coverage_constructors = ReadUInt();
+ for (intptr_t i = 0; i < constant_coverage_constructors; ++i) {
+ ReadUInt();
+ }
+
+ intptr_t end_offset = ReaderOffset();
+
+ return reader_.ExternalDataFromTo(start_offset, end_offset);
+}
+
intptr_t ActiveClass::MemberTypeParameterCount(Zone* zone) {
ASSERT(member != NULL);
if (member->IsFactory()) {
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index 0bb8141..b60884c2 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -1257,6 +1257,7 @@
const String& GetSourceFor(intptr_t index);
TypedDataPtr GetLineStartsFor(intptr_t index);
String& SourceTableImportUriFor(intptr_t index, uint32_t binaryVersion);
+ ExternalTypedDataPtr GetConstantCoverageFor(intptr_t index);
Zone* zone_;
TranslationHelper& translation_helper_;
@@ -1295,6 +1296,8 @@
friend class ObfuscationProhibitionsMetadataHelper;
friend class LoadingUnitsMetadataHelper;
friend bool NeedsDynamicInvocationForwarder(const Function& function);
+ friend ArrayPtr CollectConstConstructorCoverageFrom(
+ const Script& interesting_script);
private:
DISALLOW_COPY_AND_ASSIGN(KernelReaderHelper);
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index 2c54ec6..3e142f4 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -211,17 +211,15 @@
DEFINE_RUNTIME_ENTRY(CompileFunction, 1) {
ASSERT(thread->IsMutatorThread());
const Function& function = Function::CheckedHandle(zone, arguments.ArgAt(0));
- Object& result = Object::Handle(zone);
- ASSERT(!function.HasCode());
- result = Compiler::CompileFunction(thread, function);
- if (result.IsError()) {
- if (result.IsLanguageError()) {
- Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
- UNREACHABLE();
- }
- Exceptions::PropagateError(Error::Cast(result));
- }
+ // In single-isolate scenarios the lazy compile stub is only invoked if
+ // there's no existing code. In multi-isolate scenarios with shared JITed code
+ // we can end up in the lazy compile runtime entry here with code being
+ // installed.
+ ASSERT(!function.HasCode() || FLAG_enable_isolate_groups);
+
+ // Will throw if compilation failed (e.g. with compile-time error).
+ function.EnsureHasCode();
}
bool Compiler::CanOptimizeFunction(Thread* thread, const Function& function) {
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index b2ff3da..63c13d4 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -503,7 +503,7 @@
static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
-static constexpr dart::compiler::target::word Script_InstanceSize = 56;
+static constexpr dart::compiler::target::word Script_InstanceSize = 64;
static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word SignatureData_InstanceSize = 12;
static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -1026,7 +1026,7 @@
static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 104;
static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -1540,7 +1540,7 @@
static constexpr dart::compiler::target::word Pointer_InstanceSize = 12;
static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 20;
static constexpr dart::compiler::target::word RegExp_InstanceSize = 60;
-static constexpr dart::compiler::target::word Script_InstanceSize = 56;
+static constexpr dart::compiler::target::word Script_InstanceSize = 64;
static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word SignatureData_InstanceSize = 12;
static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -2064,7 +2064,7 @@
static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 40;
static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 104;
static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -3094,7 +3094,7 @@
static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 88;
static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -4120,7 +4120,7 @@
static constexpr dart::compiler::target::word Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word ReceivePort_InstanceSize = 24;
static constexpr dart::compiler::target::word RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word Script_InstanceSize = 88;
static constexpr dart::compiler::target::word SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word SignatureData_InstanceSize = 24;
static constexpr dart::compiler::target::word SingleTargetCache_InstanceSize =
@@ -5266,7 +5266,7 @@
static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
24;
@@ -5847,7 +5847,7 @@
static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 40;
static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
24;
@@ -6985,7 +6985,7 @@
static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
24;
@@ -7559,7 +7559,7 @@
static constexpr dart::compiler::target::word AOT_Pointer_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_ReceivePort_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_RegExp_InstanceSize = 120;
-static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 96;
+static constexpr dart::compiler::target::word AOT_Script_InstanceSize = 88;
static constexpr dart::compiler::target::word AOT_SendPort_InstanceSize = 24;
static constexpr dart::compiler::target::word AOT_SignatureData_InstanceSize =
24;
diff --git a/runtime/vm/kernel.cc b/runtime/vm/kernel.cc
index 952e41a..ed49742 100644
--- a/runtime/vm/kernel.cc
+++ b/runtime/vm/kernel.cc
@@ -347,6 +347,36 @@
script.set_debug_positions(array_object);
}
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ArrayPtr CollectConstConstructorCoverageFrom(const Script& interesting_script) {
+ Thread* thread = Thread::Current();
+ Zone* zone = thread->zone();
+ interesting_script.LookupSourceAndLineStarts(zone);
+ TranslationHelper helper(thread);
+ helper.InitFromScript(interesting_script);
+
+ ExternalTypedData& data =
+ ExternalTypedData::Handle(zone, interesting_script.constant_coverage());
+
+ KernelReaderHelper kernel_reader(zone, &helper, interesting_script, data, 0);
+
+ // Read "constant coverage constructors".
+ const intptr_t constant_coverage_constructors = kernel_reader.ReadUInt();
+ const Array& constructors =
+ Array::Handle(Array::New(constant_coverage_constructors));
+ for (intptr_t i = 0; i < constant_coverage_constructors; ++i) {
+ NameIndex kernel_name = kernel_reader.ReadCanonicalNameReference();
+ Class& klass = Class::ZoneHandle(
+ zone,
+ helper.LookupClassByKernelClass(helper.EnclosingName(kernel_name)));
+ const Function& target = Function::ZoneHandle(
+ zone, helper.LookupConstructorByKernelConstructor(klass, kernel_name));
+ constructors.SetAt(i, target);
+ }
+ return constructors.raw();
+}
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
ObjectPtr EvaluateStaticConstFieldInitializer(const Field& field) {
ASSERT(field.is_static() && field.is_const());
diff --git a/runtime/vm/kernel.h b/runtime/vm/kernel.h
index ed66e25..79c5670 100644
--- a/runtime/vm/kernel.h
+++ b/runtime/vm/kernel.h
@@ -195,6 +195,10 @@
void CollectTokenPositionsFor(const Script& script);
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ArrayPtr CollectConstConstructorCoverageFrom(const Script& interesting_script);
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
ObjectPtr EvaluateStaticConstFieldInitializer(const Field& field);
ObjectPtr EvaluateMetadata(const Library& library,
intptr_t kernel_offset,
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index 4dbcfb6..878582b 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -2093,6 +2093,10 @@
const String& uri_string = helper_.SourceTableUriFor(index);
const String& import_uri_string =
helper_.SourceTableImportUriFor(index, program_->binary_version());
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ ExternalTypedData& constant_coverage =
+ ExternalTypedData::Handle(Z, helper_.GetConstantCoverageFor(index));
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
String& sources = String::Handle(Z);
TypedData& line_starts = TypedData::Handle(Z);
@@ -2138,6 +2142,9 @@
script.set_kernel_script_index(index);
script.set_kernel_program_info(kernel_program_info_);
script.set_line_starts(line_starts);
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ script.set_constant_coverage(constant_coverage);
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
script.set_debug_positions(Array::null_array());
return script.raw();
}
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 927cfef..1347673 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -11161,6 +11161,16 @@
raw_ptr()->set_line_starts(value.raw());
}
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+void Script::set_constant_coverage(const ExternalTypedData& value) const {
+ raw_ptr()->set_constant_coverage(value.raw());
+}
+
+ExternalTypedDataPtr Script::constant_coverage() const {
+ return raw_ptr()->constant_coverage();
+}
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
void Script::set_debug_positions(const Array& value) const {
raw_ptr()->set_debug_positions(value.raw());
}
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 8ea8ae0..960f3f1 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -4522,6 +4522,12 @@
TypedDataPtr line_starts() const;
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ ExternalTypedDataPtr constant_coverage() const;
+
+ void set_constant_coverage(const ExternalTypedData& value) const;
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+
void set_line_starts(const TypedData& value) const;
void set_debug_positions(const Array& value) const;
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index b6e04fc..15af026 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -1315,7 +1315,7 @@
friend class StoreInstanceFieldInstr; // For sizeof(guarded_cid_/...)
};
-class ScriptLayout : public ObjectLayout {
+class alignas(8) ScriptLayout : public ObjectLayout {
public:
enum {
kLazyLookupSourceAndLineStartsPos = 0,
@@ -1330,6 +1330,9 @@
POINTER_FIELD(StringPtr, resolved_url)
POINTER_FIELD(ArrayPtr, compile_time_constants)
POINTER_FIELD(TypedDataPtr, line_starts)
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
+ POINTER_FIELD(ExternalTypedDataPtr, constant_coverage)
+#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
POINTER_FIELD(ArrayPtr, debug_positions)
POINTER_FIELD(KernelProgramInfoPtr, kernel_program_info)
POINTER_FIELD(StringPtr, source)
@@ -1361,8 +1364,13 @@
kLazyLookupSourceAndLineStartsSize>;
uint8_t flags_;
- intptr_t kernel_script_index_;
+#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
int64_t load_timestamp_;
+ int32_t kernel_script_index_;
+#else
+ int32_t kernel_script_index_;
+ int64_t load_timestamp_;
+#endif
};
class LibraryLayout : public ObjectLayout {
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 0a13543..23f05e8 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -1153,9 +1153,15 @@
return result.raw();
}
-static void TrySwitchInstanceCall(const ICData& ic_data,
- const Function& target_function) {
#if !defined(DART_PRECOMPILED_RUNTIME)
+static void TrySwitchInstanceCall(Thread* thread,
+ StackFrame* caller_frame,
+ const Code& caller_code,
+ const Function& caller_function,
+ const ICData& ic_data,
+ const Function& target_function) {
+ auto zone = thread->zone();
+
// Monomorphic/megamorphic calls only check the receiver CID.
if (ic_data.NumArgsTested() != 1) return;
@@ -1169,24 +1175,14 @@
if (Isolate::Current()->has_attempted_stepping()) return;
#endif
- Thread* thread = Thread::Current();
- DartFrameIterator iterator(thread,
- StackFrameIterator::kNoCrossThreadIteration);
- StackFrame* caller_frame = iterator.NextFrame();
- ASSERT(caller_frame->IsDartFrame());
-
// Monomorphic/megamorphic calls are only for unoptimized code.
- Zone* zone = thread->zone();
- const Code& caller_code = Code::Handle(zone, caller_frame->LookupDartCode());
- if (caller_code.is_optimized()) return;
+ ASSERT(!caller_code.is_optimized());
// Code is detached from its function. This will prevent us from resetting
// the switchable call later because resets are function based and because
// the ic_data_array belongs to the function instead of the code. This should
// only happen because of reload, but it sometimes happens with KBC mixed mode
// probably through a race between foreground and background compilation.
- const Function& caller_function =
- Function::Handle(zone, caller_code.function());
if (caller_function.unoptimized_code() != caller_code.raw()) {
return;
}
@@ -1237,9 +1233,8 @@
}
return; // Success.
}
-
-#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
+#endif // !defined(DART_PRECOMPILED_RUNTIME)
// Perform the subtype and return constant function based on the result.
static FunctionPtr ComputeTypeCheckTarget(const Instance& receiver,
@@ -1255,151 +1250,38 @@
return target.raw();
}
-static FunctionPtr InlineCacheMissHandlerGivenTargetFunction(
- const GrowableArray<const Instance*>& args, // Checked arguments only.
- const ICData& ic_data,
- intptr_t count,
- const Function& target_function) {
- if (target_function.IsNull()) {
- return target_function.raw();
- }
+static FunctionPtr Resolve(
+ Thread* thread,
+ Zone* zone,
+ const GrowableArray<const Instance*>& caller_arguments,
+ const Class& receiver_class,
+ const String& name,
+ const Array& descriptor) {
+ ASSERT(name.IsSymbol());
+ auto& target_function = Function::Handle(zone);
+ ArgumentsDescriptor args_desc(descriptor);
- const Instance& receiver = *args[0];
-
- if (args.length() == 1) {
- auto exactness = StaticTypeExactnessState::NotTracking();
- if (ic_data.is_tracking_exactness()) {
-#if !defined(DART_PRECOMPILED_RUNTIME)
- exactness = receiver.IsNull() ? StaticTypeExactnessState::NotExact()
- : StaticTypeExactnessState::Compute(
- Type::Cast(AbstractType::Handle(
- ic_data.receivers_static_type())),
- receiver);
-#else
- UNREACHABLE();
-#endif
- }
- ic_data.EnsureHasReceiverCheck(receiver.GetClassId(), target_function,
- count, exactness);
- } else {
- GrowableArray<intptr_t> class_ids(args.length());
- ASSERT(ic_data.NumArgsTested() == args.length());
- for (intptr_t i = 0; i < args.length(); i++) {
- class_ids.Add(args[i]->GetClassId());
- }
- ic_data.EnsureHasCheck(class_ids, target_function, count);
- }
- if (FLAG_trace_ic_miss_in_optimized || FLAG_trace_ic) {
- DartFrameIterator iterator(Thread::Current(),
- StackFrameIterator::kNoCrossThreadIteration);
- StackFrame* caller_frame = iterator.NextFrame();
- ASSERT(caller_frame != NULL);
- if (FLAG_trace_ic_miss_in_optimized) {
- const Code& caller = Code::Handle(Code::LookupCode(caller_frame->pc()));
- if (caller.is_optimized()) {
- OS::PrintErr("IC miss in optimized code; call %s -> %s\n",
- Function::Handle(caller.function()).ToCString(),
- target_function.ToCString());
- }
- }
- if (FLAG_trace_ic) {
- OS::PrintErr("InlineCacheMissHandler %" Pd " call at %#" Px
- "' "
- "adding <%s> id:%" Pd " -> <%s>\n",
- args.length(), caller_frame->pc(),
- Class::Handle(receiver.clazz()).ToCString(),
- receiver.GetClassId(), target_function.ToCString());
- }
- }
-
- TrySwitchInstanceCall(ic_data, target_function);
-
- return target_function.raw();
-}
-
-static FunctionPtr InlineCacheMissHandler(
- const GrowableArray<const Instance*>& args, // Checked arguments only.
- const ICData& ic_data) {
- Thread* thread = Thread::Current();
- Zone* zone = thread->zone();
-
- const Instance& receiver = *args[0];
- ArgumentsDescriptor arguments_descriptor(
- Array::Handle(zone, ic_data.arguments_descriptor()));
- String& function_name = String::Handle(zone, ic_data.target_name());
- ASSERT(function_name.IsSymbol());
-
- const Class& receiver_class = Class::Handle(zone, receiver.clazz());
- Function& target_function = Function::Handle(zone);
if (receiver_class.EnsureIsFinalized(thread) == Error::null()) {
- target_function = Resolver::ResolveDynamicForReceiverClass(
- receiver_class, function_name, arguments_descriptor);
+ target_function = Resolver::ResolveDynamicForReceiverClass(receiver_class,
+ name, args_desc);
+ }
+ if (caller_arguments.length() == 2 &&
+ target_function.raw() ==
+ thread->isolate()->object_store()->simple_instance_of_function()) {
+ // Replace the target function with constant function.
+ const AbstractType& type = AbstractType::Cast(*caller_arguments[1]);
+ target_function =
+ ComputeTypeCheckTarget(*caller_arguments[0], type, args_desc);
}
- ObjectStore* store = thread->isolate()->object_store();
- if (target_function.raw() == store->simple_instance_of_function()) {
- // Replace the target function with constant function.
- ASSERT(args.length() == 2);
- const AbstractType& type = AbstractType::Cast(*args[1]);
- target_function =
- ComputeTypeCheckTarget(receiver, type, arguments_descriptor);
- }
if (target_function.IsNull()) {
- if (FLAG_trace_ic) {
- OS::PrintErr("InlineCacheMissHandler NULL function for %s receiver: %s\n",
- String::Handle(zone, ic_data.target_name()).ToCString(),
- receiver.ToCString());
- }
- const Array& args_descriptor =
- Array::Handle(zone, ic_data.arguments_descriptor());
- const String& target_name = String::Handle(zone, ic_data.target_name());
- target_function =
- InlineCacheMissHelper(receiver_class, args_descriptor, target_name);
+ target_function = InlineCacheMissHelper(receiver_class, descriptor, name);
}
if (target_function.IsNull()) {
ASSERT(!FLAG_lazy_dispatchers);
- return target_function.raw();
}
- SafepointMutexLocker ml(thread->isolate_group()->patchable_call_mutex());
-
- return InlineCacheMissHandlerGivenTargetFunction(args, ic_data, 1,
- target_function);
-}
-
-// Handles inline cache misses by updating the IC data array of the call site.
-// Arg0: Receiver object.
-// Arg1: IC data object.
-// Returns: target function with compiled code or null.
-// Modifies the instance call to hold the updated IC data array.
-DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerOneArg, 2) {
- const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
- const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(1));
- RELEASE_ASSERT(!FLAG_precompiled_mode);
- GrowableArray<const Instance*> args(1);
- args.Add(&receiver);
- const Function& result =
- Function::Handle(zone, InlineCacheMissHandler(args, ic_data));
- arguments.SetReturn(result);
-}
-
-// Handles inline cache misses by updating the IC data array of the call site.
-// Arg0: Receiver object.
-// Arg1: Argument after receiver.
-// Arg2: IC data object.
-// Returns: target function with compiled code or null.
-// Modifies the instance call to hold the updated IC data array.
-DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerTwoArgs, 3) {
- const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
- const Instance& other = Instance::CheckedHandle(zone, arguments.ArgAt(1));
- const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(2));
- RELEASE_ASSERT(!FLAG_precompiled_mode);
- GrowableArray<const Instance*> args(2);
- args.Add(&receiver);
- args.Add(&other);
- const Function& result =
- Function::Handle(zone, InlineCacheMissHandler(args, ic_data));
- arguments.SetReturn(result);
+ return target_function.raw();
}
// Handles a static call in unoptimized code that has one argument type not
@@ -1554,26 +1436,39 @@
#endif // defined(DART_PRECOMPILED_RUNTIME)
-// We use two different instance calls in JIT vs AOT but both are patchable. A
-// subset of the patching cases are handled by [PatchableCallHandler].
+enum class MissHandler {
+ kInlineCacheMiss,
+ kSwitchableCallMiss,
+};
+
+// Handles updating of type feedback and possible patching of instance calls.
+//
+// It works in 3 separate steps:
+// - resolve the actual target
+// - update type feedback & (optionally) perform call site transition
+// - return the right values
+//
+// Depending on the JIT/AOT mode we obtain current and patch new (target, data)
+// differently:
//
// - JIT calls must be patched with CodePatcher::PatchInstanceCallAt()
// - AOT calls must be patched with CodePatcher::PatchSwitchableCallAt()
//
-// In the JIT it is possible that a call site will use two different miss
-// handlers:
+// Independent of which miss handler was used or how we will return, we look at
+// current (target, data) and see if we need to transition the call site to a
+// new (target, data). We do this while holding `IG->patchable_call_mutex()`.
//
-// - InlineCacheMissHandlerOneArg (which patches callsite to be monomorphic)
-// - SwitchableCallMiss (which patches callsite to be polymorphic)
-// - InlineCacheMissHandlerOneArg (which patches it to be megamorphic)
-// - SwitchableCallMiss (which updates megamorphic cache)
+// Depending on which miss handler got called we might need to return
+// differently:
//
-// Once we share JITed code across isolates there are more possible sequences
-// which we need to consider.
+// - SwitchableCallMiss will get get (stub, data) return value
+// - InlineCache*Miss will get get function as return value
+//
class PatchableCallHandler {
public:
PatchableCallHandler(Thread* thread,
- const Instance& receiver,
+ const GrowableArray<const Instance*>& caller_arguments,
+ MissHandler miss_handler,
NativeArguments arguments,
StackFrame* caller_frame,
const Code& caller_code,
@@ -1581,20 +1476,26 @@
: isolate_(thread->isolate()),
thread_(thread),
zone_(thread->zone()),
- receiver_(receiver),
+ caller_arguments_(caller_arguments),
+ miss_handler_(miss_handler),
arguments_(arguments),
caller_frame_(caller_frame),
caller_code_(caller_code),
caller_function_(caller_function),
name_(String::Handle()),
- args_descriptor_(Array::Handle()) {}
+ args_descriptor_(Array::Handle()) {
+ // We only have two arg IC calls in JIT mode.
+ ASSERT(caller_arguments_.length() == 1 || !FLAG_precompiled_mode);
+ }
+ void ResolveSwitchAndReturn(const Object& data);
+
+ private:
FunctionPtr ResolveTargetFunction(const Object& data);
void HandleMiss(const Object& old_data,
const Code& old_target,
const Function& target_function);
- private:
#if defined(DART_PRECOMPILED_RUNTIME)
void DoUnlinkedCallAOT(const UnlinkedCall& unlinked,
const Function& target_function);
@@ -1611,17 +1512,48 @@
#else
void DoMonomorphicMissJIT(const Object& data,
const Function& target_function);
+ void DoICDataMissJIT(const ICData& data,
+ const Object& old_data,
+ const Function& target_function);
#endif // !defined(DART_PRECOMPILED_RUNTIME)
void DoMegamorphicMiss(const MegamorphicCache& data,
const Function& target_function);
+ void UpdateICDataWithTarget(const ICData& ic_data,
+ const Function& target_function);
+ void TrySwitch(const ICData& ic_data, const Function& target_function);
+
+ void ReturnAOT(const Code& stub, const Object& data);
+ void ReturnJIT(const Code& stub, const Object& data, const Function& target);
+ void ReturnJITorAOT(const Code& stub,
+ const Object& data,
+ const Function& target);
+
+ const Instance& receiver() { return *caller_arguments_[0]; }
+
+ bool should_consider_patching() {
+ // In AOT we use switchable calls.
+ if (FLAG_precompiled_mode) return true;
+
+ // In JIT instance calls use a different calling sequence in unoptimized vs
+ // optimized code (see [FlowGraphCompiler::EmitInstanceCallJIT] vs
+ // [FlowGraphCompiler::EmitOptimizedInstanceCall]).
+ //
+ // The [CodePatcher::GetInstanceCallAt], [CodePatcher::PatchInstanceCallAt]
+ // only recognize unoptimized call pattern.
+ //
+ // So we will not try to switch optimized instance calls.
+ return !caller_code_.is_optimized();
+ }
+
ICDataPtr NewICData();
ICDataPtr NewICDataWithTarget(intptr_t cid, const Function& target);
Isolate* isolate_;
Thread* thread_;
Zone* zone_;
- const Instance& receiver_;
+ const GrowableArray<const Instance*>& caller_arguments_;
+ MissHandler miss_handler_;
NativeArguments arguments_;
StackFrame* caller_frame_;
const Code& caller_code_;
@@ -1640,7 +1572,7 @@
zone_,
target_function.IsNull()
? NewICData()
- : NewICDataWithTarget(receiver_.GetClassId(), target_function));
+ : NewICDataWithTarget(receiver().GetClassId(), target_function));
Object& object = Object::Handle(zone_, ic_data.raw());
Code& code = Code::Handle(zone_, StubCode::ICCallThroughCode().raw());
@@ -1659,7 +1591,7 @@
const Code& target_code =
Code::Handle(zone_, target_function.CurrentCode());
const Smi& expected_cid =
- Smi::Handle(zone_, Smi::New(receiver_.GetClassId()));
+ Smi::Handle(zone_, Smi::New(receiver().GetClassId()));
if (unlinked.can_patch_to_monomorphic()) {
object = expected_cid.raw();
@@ -1675,8 +1607,7 @@
// Return the ICData. The miss stub will jump to continue in the IC lookup
// stub.
- arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
- arguments_.SetReturn(ic_data);
+ ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
}
bool PatchableCallHandler::CanExtendSingleTargetRange(
@@ -1689,14 +1620,14 @@
return false;
}
intptr_t unchecked_lower, unchecked_upper;
- if (receiver_.GetClassId() < *lower) {
- unchecked_lower = receiver_.GetClassId();
+ if (receiver().GetClassId() < *lower) {
+ unchecked_lower = receiver().GetClassId();
unchecked_upper = *lower - 1;
- *lower = receiver_.GetClassId();
+ *lower = receiver().GetClassId();
} else {
- unchecked_upper = receiver_.GetClassId();
+ unchecked_upper = receiver().GetClassId();
unchecked_lower = *upper + 1;
- *upper = receiver_.GetClassId();
+ *upper = receiver().GetClassId();
}
return IsSingleTarget(isolate_, zone_, unchecked_lower, unchecked_upper,
@@ -1724,30 +1655,6 @@
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
-static FunctionPtr Resolve(Thread* thread,
- Zone* zone,
- const Class& receiver_class,
- const String& name,
- const Array& descriptor) {
- ASSERT(name.IsSymbol());
- Function& target_function = Function::Handle(zone);
-
- if (receiver_class.EnsureIsFinalized(thread) == Error::null()) {
- ArgumentsDescriptor args_desc(descriptor);
- target_function = Resolver::ResolveDynamicForReceiverClass(receiver_class,
- name, args_desc);
- }
-
- if (target_function.IsNull()) {
- target_function = InlineCacheMissHelper(receiver_class, descriptor, name);
- if (target_function.IsNull()) {
- ASSERT(!FLAG_lazy_dispatchers);
- }
- }
-
- return target_function.raw();
-}
-
#if defined(DART_PRECOMPILED_RUNTIME)
void PatchableCallHandler::DoMonomorphicMissAOT(
const Object& data,
@@ -1759,12 +1666,12 @@
RELEASE_ASSERT(data.IsMonomorphicSmiableCall());
old_expected_cid = MonomorphicSmiableCall::Cast(data).expected_cid();
}
- const bool is_monomorphic_hit = old_expected_cid == receiver_.GetClassId();
+ const bool is_monomorphic_hit = old_expected_cid == receiver().GetClassId();
const auto& old_receiver_class =
Class::Handle(zone_, isolate_->class_table()->At(old_expected_cid));
const auto& old_target = Function::Handle(
- zone_,
- Resolve(thread_, zone_, old_receiver_class, name_, args_descriptor_));
+ zone_, Resolve(thread_, zone_, caller_arguments_, old_receiver_class,
+ name_, args_descriptor_));
const auto& ic_data = ICData::Handle(
zone_, old_target.IsNull()
@@ -1774,8 +1681,7 @@
if (is_monomorphic_hit) {
// The site just have been updated to monomorphic state with same
// exact class id - do nothing in that case: stub will call through ic data.
- arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
- arguments_.SetReturn(ic_data);
+ ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
return;
}
@@ -1795,8 +1701,7 @@
stub);
// Return the ICData. The miss stub will jump to continue in the IC call
// stub.
- arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
- arguments_.SetReturn(ic_data);
+ ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
return;
}
@@ -1807,8 +1712,7 @@
// Return the ICData. The miss stub will jump to continue in the IC lookup
// stub.
- arguments_.SetArgAt(0, stub);
- arguments_.SetReturn(ic_data);
+ ReturnAOT(stub, ic_data);
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
@@ -1833,15 +1737,12 @@
caller_frame_->pc(), ic_data.ToCString());
}
- // Don't count during insertion because the IC stub we continue through will
- // do an increment.
- GrowableArray<const Instance*> args(1);
- args.Add(&receiver_);
- InlineCacheMissHandlerGivenTargetFunction(args, ic_data, /*count=*/0,
- target_function);
-
- arguments_.SetArgAt(0, stub);
- arguments_.SetReturn(ic_data);
+ ASSERT(caller_arguments_.length() == 1);
+ UpdateICDataWithTarget(ic_data, target_function);
+ ASSERT(should_consider_patching());
+ TrySwitchInstanceCall(thread_, caller_frame_, caller_code_, caller_function_,
+ ic_data, target_function);
+ ReturnJIT(stub, ic_data, target_function);
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
@@ -1858,7 +1759,7 @@
zone_,
target_function.IsNull()
? NewICData()
- : NewICDataWithTarget(receiver_.GetClassId(), target_function));
+ : NewICDataWithTarget(receiver().GetClassId(), target_function));
intptr_t lower = data.lower_limit();
intptr_t upper = data.upper_limit();
@@ -1868,8 +1769,7 @@
data.set_upper_limit(upper);
// Return the ICData. The single target stub will jump to continue in the
// IC call stub.
- arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
- arguments_.SetReturn(ic_data);
+ ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
return;
}
@@ -1880,8 +1780,7 @@
// Return the ICData. The single target stub will jump to continue in the
// IC call stub.
- arguments_.SetArgAt(0, stub);
- arguments_.SetReturn(ic_data);
+ ReturnAOT(stub, ic_data);
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
@@ -1889,7 +1788,7 @@
void PatchableCallHandler::DoICDataMissAOT(const ICData& ic_data,
const Function& target_function) {
const String& name = String::Handle(zone_, ic_data.target_name());
- const Class& cls = Class::Handle(zone_, receiver_.clazz());
+ const Class& cls = Class::Handle(zone_, receiver().clazz());
ASSERT(!cls.IsNull());
const Array& descriptor =
Array::CheckedHandle(zone_, ic_data.arguments_descriptor());
@@ -1900,8 +1799,7 @@
}
if (target_function.IsNull()) {
- arguments_.SetArgAt(0, StubCode::NoSuchMethodDispatcher());
- arguments_.SetReturn(ic_data);
+ ReturnAOT(StubCode::NoSuchMethodDispatcher(), ic_data);
return;
}
@@ -1919,14 +1817,13 @@
const Code& target_code =
Code::Handle(zone_, target_function.EnsureHasCode());
const Smi& expected_cid =
- Smi::Handle(zone_, Smi::New(receiver_.GetClassId()));
+ Smi::Handle(zone_, Smi::New(receiver().GetClassId()));
ASSERT(target_code.HasMonomorphicEntry());
CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_,
expected_cid, target_code);
- arguments_.SetArgAt(0, target_code);
- arguments_.SetReturn(expected_cid);
+ ReturnAOT(target_code, expected_cid);
} else {
- ic_data.EnsureHasReceiverCheck(receiver_.GetClassId(), target_function);
+ ic_data.EnsureHasReceiverCheck(receiver().GetClassId(), target_function);
if (number_of_checks > FLAG_max_polymorphic_checks) {
// Switch to megamorphic call.
const MegamorphicCache& cache = MegamorphicCache::Handle(
@@ -1935,20 +1832,60 @@
CodePatcher::PatchSwitchableCallAt(caller_frame_->pc(), caller_code_,
cache, stub);
- arguments_.SetArgAt(0, stub);
- arguments_.SetReturn(cache);
+ ReturnAOT(stub, cache);
} else {
- arguments_.SetArgAt(0, StubCode::ICCallThroughCode());
- arguments_.SetReturn(ic_data);
+ ReturnAOT(StubCode::ICCallThroughCode(), ic_data);
}
}
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME)
+void PatchableCallHandler::DoICDataMissJIT(const ICData& ic_data,
+ const Object& old_code,
+ const Function& target_function) {
+ ASSERT(ic_data.NumArgsTested() == caller_arguments_.length());
+
+ if (ic_data.NumArgsTested() == 1) {
+ ASSERT(old_code.raw() == StubCode::OneArgCheckInlineCache().raw() ||
+ old_code.raw() ==
+ StubCode::OneArgCheckInlineCacheWithExactnessCheck().raw() ||
+ old_code.raw() ==
+ StubCode::OneArgOptimizedCheckInlineCache().raw() ||
+ old_code.raw() ==
+ StubCode::OneArgOptimizedCheckInlineCacheWithExactnessCheck()
+ .raw() ||
+ old_code.raw() == StubCode::ICCallBreakpoint().raw() ||
+ (old_code.IsNull() && !should_consider_patching()));
+ UpdateICDataWithTarget(ic_data, target_function);
+ if (should_consider_patching()) {
+ TrySwitchInstanceCall(thread_, caller_frame_, caller_code_,
+ caller_function_, ic_data, target_function);
+ }
+ const Code& stub = Code::Handle(
+ zone_, ic_data.is_tracking_exactness()
+ ? StubCode::OneArgCheckInlineCacheWithExactnessCheck().raw()
+ : StubCode::OneArgCheckInlineCache().raw());
+ ReturnJIT(stub, ic_data, target_function);
+ } else {
+ ASSERT(old_code.raw() == StubCode::TwoArgsCheckInlineCache().raw() ||
+ old_code.raw() == StubCode::SmiAddInlineCache().raw() ||
+ old_code.raw() == StubCode::SmiLessInlineCache().raw() ||
+ old_code.raw() == StubCode::SmiEqualInlineCache().raw() ||
+ old_code.raw() ==
+ StubCode::TwoArgsOptimizedCheckInlineCache().raw() ||
+ old_code.raw() == StubCode::ICCallBreakpoint().raw() ||
+ (old_code.IsNull() && !should_consider_patching()));
+ UpdateICDataWithTarget(ic_data, target_function);
+ ReturnJIT(StubCode::TwoArgsCheckInlineCache(), ic_data, target_function);
+ }
+}
+#endif // !defined(DART_PRECOMPILED_RUNTIME)
+
void PatchableCallHandler::DoMegamorphicMiss(const MegamorphicCache& data,
const Function& target_function) {
const String& name = String::Handle(zone_, data.target_name());
- const Class& cls = Class::Handle(zone_, receiver_.clazz());
+ const Class& cls = Class::Handle(zone_, receiver().clazz());
ASSERT(!cls.IsNull());
const Array& descriptor =
Array::CheckedHandle(zone_, data.arguments_descriptor());
@@ -1958,18 +1895,83 @@
cls.ToCString(), args_desc.TypeArgsLen(), name.ToCString());
}
if (target_function.IsNull()) {
- arguments_.SetArgAt(0, StubCode::NoSuchMethodDispatcher());
- arguments_.SetReturn(data);
+ ReturnJITorAOT(StubCode::NoSuchMethodDispatcher(), data, target_function);
return;
}
// Insert function found into cache.
const Smi& class_id = Smi::Handle(zone_, Smi::New(cls.id()));
data.EnsureContains(class_id, target_function);
- arguments_.SetArgAt(0, StubCode::MegamorphicCall());
+ ReturnJITorAOT(StubCode::MegamorphicCall(), data, target_function);
+}
+
+void PatchableCallHandler::UpdateICDataWithTarget(
+ const ICData& ic_data,
+ const Function& target_function) {
+ if (target_function.IsNull()) return;
+
+ // If, upon return of the runtime, we will invoke the target directly we have
+ // to increment the call count here in the ICData.
+ // If we instead only insert a new ICData entry and will return to the IC stub
+ // which will call the target, the stub will take care of the increment.
+ const bool call_target_directly =
+ miss_handler_ == MissHandler::kInlineCacheMiss;
+ const intptr_t invocation_count = call_target_directly ? 1 : 0;
+
+ if (caller_arguments_.length() == 1) {
+ auto exactness = StaticTypeExactnessState::NotTracking();
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ if (ic_data.is_tracking_exactness()) {
+ exactness = receiver().IsNull()
+ ? StaticTypeExactnessState::NotExact()
+ : StaticTypeExactnessState::Compute(
+ Type::Cast(AbstractType::Handle(
+ ic_data.receivers_static_type())),
+ receiver());
+ }
+#endif // !defined(DART_PRECOMPILED_RUNTIME)
+ ic_data.EnsureHasReceiverCheck(receiver().GetClassId(), target_function,
+ invocation_count, exactness);
+ } else {
+ GrowableArray<intptr_t> class_ids(caller_arguments_.length());
+ ASSERT(ic_data.NumArgsTested() == caller_arguments_.length());
+ for (intptr_t i = 0; i < caller_arguments_.length(); i++) {
+ class_ids.Add(caller_arguments_[i]->GetClassId());
+ }
+ ic_data.EnsureHasCheck(class_ids, target_function, invocation_count);
+ }
+}
+
+void PatchableCallHandler::ReturnAOT(const Code& stub, const Object& data) {
+ ASSERT(miss_handler_ == MissHandler::kSwitchableCallMiss);
+ arguments_.SetArgAt(0, stub); // Second return value.
arguments_.SetReturn(data);
}
+void PatchableCallHandler::ReturnJIT(const Code& stub,
+ const Object& data,
+ const Function& target) {
+ // In JIT we can have two different miss handlers to which we return slightly
+ // differently.
+ if (miss_handler_ == MissHandler::kSwitchableCallMiss) {
+ arguments_.SetArgAt(0, stub); // Second return value.
+ arguments_.SetReturn(data);
+ } else {
+ ASSERT(miss_handler_ == MissHandler::kInlineCacheMiss);
+ arguments_.SetReturn(target);
+ }
+}
+
+void PatchableCallHandler::ReturnJITorAOT(const Code& stub,
+ const Object& data,
+ const Function& target) {
+#if defined(DART_PRECOMPILED_MODE)
+ ReturnAOT(stub, data);
+#else
+ ReturnJIT(stub, data, target);
+#endif
+}
+
ICDataPtr PatchableCallHandler::NewICData() {
return ICData::New(caller_function_, name_, args_descriptor_, DeoptId::kNone,
/*num_args_tested=*/1, ICData::kInstance);
@@ -2045,8 +2047,43 @@
default:
UNREACHABLE();
}
- const Class& cls = Class::Handle(zone_, receiver_.clazz());
- return Resolve(thread_, zone_, cls, name_, args_descriptor_);
+ const Class& cls = Class::Handle(zone_, receiver().clazz());
+ return Resolve(thread_, zone_, caller_arguments_, cls, name_,
+ args_descriptor_);
+}
+
+void PatchableCallHandler::ResolveSwitchAndReturn(const Object& old_data) {
+ // Find out actual target (which can be time consuminmg) without holding any
+ // locks.
+ const auto& target_function =
+ Function::Handle(zone_, ResolveTargetFunction(old_data));
+
+ auto& code = Code::Handle(zone_);
+ auto& data = Object::Handle(zone_);
+
+ // We ensure any transition in a patchable calls are done in an atomic
+ // manner, we ensure we always transition forward (e.g. Monomorphic ->
+ // Polymorphic).
+ //
+ // Mutators are only stopped if we actually need to patch a patchable call.
+ // We may not do that if we e.g. just add one more check to an ICData.
+ SafepointMutexLocker ml(thread_->isolate_group()->patchable_call_mutex());
+
+#if defined(DART_PRECOMPILED_RUNTIME)
+ data =
+ CodePatcher::GetSwitchableCallDataAt(caller_frame_->pc(), caller_code_);
+ DEBUG_ONLY(code = CodePatcher::GetSwitchableCallTargetAt(caller_frame_->pc(),
+ caller_code_));
+#else
+ if (should_consider_patching()) {
+ code ^= CodePatcher::GetInstanceCallAt(caller_frame_->pc(), caller_code_,
+ &data);
+ } else {
+ ASSERT(old_data.IsICData() || old_data.IsMegamorphicCache());
+ data = old_data.raw();
+ }
+#endif
+ HandleMiss(data, code, target_function);
}
void PatchableCallHandler::HandleMiss(const Object& old_data,
@@ -2079,13 +2116,12 @@
DoMonomorphicMissJIT(old_data, target_function);
break;
case kICDataCid:
- // Right now ICData misses wil go to the normal ICData miss handler, not
- // to SwitchableCall miss handler.
- UNREACHABLE();
+ DoICDataMissJIT(ICData::Cast(old_data), old_code, target_function);
break;
#endif // defined(DART_PRECOMPILED_RUNTIME)
case kMegamorphicCacheCid:
- ASSERT(old_code.raw() == StubCode::MegamorphicCall().raw());
+ ASSERT(old_code.raw() == StubCode::MegamorphicCall().raw() ||
+ (old_code.IsNull() && !should_consider_patching()));
DoMegamorphicMiss(MegamorphicCache::Cast(old_data), target_function);
break;
default:
@@ -2093,6 +2129,60 @@
}
}
+static void InlineCacheMissHandler(Thread* thread,
+ Zone* zone,
+ const GrowableArray<const Instance*>& args,
+ const ICData& ic_data,
+ NativeArguments native_arguments) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+ DartFrameIterator iterator(thread,
+ StackFrameIterator::kNoCrossThreadIteration);
+ StackFrame* caller_frame = iterator.NextFrame();
+ const auto& caller_code = Code::Handle(zone, caller_frame->LookupDartCode());
+ const auto& caller_function =
+ Function::Handle(zone, caller_frame->LookupDartFunction());
+
+ PatchableCallHandler handler(thread, args, MissHandler::kInlineCacheMiss,
+ native_arguments, caller_frame, caller_code,
+ caller_function);
+
+ handler.ResolveSwitchAndReturn(ic_data);
+#else
+ UNREACHABLE();
+#endif // !defined(DART_PRECOMPILED_RUNTIME)
+}
+
+// Handles inline cache misses by updating the IC data array of the call site.
+// Arg0: Receiver object.
+// Arg1: IC data object.
+// Returns: target function with compiled code or null.
+// Modifies the instance call to hold the updated IC data array.
+DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerOneArg, 2) {
+ const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
+ const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(1));
+ RELEASE_ASSERT(!FLAG_precompiled_mode);
+ GrowableArray<const Instance*> args(1);
+ args.Add(&receiver);
+ InlineCacheMissHandler(thread, zone, args, ic_data, arguments);
+}
+
+// Handles inline cache misses by updating the IC data array of the call site.
+// Arg0: Receiver object.
+// Arg1: Argument after receiver.
+// Arg2: IC data object.
+// Returns: target function with compiled code or null.
+// Modifies the instance call to hold the updated IC data array.
+DEFINE_RUNTIME_ENTRY(InlineCacheMissHandlerTwoArgs, 3) {
+ const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0));
+ const Instance& other = Instance::CheckedHandle(zone, arguments.ArgAt(1));
+ const ICData& ic_data = ICData::CheckedHandle(zone, arguments.ArgAt(2));
+ RELEASE_ASSERT(!FLAG_precompiled_mode);
+ GrowableArray<const Instance*> args(2);
+ args.Add(&receiver);
+ args.Add(&other);
+ InlineCacheMissHandler(thread, zone, args, ic_data, arguments);
+}
+
// Handle the first use of an instance call
// Arg1: Receiver.
// Arg0: Stub out.
@@ -2116,42 +2206,20 @@
const Function& caller_function =
Function::Handle(zone, caller_frame->LookupDartFunction());
- PatchableCallHandler handler(thread, receiver, arguments, caller_frame,
- caller_code, caller_function);
-
- // Find out actual target (which can be time consuminmg) without holding any
- // locks.
- Object& old_data = Object::Handle(zone);
+ auto& old_data = Object::Handle(zone);
#if defined(DART_PRECOMPILED_RUNTIME)
old_data =
CodePatcher::GetSwitchableCallDataAt(caller_frame->pc(), caller_code);
#else
CodePatcher::GetInstanceCallAt(caller_frame->pc(), caller_code, &old_data);
#endif
- const Function& target_function =
- Function::Handle(zone, handler.ResolveTargetFunction(old_data));
- {
- // We ensure any transition in a patchable calls are done in an atomic
- // manner, we ensure we always transition forward (e.g. Monomorphic ->
- // Polymorphic).
- //
- // Mutators are only stopped if we actually need to patch a patchable call.
- // We may not do that if we e.g. just add one more check to an ICData.
- SafepointMutexLocker ml(thread->isolate_group()->patchable_call_mutex());
-
- auto& old_code = Code::Handle(zone);
-#if defined(DART_PRECOMPILED_RUNTIME)
- old_data =
- CodePatcher::GetSwitchableCallDataAt(caller_frame->pc(), caller_code);
- DEBUG_ONLY(old_code = CodePatcher::GetSwitchableCallTargetAt(
- caller_frame->pc(), caller_code));
-#else
- old_code ^= CodePatcher::GetInstanceCallAt(caller_frame->pc(), caller_code,
- &old_data);
-#endif
- handler.HandleMiss(old_data, old_code, target_function);
- }
+ GrowableArray<const Instance*> caller_arguments(1);
+ caller_arguments.Add(&receiver);
+ PatchableCallHandler handler(thread, caller_arguments,
+ MissHandler::kSwitchableCallMiss, arguments,
+ caller_frame, caller_code, caller_function);
+ handler.ResolveSwitchAndReturn(old_data);
}
// Used to find the correct receiver and function to invoke or to fall back to
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index 84f5495..3f074ff 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -565,6 +565,19 @@
// Visit all closures for this isolate.
VisitClosures(&ranges);
+
+ // Output constant coverage if coverage is requested.
+ if (IsReportRequested(kCoverage)) {
+ // Find all scripts. We need to go though all scripts because a script
+ // (even one we don't want) can add coverage to another library (i.e.
+ // potentially one we want).
+ DirectChainedHashMap<ScriptTableTrait> local_script_table;
+ GrowableArray<ScriptTableEntry*> local_script_table_entries;
+ CollectAllScripts(&local_script_table, &local_script_table_entries);
+ CollectConstConstructorCoverageFromScripts(&local_script_table_entries,
+ &ranges);
+ CleanupCollectedScripts(&local_script_table, &local_script_table_entries);
+ }
}
// Print the script table.
@@ -572,5 +585,102 @@
PrintScriptTable(&scripts);
}
+void SourceReport::CollectAllScripts(
+ DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+ GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
+ ScriptTableEntry wrapper;
+ const GrowableObjectArray& libs = GrowableObjectArray::Handle(
+ zone(), thread()->isolate()->object_store()->libraries());
+ Library& lib = Library::Handle(zone());
+ Script& scriptRef = Script::Handle(zone());
+ for (int i = 0; i < libs.Length(); i++) {
+ lib ^= libs.At(i);
+ const Array& scripts = Array::Handle(zone(), lib.LoadedScripts());
+ for (intptr_t j = 0; j < scripts.Length(); j++) {
+ scriptRef ^= scripts.At(j);
+ const String& url = String::Handle(zone(), scriptRef.url());
+ wrapper.key = &url;
+ wrapper.script = &Script::Handle(zone(), scriptRef.raw());
+ ScriptTableEntry* pair = local_script_table->LookupValue(&wrapper);
+ if (pair != NULL) {
+ // Existing one.
+ continue;
+ }
+ // New one. Insert.
+ ScriptTableEntry* tmp = new ScriptTableEntry();
+ tmp->key = &url;
+ tmp->index = next_script_index_++;
+ tmp->script = wrapper.script;
+ local_script_table_entries->Add(tmp);
+ local_script_table->Insert(tmp);
+ }
+ }
+}
+
+void SourceReport::CleanupCollectedScripts(
+ DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+ GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
+ for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
+ delete local_script_table_entries->operator[](i);
+ local_script_table_entries->operator[](i) = NULL;
+ }
+ local_script_table_entries->Clear();
+ local_script_table->Clear();
+}
+
+void SourceReport::CollectConstConstructorCoverageFromScripts(
+ GrowableArray<ScriptTableEntry*>* local_script_table_entries,
+ JSONArray* ranges) {
+ // Now output the wanted constant coverage.
+ for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
+ const Script* script = local_script_table_entries->At(i)->script;
+ bool script_ok = true;
+ if (script_ != NULL && !script_->IsNull()) {
+ if (script->raw() != script_->raw()) {
+ // This is the wrong script.
+ script_ok = false;
+ }
+ }
+
+ // Whether we want *this* script or not we need to look at the constant
+ // constructor coverage. Any of those could be in a script we *do* want.
+ {
+ Script& scriptRef = Script::Handle(zone());
+ const Array& constructors =
+ Array::Handle(kernel::CollectConstConstructorCoverageFrom(*script));
+ intptr_t constructors_count = constructors.Length();
+ Function& constructor = Function::Handle(zone());
+ Code& code = Code::Handle(zone());
+ for (intptr_t i = 0; i < constructors_count; i++) {
+ constructor ^= constructors.At(i);
+ // Check if we want coverage for this constructor.
+ if (ShouldSkipFunction(constructor)) {
+ continue;
+ }
+ scriptRef ^= constructor.script();
+ code ^= constructor.unoptimized_code();
+ const TokenPosition begin_pos = constructor.token_pos();
+ const TokenPosition end_pos = constructor.end_token_pos();
+ JSONObject range(ranges);
+ range.AddProperty("scriptIndex", GetScriptIndex(scriptRef));
+ range.AddProperty("compiled",
+ !code.IsNull()); // Does this make a difference?
+ range.AddProperty("startPos", begin_pos);
+ range.AddProperty("endPos", end_pos);
+
+ JSONObject cov(&range, "coverage");
+ {
+ JSONArray hits(&cov, "hits");
+ hits.AddValue(begin_pos);
+ }
+ {
+ JSONArray misses(&cov, "misses");
+ // No misses
+ }
+ }
+ }
+ }
+}
+
} // namespace dart
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/source_report.h b/runtime/vm/source_report.h
index 0a3ddb1..4ec20c9 100644
--- a/runtime/vm/source_report.h
+++ b/runtime/vm/source_report.h
@@ -112,6 +112,18 @@
}
};
+ void CollectAllScripts(
+ DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+ GrowableArray<ScriptTableEntry*>* local_script_table_entries);
+
+ void CleanupCollectedScripts(
+ DirectChainedHashMap<ScriptTableTrait>* local_script_table,
+ GrowableArray<ScriptTableEntry*>* local_script_table_entries);
+
+ void CollectConstConstructorCoverageFromScripts(
+ GrowableArray<ScriptTableEntry*>* local_script_table_entries,
+ JSONArray* ranges);
+
intptr_t report_set_;
CompileMode compile_mode_;
Thread* thread_;
diff --git a/tools/VERSION b/tools/VERSION
index aa43b14..679ae2d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 131
+PRERELEASE 132
PRERELEASE_PATCH 0
\ No newline at end of file