blob: 8d0fcc6c093a1a78c006753e7c6f056fc1cbdf6f [file] [log] [blame]
// 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/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;
}
// 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
// for the other crash instead --- add an option so it does that automatically
// for everything it sees. One can possibly just make a copy of the state of
// the file system and save that for later...
main(List<String> arguments) async {
String filename;
Uri loadFsJson;
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;
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) {
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()}"'),
));
} 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)");
}
}
}
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 {
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: 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 {
// 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;
}
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();
}
}
_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 (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;
}
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));
}
}