blob: 797bdedd22a7fbe4ac74f066ca49d99ebf1437ac [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:convert' show utf8;
import 'dart:io' show BytesBuilder, File;
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 ScannerConfiguration, Token;
import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
import 'package:front_end/src/api_prototype/compiler_options.dart'
show CompilerOptions, DiagnosticMessage;
import 'package:front_end/src/api_prototype/memory_file_system.dart'
show MemoryFileSystem;
import 'package:front_end/src/base/processed_options.dart'
show ProcessedOptions;
import 'package:front_end/src/compute_platform_binaries_location.dart'
show computePlatformBinariesLocation;
import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
import 'package:front_end/src/fasta/incremental_compiler.dart'
show IncrementalCompiler;
import 'package:kernel/ast.dart' show Component;
import 'parser_test_listener.dart' show ParserTestListener;
import 'parser_suite.dart' as parser_suite;
import 'incremental_load_from_dill_suite.dart' show getOptions;
Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
Uri base = Uri.parse("org-dartlang-test:///");
Uri sdkSummary = base.resolve("vm_platform.dill");
Uri platformUri = sdkRoot.resolve("vm_platform_strong.dill");
main(List<String> arguments) async {
String filename;
bool nnbd = false;
for (String arg in arguments) {
if (arg.startsWith("--")) {
if (arg == "--nnbd") {
nnbd = true;
} 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 (filename == null) {
throw "Need file to operate on";
}
File file = new File(filename);
if (!file.existsSync()) throw "File $filename doesn't exist.";
await tryToMinimize(file, nnbd);
}
Future tryToMinimize(File file, bool nnbd) async {
Uint8List data;
try {
String parsedString = getFileAsStringContent(file, nnbd);
data = utf8.encode(parsedString);
} catch (e) {
// If this crash it's a crash in the scanner/parser. It's good to minimize
// that too.
data = file.readAsBytesSync();
}
print("Got data");
Uri main = base.resolve("main.dart");
CompilerContext compilerContext = setupCompilerContext(main);
Component initialComponent = await getInitialComponent(compilerContext);
MemoryFileSystem fs = compilerContext.options.fileSystem;
print("Compiled initially (without data)");
// First assure it actually crash on the input.
if (!await crashesOnCompile(
fs, main, data, compilerContext, initialComponent)) {
throw "Input doesn't crash the compiler: ${dataToText(data)}";
}
print("Step #1: We did crash on the input!");
// Try to delete lines.
Uint8List latestCrashData =
await deleteLines(data, fs, main, compilerContext, initialComponent);
print("We're now at ${latestCrashData.length} bytes.");
// Now try to delete 'arbitrarily' (for any given start offset do an
// exponential binary search).
int prevLength = latestCrashData.length;
while (true) {
latestCrashData = await binarySearchDeleteData(
latestCrashData, fs, main, compilerContext, initialComponent);
if (latestCrashData.length == prevLength) {
// No progress.
break;
} else {
print("We're now at ${latestCrashData.length} bytes");
prevLength = latestCrashData.length;
}
}
print("Got it down to ${latestCrashData.length} bytes: "
"${dataToText(latestCrashData)}");
try {
String utfDecoded = utf8.decode(latestCrashData, allowMalformed: true);
print("That's '$utfDecoded' as text");
} catch (e) {
print("(which crashes when trying to decode as utf8)");
}
}
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();
}
Future<Uint8List> binarySearchDeleteData(
Uint8List latestCrashData,
MemoryFileSystem fs,
Uri main,
CompilerContext compilerContext,
Component initialComponent) async {
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();
if (!await crashesOnCompile(
fs, main, candidate, compilerContext, 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();
if (!await crashesOnCompile(
fs, main, candidate, compilerContext, 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();
if (await crashesOnCompile(
fs, main, candidate, compilerContext, 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();
if (!await crashesOnCompile(
fs, main, candidate, compilerContext, initialComponent)) {
throw "Error in binary search.";
}
latestCrashData = candidate;
}
return latestCrashData;
}
Future<Uint8List> deleteLines(Uint8List data, MemoryFileSystem fs, Uri main,
CompilerContext compilerContext, Component initialComponent) async {
// Try to delete "lines".
const int $LF = 10;
Uint8List latestCrashData = data;
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);
for (int i = 0; i < lines.length; i++) {
include[i] = false;
final BytesBuilder builder = new BytesBuilder();
for (int j = 0; j < lines.length; j++) {
if (include[j]) {
builder.add(lines[j]);
if (j + 1 < lines.length) {
builder.addByte($LF);
}
}
}
Uint8List candidate = builder.takeBytes();
if (!await crashesOnCompile(
fs, main, candidate, compilerContext, initialComponent)) {
// Didn't crash => Can't remove line i.
include[i] = true;
} else {
print("Can delete line $i");
latestCrashData = candidate;
}
}
return latestCrashData;
}
Future<bool> crashesOnCompile(MemoryFileSystem fs, Uri main, Uint8List data,
CompilerContext compilerContext, Component initialComponent) async {
fs.entityForUri(main).writeAsBytesSync(data);
IncrementalCompiler incrementalCompiler =
new IncrementalCompiler.fromComponent(compilerContext, initialComponent);
incrementalCompiler.invalidate(main);
try {
await incrementalCompiler.computeDelta();
return false;
} catch (e) {
return true;
}
}
Future<Component> getInitialComponent(CompilerContext compilerContext) async {
IncrementalCompiler incrementalCompiler =
new IncrementalCompiler(compilerContext);
Component originalComponent = await incrementalCompiler.computeDelta();
return originalComponent;
}
CompilerContext setupCompilerContext(Uri main) {
Uint8List sdkSummaryData = new File.fromUri(platformUri).readAsBytesSync();
MemoryFileSystem fs = new MemoryFileSystem(base);
CompilerOptions options = getOptions();
options.fileSystem = fs;
options.sdkRoot = null;
options.sdkSummary = sdkSummary;
options.omitPlatform = false;
options.onDiagnostic = (DiagnosticMessage message) {
// don't care.
};
fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData);
fs.entityForUri(main).writeAsStringSync("main() {}");
CompilerContext compilerContext = new CompilerContext(
new ProcessedOptions(options: options, inputs: [main]));
return compilerContext;
}
String getFileAsStringContent(File file, bool nnbd) {
Uint8List rawBytes = file.readAsBytesSync();
List<int> lineStarts = new List<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);