ec5e71ead4
* Rename additionalDills to additionalDillModules to indicate that it is used to load a "module", not any old dill file. * Where additionalDills was used to provide the platform use sdkSummary instead. * Remove unused constructor in KernelCompilationResults. Tested: Existing tests. Change-Id: I5f73f61db73b2bc932c211fb0b2e66ec2f5c50c5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495981 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Commit-Queue: Jens Johansen <jensj@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
2788 lines
84 KiB
Dart
2788 lines
84 KiB
Dart
// Copyright (c) 2016, 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 jsonDecode, utf8;
|
|
import 'dart:io' show Directory, File, Platform;
|
|
import 'dart:typed_data' show Uint8List;
|
|
|
|
import 'package:_fe_analyzer_shared/src/parser/experimental_features.dart'
|
|
show ExperimentalFeatures;
|
|
import 'package:_fe_analyzer_shared/src/scanner/token.dart'
|
|
show LanguageVersionToken, Token;
|
|
import 'package:_fe_analyzer_shared/src/util/colors.dart' as colors;
|
|
import 'package:_fe_analyzer_shared/src/util/libraries_specification.dart'
|
|
show LibraryInfo;
|
|
import 'package:compiler/src/kernel/dart2js_target.dart';
|
|
import 'package:compiler/src/options.dart'
|
|
as dart2jsOptions
|
|
show CompilerOptions;
|
|
import 'package:dart2wasm/target.dart';
|
|
import 'package:dev_compiler/src/kernel/target.dart';
|
|
import 'package:front_end/src/api_prototype/compiler_options.dart'
|
|
show CompilerOptions, CfeDiagnosticMessage;
|
|
import 'package:front_end/src/api_prototype/constant_evaluator.dart'
|
|
show ConstantEvaluator, ErrorReporter;
|
|
import 'package:front_end/src/api_prototype/experimental_flags.dart'
|
|
show
|
|
AllowedExperimentalFlags,
|
|
ExperimentalFlag,
|
|
LibraryFeatures,
|
|
LibraryExperimentalFeatures;
|
|
import 'package:front_end/src/api_prototype/file_system.dart'
|
|
show FileSystem, FileSystemEntity, FileSystemException;
|
|
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart'
|
|
show IncrementalCompilerResult;
|
|
import 'package:front_end/src/base/compiler_context.dart' show CompilerContext;
|
|
import 'package:front_end/src/base/crash.dart';
|
|
import 'package:front_end/src/base/incremental_compiler.dart'
|
|
show AdvancedInvalidationResult, IncrementalCompiler;
|
|
import 'package:front_end/src/base/messages.dart' show LocatedMessage;
|
|
import 'package:front_end/src/base/problems.dart';
|
|
import 'package:front_end/src/base/processed_options.dart'
|
|
show ProcessedOptions;
|
|
import 'package:front_end/src/base/uri_translator.dart' show UriTranslator;
|
|
import 'package:front_end/src/builder/library_builder.dart' show LibraryBuilder;
|
|
import 'package:front_end/src/compute_platform_binaries_location.dart'
|
|
show computePlatformBinariesLocation, computePlatformDillName;
|
|
import 'package:front_end/src/kernel/hierarchy/hierarchy_builder.dart'
|
|
show ClassHierarchyBuilder;
|
|
import 'package:front_end/src/kernel/hierarchy/hierarchy_node.dart'
|
|
show ClassHierarchyNode;
|
|
import 'package:front_end/src/kernel/kernel_target.dart' show KernelTarget;
|
|
import 'package:front_end/src/kernel/utils.dart' show ByteSink;
|
|
import 'package:front_end/src/kernel/cfe_verifier.dart' show verifyComponent;
|
|
import 'package:front_end/src/kernel_generator_impl.dart';
|
|
import 'package:front_end/src/util/parser_ast.dart'
|
|
show IgnoreSomeForCompatibilityAstVisitor, getAST;
|
|
import 'package:front_end/src/util/parser_ast_helper.dart';
|
|
import 'package:kernel/ast.dart'
|
|
show
|
|
BasicLiteral,
|
|
Class,
|
|
Component,
|
|
Constant,
|
|
ConstantExpression,
|
|
Expression,
|
|
Extension,
|
|
ExtensionTypeDeclaration,
|
|
FileUriExpression,
|
|
FileUriNode,
|
|
InstanceInvocation,
|
|
InstanceSet,
|
|
InvalidExpression,
|
|
Library,
|
|
LibraryPart,
|
|
Member,
|
|
Node,
|
|
RecursiveVisitor,
|
|
Reference,
|
|
TreeNode,
|
|
Typedef,
|
|
UnevaluatedConstant,
|
|
VariableDeclaration,
|
|
Version;
|
|
import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
|
|
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
|
|
import 'package:kernel/core_types.dart' show CoreTypes;
|
|
import 'package:kernel/kernel.dart'
|
|
show RecursiveResultVisitor, loadComponentFromBytes;
|
|
import 'package:kernel/reference_from_index.dart' show ReferenceFromIndex;
|
|
import 'package:kernel/src/equivalence.dart'
|
|
show
|
|
EquivalenceResult,
|
|
EquivalenceStrategy,
|
|
EquivalenceVisitor,
|
|
ReferenceName,
|
|
checkEquivalence;
|
|
import 'package:kernel/target/changed_structure_notifier.dart'
|
|
show ChangedStructureNotifier;
|
|
import 'package:kernel/target/targets.dart'
|
|
show
|
|
DiagnosticReporter,
|
|
NoneConstantsBackend,
|
|
NoneTarget,
|
|
NumberSemantics,
|
|
Target,
|
|
TestTargetFlags,
|
|
TestTargetMixin,
|
|
TestTargetWrapper;
|
|
import 'package:kernel/type_environment.dart'
|
|
show StaticTypeContext, TypeEnvironment;
|
|
import 'package:kernel/verifier.dart' show VerificationStage;
|
|
import 'package:testing/testing.dart'
|
|
show
|
|
Chain,
|
|
ChainContext,
|
|
Expectation,
|
|
ExpectationSet,
|
|
Result,
|
|
Step,
|
|
TestDescription,
|
|
StdioProcess;
|
|
import 'package:vm/modular/target/vm.dart' show VmTarget;
|
|
|
|
import '../incremental_suite.dart' show TestRecorderForTesting;
|
|
import '../testing_utils.dart' show checkEnvironment;
|
|
import '../utils/kernel_chain.dart'
|
|
show
|
|
ComponentResult,
|
|
ErrorCommentChecker,
|
|
MatchContext,
|
|
MatchExpectation,
|
|
Print,
|
|
TypeCheck,
|
|
WriteDill;
|
|
import 'environment_keys.dart';
|
|
import 'folder_options.dart';
|
|
import 'test_options.dart';
|
|
|
|
export 'package:testing/testing.dart' show Chain, runMe;
|
|
|
|
const String EXPECTATIONS = '''
|
|
[
|
|
{
|
|
"name": "ExpectationFileMismatch",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "ExpectationFileMismatchSerialized",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "ExpectationFileMissing",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "TypeCheckError",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "VerificationError",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "TransformVerificationError",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "TextSerializationFailure",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "SemiFuzzFailure",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "semiFuzzFailureOnForceRebuildBodies",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "SemiFuzzCrash",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "SemiFuzzAssertFailure",
|
|
"group": "Fail"
|
|
},
|
|
{
|
|
"name": "ErrorCommentCheckFailure",
|
|
"group": "Fail"
|
|
}
|
|
]
|
|
''';
|
|
|
|
final Expectation runtimeError =
|
|
ExpectationSet.defaultExpectations["RuntimeError"];
|
|
|
|
const String experimentalFlagOptions = '--enable-experiment=';
|
|
|
|
final ExpectationSet staticExpectationSet = new ExpectationSet.fromJsonList(
|
|
jsonDecode(EXPECTATIONS),
|
|
);
|
|
final Expectation semiFuzzFailure = staticExpectationSet["SemiFuzzFailure"];
|
|
final Expectation semiFuzzFailureOnForceRebuildBodies =
|
|
staticExpectationSet["semiFuzzFailureOnForceRebuildBodies"];
|
|
final Expectation semiFuzzCrash = staticExpectationSet["SemiFuzzCrash"];
|
|
final Expectation semiFuzzAssertFailure =
|
|
staticExpectationSet["SemiFuzzAssertFailure"];
|
|
|
|
class FastaContext extends ChainContext with MatchContext {
|
|
final Uri baseUri;
|
|
@override
|
|
final List<Step> steps;
|
|
final Uri vm;
|
|
final Map<ExperimentalFlag, bool> forcedExperimentalFlags;
|
|
final bool skipVm;
|
|
final bool semiFuzz;
|
|
final bool verify;
|
|
final Uri platformBinaries;
|
|
final Map<Uri, Uri?> _librariesJson = {};
|
|
final SuiteFolderOptions suiteFolderOptions;
|
|
final SuiteTestOptions suiteTestOptions;
|
|
final CompileMode compileMode;
|
|
|
|
@override
|
|
final bool updateExpectations;
|
|
|
|
@override
|
|
String get updateExpectationsOption =>
|
|
'${EnvironmentKeys.updateExpectations}=true';
|
|
|
|
@override
|
|
bool get canBeFixWithUpdateExpectations => true;
|
|
|
|
@override
|
|
final ExpectationSet expectationSet = staticExpectationSet;
|
|
|
|
Map<Uri, Component> _platforms = {};
|
|
|
|
bool? _assertsEnabled;
|
|
bool get assertsEnabled {
|
|
if (_assertsEnabled == null) {
|
|
_assertsEnabled = false;
|
|
assert(() {
|
|
_assertsEnabled = true;
|
|
return true;
|
|
}());
|
|
}
|
|
return _assertsEnabled!;
|
|
}
|
|
|
|
FastaContext(
|
|
this.baseUri,
|
|
this.vm,
|
|
this.platformBinaries,
|
|
this.forcedExperimentalFlags,
|
|
bool ignoreExpectations,
|
|
this.updateExpectations,
|
|
bool updateComments,
|
|
this.skipVm,
|
|
this.semiFuzz,
|
|
this.compileMode,
|
|
this.verify,
|
|
) : steps = <Step>[
|
|
new Outline(compileMode, updateComments: updateComments),
|
|
const Print(),
|
|
new Verify(
|
|
compileMode == CompileMode.full
|
|
? VerificationStage.afterConstantEvaluation
|
|
: VerificationStage.outline,
|
|
),
|
|
],
|
|
suiteFolderOptions = new SuiteFolderOptions(baseUri),
|
|
suiteTestOptions = new SuiteTestOptions() {
|
|
String prefix = '.strong';
|
|
String infix;
|
|
switch (compileMode) {
|
|
case CompileMode.outline:
|
|
infix = '.outline';
|
|
break;
|
|
case CompileMode.modular:
|
|
infix = '.modular';
|
|
break;
|
|
case CompileMode.full:
|
|
infix = '';
|
|
break;
|
|
}
|
|
|
|
if (compileMode == CompileMode.outline) {
|
|
// If doing an outline compile, this is the only expect file so we run the
|
|
// extra constant evaluation now. If we do a full or modular compilation,
|
|
// we'll do if after the transformation. That also ensures we don't get
|
|
// the same 'extra constant evaluation' output twice (in .transformed and
|
|
// not).
|
|
steps.add(const StressConstantEvaluatorStep());
|
|
}
|
|
if (!ignoreExpectations) {
|
|
steps.add(
|
|
new MatchExpectation(
|
|
"$prefix$infix.expect",
|
|
serializeFirst: false,
|
|
isLastMatchStep: false,
|
|
),
|
|
);
|
|
steps.add(
|
|
new MatchExpectation(
|
|
"$prefix$infix.expect",
|
|
serializeFirst: true,
|
|
isLastMatchStep: true,
|
|
),
|
|
);
|
|
}
|
|
steps.add(const TypeCheck());
|
|
steps.add(const EnsureNoErrors());
|
|
switch (compileMode) {
|
|
case CompileMode.full:
|
|
steps.add(const Transform());
|
|
steps.add(const Verify(VerificationStage.afterModularTransformations));
|
|
steps.add(const StressConstantEvaluatorStep());
|
|
if (!ignoreExpectations) {
|
|
steps.add(
|
|
new MatchExpectation(
|
|
"$prefix$infix.transformed.expect",
|
|
serializeFirst: false,
|
|
isLastMatchStep: updateExpectations,
|
|
),
|
|
);
|
|
if (!updateExpectations) {
|
|
steps.add(
|
|
new MatchExpectation(
|
|
"$prefix$infix.transformed.expect",
|
|
serializeFirst: true,
|
|
isLastMatchStep: true,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
steps.add(new ErrorCommentChecker(compileMode));
|
|
steps.add(const EnsureNoErrors());
|
|
steps.add(new WriteDill(skipVm: skipVm));
|
|
if (semiFuzz) {
|
|
steps.add(const FuzzCompiles());
|
|
}
|
|
|
|
// Notice: The below steps will run async, i.e. the next test will run
|
|
// intertwined with this/these step(s). That for instance means that they
|
|
// should not touch any ASTs!
|
|
if (!skipVm) {
|
|
steps.add(const Run());
|
|
}
|
|
break;
|
|
case CompileMode.modular:
|
|
case CompileMode.outline:
|
|
steps.add(new ErrorCommentChecker(compileMode));
|
|
steps.add(new WriteDill(skipVm: true));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Libraries json for [description].
|
|
Uri? computeLibrariesSpecificationUri(TestDescription description) {
|
|
Directory directory = new File.fromUri(description.uri).parent;
|
|
if (_librariesJson.containsKey(directory.uri)) {
|
|
return _librariesJson[directory.uri];
|
|
} else {
|
|
Uri? librariesJson;
|
|
File jsonFile = new File.fromUri(directory.uri.resolve('libraries.json'));
|
|
if (jsonFile.existsSync()) {
|
|
librariesJson = jsonFile.uri;
|
|
}
|
|
return _librariesJson[directory.uri] = librariesJson;
|
|
}
|
|
}
|
|
|
|
/// Custom package config used for [description].
|
|
Uri? computePackageConfigUri(TestDescription description) {
|
|
Uri packageConfig = description.uri.resolve(
|
|
".dart_tool/package_config.json",
|
|
);
|
|
return new File.fromUri(packageConfig).existsSync() ? packageConfig : null;
|
|
}
|
|
|
|
Expectation get verificationError => expectationSet["VerificationError"];
|
|
|
|
Uri _getPlatformUri(Target target) {
|
|
String fileName = computePlatformDillName(
|
|
target,
|
|
() => throw new UnsupportedError(
|
|
"No platform dill for target '${target.name}'.",
|
|
),
|
|
)!;
|
|
return platformBinaries.resolve(fileName);
|
|
}
|
|
|
|
Component loadPlatform(Target target) {
|
|
Uri uri = _getPlatformUri(target);
|
|
return _platforms.putIfAbsent(uri, () {
|
|
return loadComponentFromBytes(new File.fromUri(uri).readAsBytesSync());
|
|
});
|
|
}
|
|
|
|
void clearPlatformCache(Target target) {
|
|
Uri uri = _getPlatformUri(target);
|
|
_platforms.remove(uri);
|
|
}
|
|
|
|
@override
|
|
Set<Expectation> processExpectedOutcomes(
|
|
Set<Expectation> outcomes,
|
|
TestDescription description,
|
|
) {
|
|
// Remove outcomes related to phases not currently in effect.
|
|
|
|
Set<Expectation>? result;
|
|
|
|
// If skipping VM we can't get a runtime error.
|
|
if (skipVm && outcomes.contains(runtimeError)) {
|
|
result ??= new Set.from(outcomes);
|
|
result.remove(runtimeError);
|
|
}
|
|
|
|
// If not semi-fuzzing we can't get semi-fuzz errors.
|
|
if (!semiFuzz &&
|
|
(outcomes.contains(semiFuzzFailure) ||
|
|
outcomes.contains(semiFuzzFailureOnForceRebuildBodies) ||
|
|
outcomes.contains(semiFuzzCrash) ||
|
|
outcomes.contains(semiFuzzAssertFailure))) {
|
|
result ??= new Set.from(outcomes);
|
|
result.remove(semiFuzzFailure);
|
|
result.remove(semiFuzzFailureOnForceRebuildBodies);
|
|
result.remove(semiFuzzCrash);
|
|
result.remove(semiFuzzAssertFailure);
|
|
}
|
|
if (!assertsEnabled && outcomes.contains(semiFuzzAssertFailure)) {
|
|
result ??= new Set.from(outcomes);
|
|
result.remove(semiFuzzAssertFailure);
|
|
}
|
|
|
|
// Fast-path: no changes made.
|
|
if (result == null) return outcomes;
|
|
|
|
// Changes made: No expectations left. This happens when all expected
|
|
// outcomes are removed above.
|
|
// We have to put in the implicit assumption that it will pass then.
|
|
if (result.isEmpty) return {Expectation.pass};
|
|
|
|
// Changes made with at least one expectation left. That's out result!
|
|
return result;
|
|
}
|
|
|
|
static Future<FastaContext> create(
|
|
Chain suite,
|
|
Map<String, String> environment,
|
|
) {
|
|
const Set<String> knownEnvironmentKeys = {
|
|
EnvironmentKeys.ignoreExpectations,
|
|
EnvironmentKeys.updateExpectations,
|
|
EnvironmentKeys.updateComments,
|
|
EnvironmentKeys.skipVm,
|
|
EnvironmentKeys.semiFuzz,
|
|
EnvironmentKeys.verify,
|
|
EnvironmentKeys.platformBinaries,
|
|
EnvironmentKeys.compilationMode,
|
|
};
|
|
checkEnvironment(environment, knownEnvironmentKeys);
|
|
|
|
String resolvedExecutable =
|
|
Platform.environment['resolvedExecutable'] ??
|
|
Platform.resolvedExecutable;
|
|
Uri vm = Uri.base.resolveUri(new Uri.file(resolvedExecutable));
|
|
Map<ExperimentalFlag, bool> experimentalFlags =
|
|
SuiteFolderOptions.computeForcedExperimentalFlags(environment);
|
|
|
|
bool ignoreExpectations =
|
|
environment[EnvironmentKeys.ignoreExpectations] == "true";
|
|
bool updateExpectations =
|
|
environment[EnvironmentKeys.updateExpectations] == "true";
|
|
bool updateComments = environment[EnvironmentKeys.updateComments] == "true";
|
|
bool skipVm = environment[EnvironmentKeys.skipVm] == "true";
|
|
bool semiFuzz = environment[EnvironmentKeys.semiFuzz] == "true";
|
|
bool verify = environment[EnvironmentKeys.verify] != "false";
|
|
String? platformBinaries = environment[EnvironmentKeys.platformBinaries];
|
|
if (platformBinaries != null && !platformBinaries.endsWith('/')) {
|
|
platformBinaries = '$platformBinaries/';
|
|
}
|
|
return new Future.value(
|
|
new FastaContext(
|
|
suite.root,
|
|
vm,
|
|
platformBinaries == null
|
|
? computePlatformBinariesLocation(forceBuildDir: true)
|
|
: Uri.base.resolve(platformBinaries),
|
|
experimentalFlags,
|
|
ignoreExpectations,
|
|
updateExpectations,
|
|
updateComments,
|
|
skipVm,
|
|
semiFuzz,
|
|
compileModeFromName(environment[EnvironmentKeys.compilationMode]),
|
|
verify,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Run extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
const Run();
|
|
|
|
@override
|
|
String get name => "run";
|
|
|
|
/// WARNING: Every subsequent step in this test will run async as well!
|
|
@override
|
|
bool get isAsync => true;
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
Uri? outputUri = result.outputUri;
|
|
if (outputUri == null) {
|
|
return pass(result);
|
|
}
|
|
|
|
File generated = new File.fromUri(result.outputUri!);
|
|
try {
|
|
FolderOptions folderOptions = context.suiteFolderOptions
|
|
.computeFolderOptions(result.description);
|
|
switch (folderOptions.target) {
|
|
case "vm":
|
|
if (context._platforms.isEmpty) {
|
|
throw "Executed `Run` step before initializing the context.";
|
|
}
|
|
List<String> args = <String>[];
|
|
args.add(generated.path);
|
|
StdioProcess process = await StdioProcess.run(
|
|
context.vm.toFilePath(),
|
|
args,
|
|
);
|
|
print(process.output);
|
|
Result<int> runResult = process.toResult();
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
runResult.outcome,
|
|
runResult.error,
|
|
);
|
|
case "none":
|
|
case "dart2js":
|
|
case "dartdevc":
|
|
case "wasm":
|
|
// TODO(johnniwinther): Support running dart2js, dartdevc and/or wasm.
|
|
return pass(result);
|
|
default:
|
|
throw new ArgumentError(
|
|
"Unsupported run target '${folderOptions.target}'.",
|
|
);
|
|
}
|
|
} finally {
|
|
await generated.parent.delete(recursive: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
class StressConstantEvaluatorStep
|
|
extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
const StressConstantEvaluatorStep();
|
|
|
|
@override
|
|
String get name => "stress constant evaluator";
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) {
|
|
KernelTarget target = result.sourceTarget;
|
|
TypeEnvironment environment = new TypeEnvironment(
|
|
target.loader.coreTypes,
|
|
target.loader.hierarchy,
|
|
);
|
|
StressConstantEvaluatorVisitor stressConstantEvaluatorVisitor =
|
|
new StressConstantEvaluatorVisitor(
|
|
target.backendTarget,
|
|
result.component,
|
|
result.options.environmentDefines,
|
|
target.globalFeatures.tripleShift.isEnabled,
|
|
environment,
|
|
!target.backendTarget.supportsSetLiterals,
|
|
result.options.errorOnUnevaluatedConstant,
|
|
);
|
|
for (Library lib in result.component.libraries) {
|
|
if (!result.isUserLibrary(lib)) continue;
|
|
lib.accept(stressConstantEvaluatorVisitor);
|
|
}
|
|
if (stressConstantEvaluatorVisitor.success > 0) {
|
|
result.extraConstantStrings.addAll(stressConstantEvaluatorVisitor.output);
|
|
result.extraConstantStrings.add(
|
|
"Extra constant evaluation: "
|
|
"evaluated: ${stressConstantEvaluatorVisitor.tries}, "
|
|
"effectively constant: ${stressConstantEvaluatorVisitor.success}",
|
|
);
|
|
}
|
|
return new Future.value(pass(result));
|
|
}
|
|
}
|
|
|
|
class StressConstantEvaluatorVisitor extends RecursiveResultVisitor<Node>
|
|
implements ErrorReporter {
|
|
late ConstantEvaluator constantEvaluator;
|
|
late ConstantEvaluator constantEvaluatorWithEmptyEnvironment;
|
|
int tries = 0;
|
|
int success = 0;
|
|
List<String> output = [];
|
|
|
|
@override
|
|
bool get supportsTrackingReportedErrors => false;
|
|
|
|
@override
|
|
bool get hasSeenError {
|
|
return unsupported("StressConstantEvaluatorVisitor.hasSeenError", -1, null);
|
|
}
|
|
|
|
StressConstantEvaluatorVisitor(
|
|
Target target,
|
|
Component component,
|
|
Map<String, String>? environmentDefines,
|
|
bool enableTripleShift,
|
|
TypeEnvironment typeEnvironment,
|
|
bool desugarSets,
|
|
bool errorOnUnevaluatedConstant,
|
|
) {
|
|
constantEvaluator = new ConstantEvaluator(
|
|
target.dartLibrarySupport,
|
|
target.constantsBackend,
|
|
component,
|
|
environmentDefines,
|
|
typeEnvironment,
|
|
this,
|
|
enableTripleShift: enableTripleShift,
|
|
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
|
|
);
|
|
constantEvaluatorWithEmptyEnvironment = new ConstantEvaluator(
|
|
target.dartLibrarySupport,
|
|
target.constantsBackend,
|
|
component,
|
|
{},
|
|
typeEnvironment,
|
|
this,
|
|
enableTripleShift: enableTripleShift,
|
|
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
|
|
);
|
|
}
|
|
|
|
Library? currentLibrary;
|
|
|
|
@override
|
|
Library visitLibrary(Library node) {
|
|
currentLibrary = node;
|
|
node.visitChildren(this);
|
|
currentLibrary = null;
|
|
return node;
|
|
}
|
|
|
|
Member? currentMember;
|
|
|
|
@override
|
|
Node defaultMember(Member node) {
|
|
Member? prevCurrentMember = currentMember;
|
|
currentMember = node;
|
|
node.visitChildren(this);
|
|
currentMember = prevCurrentMember;
|
|
return node;
|
|
}
|
|
|
|
@override
|
|
Node defaultExpression(Expression node) {
|
|
if (node is BasicLiteral) return node;
|
|
if (node is InvalidExpression) return node;
|
|
if (node is ConstantExpression) {
|
|
bool evaluate = false;
|
|
Constant constant = node.constant;
|
|
if (constant is UnevaluatedConstant) {
|
|
if (constant.expression is! InvalidExpression) {
|
|
evaluate = true;
|
|
}
|
|
}
|
|
if (!evaluate) return node;
|
|
if (constantEvaluator.hasEnvironment) {
|
|
throw "Unexpected UnevaluatedConstant "
|
|
"when the environment is not null.";
|
|
}
|
|
}
|
|
|
|
// Try to evaluate it as a constant.
|
|
tries++;
|
|
StaticTypeContext staticTypeContext;
|
|
if (currentMember == null) {
|
|
staticTypeContext = new StaticTypeContext.forAnnotations(
|
|
currentLibrary!,
|
|
constantEvaluator.typeEnvironment,
|
|
);
|
|
} else {
|
|
staticTypeContext = new StaticTypeContext(
|
|
currentMember!,
|
|
constantEvaluator.typeEnvironment,
|
|
);
|
|
}
|
|
Constant x = constantEvaluator.evaluate(staticTypeContext, node);
|
|
bool evaluatedWithEmptyEnvironment = false;
|
|
if (x is UnevaluatedConstant && x.expression is! InvalidExpression) {
|
|
// try with an environment
|
|
if (constantEvaluator.hasEnvironment) {
|
|
throw "Unexpected UnevaluatedConstant (with an InvalidExpression in "
|
|
"it) when the environment is not null.";
|
|
}
|
|
x = constantEvaluatorWithEmptyEnvironment.evaluate(
|
|
new StaticTypeContext(
|
|
currentMember!,
|
|
constantEvaluator.typeEnvironment,
|
|
),
|
|
new ConstantExpression(x),
|
|
);
|
|
evaluatedWithEmptyEnvironment = true;
|
|
}
|
|
if (x is UnevaluatedConstant) {
|
|
if (x.expression is! InvalidExpression &&
|
|
x.expression is! FileUriExpression) {
|
|
throw "Unexpected ${x.runtimeType} with "
|
|
"${x.expression.runtimeType} inside.";
|
|
}
|
|
node.visitChildren(this);
|
|
} else {
|
|
success++;
|
|
if (!evaluatedWithEmptyEnvironment) {
|
|
output.add(
|
|
"Evaluated: ${node.runtimeType} @ ${getLocation(node)} -> $x",
|
|
);
|
|
// Don't recurse into children - theoretically we could replace this
|
|
// node with a constant expression.
|
|
} else {
|
|
output.add(
|
|
"Evaluated with empty environment: "
|
|
"${node.runtimeType} @ ${getLocation(node)} -> $x",
|
|
);
|
|
// Here we (for now) recurse into children.
|
|
node.visitChildren(this);
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
String getLocation(TreeNode node) {
|
|
try {
|
|
return node.location.toString();
|
|
} catch (e) {
|
|
TreeNode? n = node;
|
|
while (n != null && n is! FileUriNode) {
|
|
n = n.parent;
|
|
}
|
|
if (n == null) return "(unknown location)";
|
|
FileUriNode fileUriNode = n as FileUriNode;
|
|
return ("(unknown position in ${fileUriNode.fileUri})");
|
|
}
|
|
}
|
|
|
|
@override
|
|
void report(LocatedMessage message, [List<LocatedMessage>? context]) {
|
|
// ignored.
|
|
}
|
|
}
|
|
|
|
class CompilationSetup {
|
|
final TestOptions testOptions;
|
|
final FolderOptions folderOptions;
|
|
final CompilerOptions compilerOptions;
|
|
final ProcessedOptions options;
|
|
final List<Iterable<String>> errors;
|
|
final CompilerOptions Function(
|
|
AllowedExperimentalFlags? allowedExperimentalFlags,
|
|
Map<ExperimentalFlag, Version>? experimentEnabledVersion,
|
|
Map<ExperimentalFlag, Version>? experimentReleasedVersion,
|
|
Uri? dynamicInterfaceSpecificationUri,
|
|
bool allowDynamicCallsInDynamicModules,
|
|
List<String> dynamicCallsSelectorAllowList,
|
|
)
|
|
createCompilerOptions;
|
|
|
|
final ProcessedOptions Function(CompilerOptions compilerOptions)
|
|
createProcessedOptions;
|
|
|
|
CompilationSetup(
|
|
this.testOptions,
|
|
this.folderOptions,
|
|
this.compilerOptions,
|
|
this.options,
|
|
this.errors,
|
|
this.createCompilerOptions,
|
|
this.createProcessedOptions,
|
|
);
|
|
}
|
|
|
|
CompilationSetup createCompilationSetup(
|
|
TestDescription description,
|
|
FastaContext context, {
|
|
bool? forceVerifyTo,
|
|
}) {
|
|
List<Iterable<String>> errors = <Iterable<String>>[];
|
|
|
|
Uri? librariesSpecificationUri = context.computeLibrariesSpecificationUri(
|
|
description,
|
|
);
|
|
Uri packagesFileUri =
|
|
context.computePackageConfigUri(description) ??
|
|
Uri.base.resolve(".dart_tool/package_config.json");
|
|
TestOptions testOptions = context.suiteTestOptions.computeTestOptions(
|
|
description,
|
|
);
|
|
FolderOptions folderOptions = context.suiteFolderOptions.computeFolderOptions(
|
|
description,
|
|
);
|
|
Map<ExperimentalFlag, bool> experimentalFlags = folderOptions
|
|
.computeExplicitExperimentalFlags(context.forcedExperimentalFlags);
|
|
List<Uri> inputs = <Uri>[description.uri];
|
|
|
|
CompilerOptions createCompilerOptions(
|
|
AllowedExperimentalFlags? allowedExperimentalFlags,
|
|
Map<ExperimentalFlag, Version>? experimentEnabledVersion,
|
|
Map<ExperimentalFlag, Version>? experimentReleasedVersion,
|
|
Uri? dynamicInterfaceSpecificationUri,
|
|
bool allowDynamicCallsInDynamicModules,
|
|
List<String> dynamicCallsSelectorAllowList,
|
|
) {
|
|
CompilerOptions compilerOptions = new CompilerOptions()
|
|
..onDiagnostic = (CfeDiagnosticMessage message) {
|
|
errors.add(message.plainTextFormatted);
|
|
}
|
|
..enableUnscheduledExperiments =
|
|
folderOptions.enableUnscheduledExperiments ?? false
|
|
..environmentDefines = folderOptions.defines
|
|
..explicitExperimentalFlags = experimentalFlags
|
|
..librariesSpecificationUri = librariesSpecificationUri
|
|
..allowedExperimentalFlagsForTesting = allowedExperimentalFlags
|
|
..experimentEnabledVersionForTesting = experimentEnabledVersion
|
|
..experimentReleasedVersionForTesting = experimentReleasedVersion
|
|
..skipPlatformVerification = true
|
|
..omitPlatform = true
|
|
..omitOsMessageForTesting = true
|
|
..packagesFileUri = packagesFileUri
|
|
..dynamicInterfaceSpecificationUri = dynamicInterfaceSpecificationUri
|
|
..allowDynamicCallsInDynamicModules = allowDynamicCallsInDynamicModules
|
|
..dynamicCallsSelectorAllowList = dynamicCallsSelectorAllowList
|
|
..target = createTarget(folderOptions, context)
|
|
..verify =
|
|
// TODO(johnniwinther): Enable verification in outline and modular
|
|
// compilation.
|
|
(context.compileMode != CompileMode.full || folderOptions.noVerify)
|
|
? false
|
|
: context.verify;
|
|
if (forceVerifyTo != null) {
|
|
compilerOptions.verify = forceVerifyTo;
|
|
}
|
|
compilerOptions.sdkSummary = context._getPlatformUri(
|
|
compilerOptions.target!,
|
|
);
|
|
if (folderOptions.overwriteCurrentSdkVersion != null) {
|
|
compilerOptions.currentSdkVersion =
|
|
folderOptions.overwriteCurrentSdkVersion!;
|
|
}
|
|
return compilerOptions;
|
|
}
|
|
|
|
ProcessedOptions createProcessedOptions(CompilerOptions compilerOptions) {
|
|
return new ProcessedOptions(options: compilerOptions, inputs: inputs);
|
|
}
|
|
|
|
// Disable colors to ensure that expectation files are the same across
|
|
// platforms and independent of stdin/stderr.
|
|
colors.enableColors = false;
|
|
|
|
CompilerOptions compilerOptions = createCompilerOptions(
|
|
testOptions.allowedExperimentalFlags,
|
|
testOptions.experimentEnabledVersion,
|
|
testOptions.experimentReleasedVersion,
|
|
testOptions.dynamicInterfaceSpecificationUri,
|
|
testOptions.allowDynamicCallsInDynamicModules,
|
|
testOptions.dynamicCallsSelectorAllowList,
|
|
);
|
|
ProcessedOptions options = createProcessedOptions(compilerOptions);
|
|
options.sdkSummaryComponent = context.loadPlatform(options.target);
|
|
return new CompilationSetup(
|
|
testOptions,
|
|
folderOptions,
|
|
compilerOptions,
|
|
options,
|
|
errors,
|
|
createCompilerOptions,
|
|
createProcessedOptions,
|
|
);
|
|
}
|
|
|
|
class FuzzCompiles
|
|
extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
const FuzzCompiles();
|
|
|
|
@override
|
|
String get name {
|
|
return "semifuzz";
|
|
}
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
bool? originalFlag =
|
|
context.forcedExperimentalFlags[ExperimentalFlag
|
|
.alternativeInvalidationStrategy];
|
|
context.forcedExperimentalFlags[ExperimentalFlag
|
|
.alternativeInvalidationStrategy] =
|
|
true;
|
|
|
|
CompilationSetup compilationSetup = createCompilationSetup(
|
|
result.description,
|
|
context,
|
|
forceVerifyTo: false,
|
|
);
|
|
|
|
Target backendTarget = compilationSetup.options.target;
|
|
if (backendTarget is TestTarget) {
|
|
// For the fuzzing we want to run the VM transformations, i.e. have the
|
|
// incremental compiler behave as normal.
|
|
backendTarget.performModularTransformations = true;
|
|
}
|
|
|
|
Component platform = context.loadPlatform(backendTarget);
|
|
|
|
final bool hasErrors;
|
|
{
|
|
bool foundErrors = false;
|
|
if ((result.component.problemsAsJson?.length ?? 0) > 0) {
|
|
foundErrors = true;
|
|
} else {
|
|
for (Library library in result.component.libraries) {
|
|
if ((library.problemsAsJson?.length ?? 0) > 0) {
|
|
foundErrors = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
hasErrors = foundErrors;
|
|
}
|
|
|
|
try {
|
|
Result<ComponentResult>? passResult = await performFileInvalidation(
|
|
compilationSetup,
|
|
platform,
|
|
context,
|
|
originalCompilationResult: result,
|
|
forceAndCheckRebuildBodiesOnly: false,
|
|
);
|
|
if (passResult != null) return passResult;
|
|
|
|
passResult = await performChunkReordering(
|
|
compilationSetup,
|
|
platform,
|
|
result,
|
|
context,
|
|
);
|
|
if (passResult != null) return passResult;
|
|
|
|
passResult = await wrapInPart(
|
|
compilationSetup,
|
|
platform,
|
|
result,
|
|
context,
|
|
);
|
|
if (passResult != null) return passResult;
|
|
|
|
if (!hasErrors) {
|
|
// To get proper splitting (between dill and not dill builders) we need
|
|
// experimental invalidation - it doesn't work when there's errors
|
|
// though, so skip those up front.
|
|
// Note also that because of splitting and privacy this might fail with
|
|
// an error --- so it should probably be the last one. At some point we
|
|
// might swallow that so we can continue, but for now it will be good
|
|
// to know when it's not run because of that.
|
|
passResult = await performFileSplitting(
|
|
compilationSetup,
|
|
platform,
|
|
result,
|
|
context,
|
|
);
|
|
if (passResult != null) return passResult;
|
|
}
|
|
|
|
return pass(result);
|
|
} catch (e, st) {
|
|
if (e is AssertionError || (e is Crash && e.error is AssertionError)) {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzAssertFailure,
|
|
"Assertion failure with '$e' when fuzz compiling.\n\n$st",
|
|
);
|
|
}
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzCrash,
|
|
"Crashed with '$e' when fuzz compiling.\n\n$st",
|
|
);
|
|
} finally {
|
|
if (originalFlag != null) {
|
|
context.forcedExperimentalFlags[ExperimentalFlag
|
|
.alternativeInvalidationStrategy] =
|
|
originalFlag;
|
|
} else {
|
|
context.forcedExperimentalFlags.remove(
|
|
ExperimentalFlag.alternativeInvalidationStrategy,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Perform a number of compilations where each user-file is invalidated
|
|
/// one at a time, and the code recompiled after each invalidation.
|
|
/// Verifies that either it's an error in all cases or in no cases.
|
|
/// Verifies that the same libraries comes out as a result.
|
|
Future<Result<ComponentResult>?> performFileInvalidation(
|
|
CompilationSetup compilationSetup,
|
|
Component platform,
|
|
FastaContext context, {
|
|
ComponentResult? originalCompilationResult,
|
|
required bool forceAndCheckRebuildBodiesOnly,
|
|
}) async {
|
|
compilationSetup.errors.clear();
|
|
SemiForceExperimentalInvalidationIncrementalCompiler incrementalCompiler =
|
|
new SemiForceExperimentalInvalidationIncrementalCompiler.fromComponent(
|
|
new CompilerContext(compilationSetup.options),
|
|
platform,
|
|
);
|
|
incrementalCompiler.skipExperimentalInvalidationChecksForTesting =
|
|
forceAndCheckRebuildBodiesOnly;
|
|
IncrementalCompilerResult incrementalCompilerResult =
|
|
await incrementalCompiler.computeDelta();
|
|
final Component component = incrementalCompilerResult.component;
|
|
print("Compiled and got ${component.libraries.length} libs");
|
|
if (!canSerialize(component)) {
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Couldn't serialize initial component for fuzzing",
|
|
);
|
|
}
|
|
|
|
final UriTranslator uriTranslator = await compilationSetup.options
|
|
.getUriTranslator();
|
|
final Set<Uri> userLibraries = createUserLibrariesImportUriSet(
|
|
component,
|
|
uriTranslator,
|
|
);
|
|
final bool expectErrors = compilationSetup.errors.isNotEmpty;
|
|
|
|
if (expectErrors && forceAndCheckRebuildBodiesOnly) {
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailureOnForceRebuildBodies,
|
|
"Errors upon compilation not compatible "
|
|
"with forcing rebuild bodies. Got ${compilationSetup.errors}",
|
|
);
|
|
}
|
|
|
|
List<Iterable<String>> originalErrors = new List<Iterable<String>>.from(
|
|
compilationSetup.errors,
|
|
);
|
|
|
|
if (originalCompilationResult != null) {
|
|
Set<Uri> intersectionUserLibraries = originalCompilationResult
|
|
.userLibraries
|
|
.intersection(userLibraries);
|
|
if (intersectionUserLibraries.length != userLibraries.length ||
|
|
userLibraries.length !=
|
|
originalCompilationResult.userLibraries.length) {
|
|
String originalCompileString = originalCompilationResult.userLibraries
|
|
.map((e) => e.toString())
|
|
.join("\n");
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Got a different amount of user libraries on first compile "
|
|
"compared to 'original' compilation:\n\n"
|
|
"This compile:\n"
|
|
"${userLibraries.map((e) => e.toString()).join("\n")}\n\n"
|
|
"Original compile:\n"
|
|
"$originalCompileString",
|
|
);
|
|
}
|
|
}
|
|
|
|
compilationSetup.errors.clear();
|
|
for (Uri importUri in userLibraries) {
|
|
print(" -> invalidating $importUri");
|
|
incrementalCompiler.invalidate(importUri);
|
|
final IncrementalCompilerResult newResult;
|
|
try {
|
|
newResult = await incrementalCompiler.computeDelta(fullComponent: true);
|
|
} catch (e, st) {
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzCrash,
|
|
"Crashed with '$e' on recompilation after invalidating "
|
|
"'$importUri'.\n\n$st",
|
|
);
|
|
}
|
|
if (forceAndCheckRebuildBodiesOnly) {
|
|
bool didRebuildBodiesOnly =
|
|
incrementalCompiler.recorderForTesting.rebuildBodiesCount! > 0;
|
|
if (!didRebuildBodiesOnly) {
|
|
AdvancedInvalidationResult? error =
|
|
incrementalCompiler.recorderForTesting.advancedInvalidationResult;
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Didn't rebuild bodies only! (error: $error)",
|
|
);
|
|
}
|
|
}
|
|
final Component newComponent = newResult.component;
|
|
print(" -> and got ${newComponent.libraries.length} libs");
|
|
if (canFindDuplicateLibraries(newComponent)) {
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Found duplicate libraries in fuzzed component",
|
|
);
|
|
}
|
|
if (!canSerialize(newComponent)) {
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Couldn't serialize fuzzed component",
|
|
);
|
|
}
|
|
|
|
final Set<Uri> newUserLibraries = createUserLibrariesImportUriSet(
|
|
newComponent,
|
|
uriTranslator,
|
|
);
|
|
final bool gotErrors = compilationSetup.errors.isNotEmpty;
|
|
|
|
if (expectErrors != gotErrors) {
|
|
if (expectErrors) {
|
|
String errorsString = originalErrors
|
|
.map((error) => error.join('\n'))
|
|
.join('\n\n');
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Expected these errors:\n${errorsString}\n\n"
|
|
"but didn't get any after invalidating $importUri",
|
|
);
|
|
} else {
|
|
String errorsString = compilationSetup.errors
|
|
.map((error) => error.join('\n'))
|
|
.join('\n\n');
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Unexpected errors:\n${errorsString}\n\n"
|
|
"after invalidating $importUri",
|
|
);
|
|
}
|
|
}
|
|
|
|
Set<Uri> intersectionUserLibraries = userLibraries.intersection(
|
|
newUserLibraries,
|
|
);
|
|
if (intersectionUserLibraries.length != newUserLibraries.length ||
|
|
newUserLibraries.length != userLibraries.length) {
|
|
String originalCompileString = "";
|
|
if (originalCompilationResult != null) {
|
|
originalCompileString =
|
|
"Original compile:\n" +
|
|
originalCompilationResult.userLibraries
|
|
.map((e) => e.toString())
|
|
.join("\n");
|
|
}
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Got a different amount of user libraries on recompile "
|
|
"compared to 'original' compilation after having invalidated "
|
|
"$importUri.\n\n"
|
|
"This compile:\n"
|
|
"${newUserLibraries.map((e) => e.toString()).join("\n")}\n\n"
|
|
"${originalCompileString}",
|
|
);
|
|
}
|
|
|
|
if (!compareComponents(component, newComponent)) {
|
|
return new Result<ComponentResult>(
|
|
originalCompilationResult,
|
|
semiFuzzFailure,
|
|
"Fuzzed component changed in an unexpected way.",
|
|
);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool compareComponents(Component a, Component b) {
|
|
if (a.libraries.length != b.libraries.length) {
|
|
print("Not the same number of libraries.");
|
|
return false;
|
|
}
|
|
a.libraries.sort((l1, l2) {
|
|
return "${l1.importUri}".compareTo("${l2.importUri}");
|
|
});
|
|
b.libraries.sort((l1, l2) {
|
|
return "${l1.importUri}".compareTo("${l2.importUri}");
|
|
});
|
|
for (int i = 0; i < a.libraries.length; i++) {
|
|
EquivalenceResult result = checkEquivalence(
|
|
a.libraries[i],
|
|
b.libraries[i],
|
|
strategy: const Strategy(),
|
|
);
|
|
if (!result.isEquivalent) {
|
|
print(
|
|
'Original component and new component are not equivalent:\n'
|
|
'$result',
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool canSerialize(Component component) {
|
|
ByteSink byteSink = new ByteSink();
|
|
try {
|
|
new BinaryPrinter(byteSink).writeComponentFile(component);
|
|
return true;
|
|
} catch (e, st) {
|
|
print("Can't serialize, got '$e' from $st");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool canFindDuplicateLibraries(Component component) {
|
|
_LibraryFinder libraryFinder = new _LibraryFinder();
|
|
component.accept(libraryFinder);
|
|
Set<Uri> importUris = {};
|
|
for (Library library in libraryFinder.allLibraries) {
|
|
if (!importUris.add(library.importUri)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Perform a compilations where the user file is wrapped in a part and that
|
|
/// no crashing occurs (because of wrong uris being set).
|
|
Future<Result<ComponentResult>?> wrapInPart(
|
|
CompilationSetup compilationSetup,
|
|
Component platform,
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
FileSystem orgFileSystem = compilationSetup.options.fileSystem;
|
|
Uri mainUri = compilationSetup.options.inputs.single;
|
|
Uint8List orgData = await orgFileSystem.entityForUri(mainUri).readAsBytes();
|
|
|
|
compilationSetup.options.clearFileSystemCache();
|
|
_FakeFileSystem fs = new _FakeFileSystem(orgFileSystem);
|
|
compilationSetup.compilerOptions.fileSystem = fs;
|
|
|
|
Uri newEntryUri = Uri.parse("foo:///newMain.dart");
|
|
|
|
Uint8List newMainData = Uint8List.fromList([
|
|
...utf8.encode("""
|
|
part of "${newEntryUri}";
|
|
|
|
// La la la la la la la la la la la la la.
|
|
// La la la la la la la la la la la la la.
|
|
// La la la la la la la la la la la la la.
|
|
// La la la la la la la la la la la la la.
|
|
// La la la la la la la la la la la la la.
|
|
"""),
|
|
...orgData,
|
|
]);
|
|
|
|
compilationSetup.errors.clear();
|
|
|
|
fs.data[mainUri] = newMainData;
|
|
fs.data[newEntryUri] = utf8.encode("part '$mainUri';");
|
|
IncrementalCompiler incrementalCompiler =
|
|
new IncrementalCompiler.fromComponent(
|
|
new CompilerContext(compilationSetup.options),
|
|
platform,
|
|
);
|
|
try {
|
|
await incrementalCompiler.computeDelta(entryPoints: [newEntryUri]);
|
|
} catch (e, st) {
|
|
if (e is AssertionError || (e is Crash && e.error is AssertionError)) {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzAssertFailure,
|
|
"Assertion failure with '$e' after putting '$mainUri' into a part."
|
|
"\n\n$st",
|
|
);
|
|
}
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzCrash,
|
|
"Crashed with '$e' after putting '$mainUri' into a part."
|
|
"\n\n$st",
|
|
);
|
|
}
|
|
|
|
compilationSetup.options.clearFileSystemCache();
|
|
compilationSetup.compilerOptions.fileSystem = orgFileSystem;
|
|
return null;
|
|
}
|
|
|
|
/// Perform a number of compilations where each user-file is in turn sorted
|
|
/// in both ascending and descending order (i.e. the procedures and classes
|
|
/// etc are sorted).
|
|
/// Verifies that either it's an error in all cases or in no cases.
|
|
/// Verifies that the same libraries comes out as a result.
|
|
Future<Result<ComponentResult>?> performChunkReordering(
|
|
CompilationSetup compilationSetup,
|
|
Component platform,
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
compilationSetup.errors.clear();
|
|
|
|
FileSystem orgFileSystem = compilationSetup.options.fileSystem;
|
|
compilationSetup.options.clearFileSystemCache();
|
|
_FakeFileSystem fs = new _FakeFileSystem(orgFileSystem);
|
|
compilationSetup.compilerOptions.fileSystem = fs;
|
|
IncrementalCompiler incrementalCompiler =
|
|
new IncrementalCompiler.fromComponent(
|
|
new CompilerContext(compilationSetup.options),
|
|
platform,
|
|
);
|
|
IncrementalCompilerResult initialResult = await incrementalCompiler
|
|
.computeDelta();
|
|
Component initialComponent = initialResult.component;
|
|
if (!canSerialize(initialComponent)) {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzFailure,
|
|
"Couldn't serialize initial component for fuzzing",
|
|
);
|
|
}
|
|
|
|
final bool expectErrors = compilationSetup.errors.isNotEmpty;
|
|
List<Iterable<String>> originalErrors = new List<Iterable<String>>.from(
|
|
compilationSetup.errors,
|
|
);
|
|
compilationSetup.errors.clear();
|
|
|
|
// Create lookup-table from file uri to whatever.
|
|
Map<Uri, LibraryBuilder> builders = {};
|
|
for (LibraryBuilder builder
|
|
in incrementalCompiler
|
|
.kernelTargetForTesting!
|
|
.loader
|
|
.loadedLibraryBuilders) {
|
|
if (builder.importUri.isScheme("dart") && !builder.isSynthetic) continue;
|
|
builders[builder.fileUri] = builder;
|
|
for (LibraryPart part in builder.library.parts) {
|
|
Uri thisPartUri = builder.importUri.resolve(part.partUri);
|
|
if (thisPartUri.isScheme("package")) {
|
|
thisPartUri = incrementalCompiler
|
|
.kernelTargetForTesting!
|
|
.uriTranslator
|
|
.translate(thisPartUri)!;
|
|
}
|
|
builders[thisPartUri] = builder;
|
|
}
|
|
}
|
|
|
|
for (Uri uri in fs.data.keys) {
|
|
print("Work on $uri");
|
|
LibraryBuilder? builder = builders[uri];
|
|
if (builder == null) {
|
|
print("Skipping $uri -- couldn't find builder for it.");
|
|
continue;
|
|
}
|
|
Uint8List? orgData = fs.data[uri];
|
|
if (orgData == null) {
|
|
print("Skipping $uri -- couldn't find source for it.");
|
|
continue;
|
|
}
|
|
FuzzAstVisitorSorter fuzzAstVisitorSorter;
|
|
try {
|
|
LibraryFeatures libFeatures = new LibraryFeatures(
|
|
compilationSetup.options.globalFeatures,
|
|
builder.importUri,
|
|
builder.languageVersion,
|
|
);
|
|
fuzzAstVisitorSorter = new FuzzAstVisitorSorter(
|
|
orgData,
|
|
new LibraryExperimentalFeatures(libFeatures),
|
|
);
|
|
} on FormatException catch (e, st) {
|
|
// UTF-16-LE formatted test crashes `utf8.decode(bytes)` --- catch that
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzCrash,
|
|
"$e\n\n"
|
|
"$st",
|
|
);
|
|
}
|
|
|
|
// Sort ascending and then compile. Then sort descending and try again.
|
|
for (void Function() sorter in [
|
|
() => fuzzAstVisitorSorter.sortAscending(),
|
|
() => fuzzAstVisitorSorter.sortDescending(),
|
|
]) {
|
|
sorter();
|
|
StringBuffer sb = new StringBuffer();
|
|
for (FuzzAstVisitorSorterChunk chunk in fuzzAstVisitorSorter.chunks) {
|
|
sb.writeln(chunk.getSource());
|
|
}
|
|
Uint8List sortedData = utf8.encode(sb.toString());
|
|
fs.data[uri] = sortedData;
|
|
incrementalCompiler = new IncrementalCompiler.fromComponent(
|
|
new CompilerContext(compilationSetup.options),
|
|
platform,
|
|
);
|
|
try {
|
|
IncrementalCompilerResult incrementalCompilerResult =
|
|
await incrementalCompiler.computeDelta();
|
|
Component component = incrementalCompilerResult.component;
|
|
if (!canSerialize(component)) {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzFailure,
|
|
"Couldn't serialize fuzzed component",
|
|
);
|
|
}
|
|
} catch (e, st) {
|
|
if (e is AssertionError ||
|
|
(e is Crash && e.error is AssertionError)) {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzAssertFailure,
|
|
"Assertion failure with '$e' after reordering '$uri' to\n\n"
|
|
"$sb\n\n"
|
|
"$st",
|
|
);
|
|
}
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzCrash,
|
|
"Crashed with '$e' after reordering '$uri' to\n\n"
|
|
"$sb\n\n"
|
|
"$st",
|
|
);
|
|
}
|
|
final bool gotErrors = compilationSetup.errors.isNotEmpty;
|
|
String errorsString = compilationSetup.errors
|
|
.map((error) => error.join('\n'))
|
|
.join('\n\n');
|
|
compilationSetup.errors.clear();
|
|
|
|
// TODO(jensj): When we get errors we should try to verify it's
|
|
// "the same" errors (note, though, that they will naturally be at a
|
|
// changed location --- some will likely have different wording).
|
|
if (expectErrors != gotErrors) {
|
|
if (expectErrors) {
|
|
String errorsString = originalErrors
|
|
.map((error) => error.join('\n'))
|
|
.join('\n\n');
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzFailure,
|
|
"Expected these errors:\n${errorsString}\n\n"
|
|
"but didn't get any after reordering $uri "
|
|
"to have this content:\n\n"
|
|
"$sb",
|
|
);
|
|
} else {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzFailure,
|
|
"Unexpected errors:\n${errorsString}\n\n"
|
|
"after reordering $uri to have this content:\n\n"
|
|
"$sb",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
compilationSetup.options.clearFileSystemCache();
|
|
compilationSetup.compilerOptions.fileSystem = orgFileSystem;
|
|
return null;
|
|
}
|
|
|
|
/// Splits all files into "sub files" that all import and export each other
|
|
/// so everything should still work (except for privacy).
|
|
/// Then invalidate one file at a time with forced experimental invalidation.
|
|
///
|
|
/// Prerequisite: No errors should be present, as that doesn't work with
|
|
/// experimental invalidation.
|
|
Future<Result<ComponentResult>?> performFileSplitting(
|
|
CompilationSetup compilationSetup,
|
|
Component platform,
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
FileSystem orgFileSystem = compilationSetup.options.fileSystem;
|
|
compilationSetup.options.clearFileSystemCache();
|
|
_FakeFileSystem fs = new _FakeFileSystem(orgFileSystem);
|
|
compilationSetup.compilerOptions.fileSystem = fs;
|
|
IncrementalCompiler incrementalCompiler =
|
|
new IncrementalCompiler.fromComponent(
|
|
new CompilerContext(compilationSetup.options),
|
|
platform,
|
|
);
|
|
IncrementalCompilerResult initialResult = await incrementalCompiler
|
|
.computeDelta();
|
|
Component initialComponent = initialResult.component;
|
|
if (!canSerialize(initialComponent)) {
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzFailure,
|
|
"Couldn't serialize initial component for fuzzing",
|
|
);
|
|
}
|
|
|
|
// Create lookup-table from file uri to whatever.
|
|
Map<Uri, LibraryBuilder> builders = {};
|
|
for (LibraryBuilder builder
|
|
in incrementalCompiler
|
|
.kernelTargetForTesting!
|
|
.loader
|
|
.loadedLibraryBuilders) {
|
|
if (builder.importUri.isScheme("dart") && !builder.isSynthetic) continue;
|
|
if (builder.importUri.isScheme("package") &&
|
|
!builder.fileUri.toString().contains("/pkg/front_end/testcases/")) {
|
|
// A package uri where the file uri is *not* inside out testcases.
|
|
// This for instance ignores "package:expect/expect.dart" etc.
|
|
continue;
|
|
}
|
|
builders[builder.fileUri] = builder;
|
|
for (LibraryPart part in builder.library.parts) {
|
|
Uri thisPartUri = builder.importUri.resolve(part.partUri);
|
|
if (thisPartUri.isScheme("package")) {
|
|
thisPartUri = incrementalCompiler
|
|
.kernelTargetForTesting!
|
|
.uriTranslator
|
|
.translate(thisPartUri)!;
|
|
}
|
|
builders[thisPartUri] = builder;
|
|
}
|
|
}
|
|
|
|
List<Uri> originalUris = List<Uri>.of(fs.data.keys);
|
|
uriLoop:
|
|
for (Uri uri in originalUris) {
|
|
print("Work on $uri");
|
|
LibraryBuilder? builder = builders[uri];
|
|
if (builder == null) {
|
|
print("Skipping $uri -- couldn't find builder for it.");
|
|
continue;
|
|
}
|
|
Uint8List orgData = fs.data[uri]!;
|
|
FuzzAstVisitorSorter fuzzAstVisitorSorter;
|
|
try {
|
|
LibraryFeatures libFeatures = new LibraryFeatures(
|
|
compilationSetup.options.globalFeatures,
|
|
builder.importUri,
|
|
builder.languageVersion,
|
|
);
|
|
fuzzAstVisitorSorter = new FuzzAstVisitorSorter(
|
|
orgData,
|
|
new LibraryExperimentalFeatures(libFeatures),
|
|
);
|
|
} on FormatException catch (e, st) {
|
|
// UTF-16-LE formatted test crashes `utf8.decode(bytes)` --- catch that
|
|
return new Result<ComponentResult>(
|
|
result,
|
|
semiFuzzCrash,
|
|
"$e\n\n"
|
|
"$st",
|
|
);
|
|
}
|
|
|
|
// Put each chunk into its own file.
|
|
StringBuffer headerSb = new StringBuffer();
|
|
StringBuffer orgFileOnlyHeaderSb = new StringBuffer();
|
|
List<FuzzAstVisitorSorterChunk> nonHeaderChunks = [];
|
|
|
|
print("Found ${fuzzAstVisitorSorter.chunks.length} chunks...");
|
|
|
|
for (FuzzAstVisitorSorterChunk chunk in fuzzAstVisitorSorter.chunks) {
|
|
if (chunk.originalType == FuzzOriginalType.PartOf) {
|
|
print("Skipping part...");
|
|
continue uriLoop;
|
|
} else if (chunk.originalType == FuzzOriginalType.Part) {
|
|
// The part declaration should only be in the "main" file.
|
|
orgFileOnlyHeaderSb.writeln(chunk.getSource());
|
|
} else if (chunk.originalType == FuzzOriginalType.Import ||
|
|
chunk.originalType == FuzzOriginalType.Export ||
|
|
chunk.originalType == FuzzOriginalType.LibraryName ||
|
|
chunk.originalType == FuzzOriginalType.LanguageVersion) {
|
|
headerSb.writeln(chunk.getSource());
|
|
} else {
|
|
nonHeaderChunks.add(chunk);
|
|
}
|
|
}
|
|
|
|
Uri getUriForChunk(int chunkNum) {
|
|
return uri.resolve(uri.pathSegments.last + ".split.$chunkNum.dart");
|
|
}
|
|
|
|
int totalSubFiles = nonHeaderChunks.length;
|
|
int currentSubFile = 0;
|
|
for (FuzzAstVisitorSorterChunk chunk in nonHeaderChunks) {
|
|
// We need to have special handling for dart versions, imports,
|
|
// exports, etc.
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.writeln(headerSb.toString());
|
|
sb.writeln("import '${uri.pathSegments.last}';");
|
|
sb.writeln(chunk.getSource());
|
|
fs.data[getUriForChunk(currentSubFile)] = utf8.encode(sb.toString());
|
|
print(
|
|
" => Split into ${getUriForChunk(currentSubFile)}:\n"
|
|
"${sb.toString()}\n-------------\n",
|
|
);
|
|
currentSubFile++;
|
|
}
|
|
|
|
// Rewrite main file.
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.writeln(headerSb.toString());
|
|
for (int i = 0; i < totalSubFiles; i++) {
|
|
sb.writeln("import '${getUriForChunk(i).pathSegments.last}';");
|
|
sb.writeln("export '${getUriForChunk(i).pathSegments.last}';");
|
|
}
|
|
sb.writeln(orgFileOnlyHeaderSb.toString());
|
|
print(" => Main file becomes:\n${sb.toString()}\n-------------\n");
|
|
fs.data[uri] = utf8.encode(sb.toString());
|
|
}
|
|
|
|
Result<ComponentResult>? passResult = await performFileInvalidation(
|
|
compilationSetup,
|
|
platform,
|
|
context,
|
|
originalCompilationResult: null,
|
|
forceAndCheckRebuildBodiesOnly: true,
|
|
);
|
|
if (passResult != null) return passResult;
|
|
|
|
compilationSetup.options.clearFileSystemCache();
|
|
compilationSetup.compilerOptions.fileSystem = orgFileSystem;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class Strategy extends EquivalenceStrategy {
|
|
const Strategy();
|
|
|
|
@override
|
|
bool checkLibrary_procedures(
|
|
EquivalenceVisitor visitor,
|
|
Library node,
|
|
Library other,
|
|
) {
|
|
return visitor.checkSets(
|
|
node.procedures.toSet(),
|
|
other.procedures.toSet(),
|
|
visitor.matchNamedNodes,
|
|
visitor.checkNodes,
|
|
'procedures',
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool checkClass_procedures(
|
|
EquivalenceVisitor visitor,
|
|
Class node,
|
|
Class other,
|
|
) {
|
|
return visitor.checkSets(
|
|
node.procedures.toSet(),
|
|
other.procedures.toSet(),
|
|
visitor.matchNamedNodes,
|
|
visitor.checkNodes,
|
|
'procedures',
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool checkLibrary_additionalExports(
|
|
EquivalenceVisitor visitor,
|
|
Library node,
|
|
Library other,
|
|
) {
|
|
return visitor.checkSets(
|
|
node.additionalExports.toSet(),
|
|
other.additionalExports.toSet(),
|
|
visitor.matchReferences,
|
|
visitor.checkReferences,
|
|
'additionalExports',
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool checkVariableStatement_binaryOffsetNoTag(
|
|
EquivalenceVisitor visitor,
|
|
VariableDeclaration node,
|
|
VariableDeclaration other,
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
/// Allow assuming references like
|
|
/// "_Class&Superclass&Mixin::@methods::method4" and
|
|
/// "Mixin::@methods::method4" are equal (an interfaceTargetReference change
|
|
/// that often occur on recompile in regards to mixins).
|
|
///
|
|
/// Copied from incremental_dart2js_load_from_dill_test.dart
|
|
bool _isMixinOrCloneReference(
|
|
EquivalenceVisitor visitor,
|
|
Reference? a,
|
|
Reference? b,
|
|
String propertyName,
|
|
) {
|
|
if (a != null && b != null) {
|
|
ReferenceName thisName = ReferenceName.fromReference(a)!;
|
|
ReferenceName otherName = ReferenceName.fromReference(b)!;
|
|
if (thisName.isMember &&
|
|
otherName.isMember &&
|
|
thisName.memberName == otherName.memberName) {
|
|
String? thisClassName = thisName.declarationName;
|
|
String? otherClassName = otherName.declarationName;
|
|
if (thisClassName != null &&
|
|
otherClassName != null &&
|
|
thisClassName.contains('&${otherClassName}')) {
|
|
visitor.assumeReferences(a, b);
|
|
}
|
|
}
|
|
}
|
|
return visitor.checkReferences(a, b, propertyName);
|
|
}
|
|
|
|
@override
|
|
bool checkInstanceInvocation_interfaceTargetReference(
|
|
EquivalenceVisitor visitor,
|
|
InstanceInvocation node,
|
|
InstanceInvocation other,
|
|
) {
|
|
return _isMixinOrCloneReference(
|
|
visitor,
|
|
node.interfaceTargetReference,
|
|
other.interfaceTargetReference,
|
|
'interfaceTargetReference',
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool checkInstanceSet_interfaceTargetReference(
|
|
EquivalenceVisitor visitor,
|
|
InstanceSet node,
|
|
InstanceSet other,
|
|
) {
|
|
return _isMixinOrCloneReference(
|
|
visitor,
|
|
node.interfaceTargetReference,
|
|
other.interfaceTargetReference,
|
|
'interfaceTargetReference',
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool checkLibrary_problemsAsJson(
|
|
EquivalenceVisitor visitor,
|
|
Library node,
|
|
Library other,
|
|
) {
|
|
List<String>? a = node.problemsAsJson;
|
|
if (a != null) {
|
|
a = a.map(_rewriteJsonMap).toList();
|
|
}
|
|
List<String>? b = other.problemsAsJson;
|
|
if (b != null) {
|
|
b = b.map(_rewriteJsonMap).toList();
|
|
}
|
|
return visitor.checkLists(a, b, visitor.checkValues, 'problemsAsJson');
|
|
}
|
|
|
|
String _rewriteJsonMap(String s) {
|
|
// Several things can change, e.g.
|
|
// * unserializableExports (when from dill) causes the message code to
|
|
// become "unspecified" from usage of `templateUnspecified`)
|
|
// * Order of things (and for instance which line it's reported on) can
|
|
// change depending on order, e.g. duplicate names in exports upon
|
|
// recompile of one of the exported libraries.
|
|
Map<String, dynamic> decoded = jsonDecode(s);
|
|
return decoded["uri"];
|
|
}
|
|
}
|
|
|
|
class FuzzAstVisitorSorterChunk {
|
|
final FuzzOriginalType originalType;
|
|
final String data;
|
|
final String? metadataAndComments;
|
|
final int layer;
|
|
|
|
FuzzAstVisitorSorterChunk(
|
|
this.originalType,
|
|
this.data,
|
|
this.metadataAndComments,
|
|
this.layer,
|
|
);
|
|
|
|
@override
|
|
String toString() {
|
|
return "FuzzAstVisitorSorterChunk[${getSource()}]";
|
|
}
|
|
|
|
String getSource() {
|
|
if (metadataAndComments != null) {
|
|
return "$metadataAndComments\n$data";
|
|
}
|
|
return "$data";
|
|
}
|
|
}
|
|
|
|
enum FuzzSorterState { nonSortable, importExportSortable, sortableRest }
|
|
|
|
enum FuzzOriginalType {
|
|
Import,
|
|
Export,
|
|
LanguageVersion,
|
|
AdditionalMetadata,
|
|
Class,
|
|
Mixin,
|
|
Enum,
|
|
Extension,
|
|
ExtensionTypeDeclaration,
|
|
LibraryName,
|
|
Part,
|
|
PartOf,
|
|
TopLevelFields,
|
|
TopLevelMethod,
|
|
TypeDef,
|
|
}
|
|
|
|
// We extend IgnoreSomeForCompatibilityAstVisitor for compatibility with how
|
|
// the code is currently written: At least visiting `TypeVariablesEnd` can
|
|
// cause trouble with how metadata is handled here, e.g.
|
|
// ```
|
|
// @Const()
|
|
// extension Extension<@Const() T> on Class<T> {
|
|
// }
|
|
// ```
|
|
// will visit the first metadata, then the type parameters which itself has the
|
|
// second metadata, only then it visits the extension (which is what we care
|
|
// about here) --- and we will with the current handling of metadata think the
|
|
// metadata for the extension goes from the first metadata to the second.
|
|
class FuzzAstVisitorSorter extends IgnoreSomeForCompatibilityAstVisitor {
|
|
final Uint8List bytes;
|
|
final String asString;
|
|
final ExperimentalFeatures experimentalFeatures;
|
|
|
|
FuzzAstVisitorSorter(this.bytes, this.experimentalFeatures)
|
|
: asString = utf8.decode(bytes) {
|
|
CompilationUnitEnd ast = getAST(
|
|
bytes,
|
|
includeBody: false,
|
|
includeComments: true,
|
|
experimentalFeatures: experimentalFeatures,
|
|
);
|
|
ast.accept(this);
|
|
|
|
if (metadataStart == null &&
|
|
ast.token.precedingComments != null &&
|
|
chunks.isEmpty) {
|
|
_chunkOutLanguageVersionComment(ast.token);
|
|
}
|
|
|
|
if (metadataStart != null) {
|
|
String metadata = asString.substring(
|
|
metadataStart!.charOffset,
|
|
metadataEndInclusive!.charEnd,
|
|
);
|
|
layer++;
|
|
chunks.add(
|
|
new FuzzAstVisitorSorterChunk(
|
|
FuzzOriginalType.AdditionalMetadata,
|
|
"",
|
|
metadata,
|
|
layer,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void sortAscending() {
|
|
chunks.sort(_ascendingSorter);
|
|
}
|
|
|
|
void sortDescending() {
|
|
chunks.sort(_descendingSorter);
|
|
}
|
|
|
|
int _ascendingSorter(
|
|
FuzzAstVisitorSorterChunk a,
|
|
FuzzAstVisitorSorterChunk b,
|
|
) {
|
|
if (a.layer < b.layer) return -1;
|
|
if (a.layer > b.layer) return 1;
|
|
return a.data.compareTo(b.data);
|
|
}
|
|
|
|
int _descendingSorter(
|
|
FuzzAstVisitorSorterChunk a,
|
|
FuzzAstVisitorSorterChunk b,
|
|
) {
|
|
// Only sort layers differently internally.
|
|
if (a.layer < b.layer) return -1;
|
|
if (a.layer > b.layer) return 1;
|
|
return b.data.compareTo(a.data);
|
|
}
|
|
|
|
List<FuzzAstVisitorSorterChunk> chunks = [];
|
|
Token? metadataStart;
|
|
Token? metadataEndInclusive;
|
|
int layer = 0;
|
|
FuzzSorterState? state = null;
|
|
|
|
/// If there's any LanguageVersionToken in the comment preceding the given
|
|
/// token add it as a separate chunk to keep it in place.
|
|
void _chunkOutLanguageVersionComment(Token fromToken) {
|
|
Token comment = fromToken.precedingComments!;
|
|
bool hasLanguageVersion = comment is LanguageVersionToken;
|
|
while (comment.next != null) {
|
|
comment = comment.next!;
|
|
hasLanguageVersion |= comment is LanguageVersionToken;
|
|
}
|
|
if (hasLanguageVersion) {
|
|
layer++;
|
|
chunks.add(
|
|
new FuzzAstVisitorSorterChunk(
|
|
FuzzOriginalType.LanguageVersion,
|
|
asString.substring(
|
|
fromToken.precedingComments!.charOffset,
|
|
comment.charEnd,
|
|
),
|
|
null,
|
|
layer,
|
|
),
|
|
);
|
|
layer++;
|
|
}
|
|
}
|
|
|
|
void handleData(
|
|
FuzzOriginalType originalType,
|
|
FuzzSorterState thisState,
|
|
Token startInclusive,
|
|
Token endInclusive,
|
|
) {
|
|
// Non-sortable things always gets a new layer.
|
|
if (state != thisState || thisState == FuzzSorterState.nonSortable) {
|
|
state = thisState;
|
|
layer++;
|
|
}
|
|
|
|
// "Chunk out" any language version at the top, i.e. if there are no other
|
|
// chunks and there is a metadata, any language version chunk on the
|
|
// metadata will be "chunked out". If there is no metadata, any language
|
|
// version on the non-metadata will be "chunked out".
|
|
// Note that if there is metadata and there is a language version on the
|
|
// non-metadata it will not be chunked out as it's in an illegal place
|
|
// anyway, so possibly allowing it to be sorted (and put in another place)
|
|
// won't make it more or less illegal.
|
|
if (metadataStart != null &&
|
|
metadataStart!.precedingComments != null &&
|
|
chunks.isEmpty) {
|
|
_chunkOutLanguageVersionComment(metadataStart!);
|
|
} else if (metadataStart == null &&
|
|
startInclusive.precedingComments != null &&
|
|
chunks.isEmpty) {
|
|
_chunkOutLanguageVersionComment(startInclusive);
|
|
}
|
|
|
|
String? metadata;
|
|
if (metadataStart != null || metadataEndInclusive != null) {
|
|
metadata = asString.substring(
|
|
metadataStart!.charOffset,
|
|
metadataEndInclusive!.charEnd,
|
|
);
|
|
}
|
|
chunks.add(
|
|
new FuzzAstVisitorSorterChunk(
|
|
originalType,
|
|
asString.substring(startInclusive.charOffset, endInclusive.charEnd),
|
|
metadata,
|
|
layer,
|
|
),
|
|
);
|
|
metadataStart = null;
|
|
metadataEndInclusive = null;
|
|
}
|
|
|
|
@override
|
|
void visitExportEnd(ExportEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.Export,
|
|
FuzzSorterState.importExportSortable,
|
|
node.exportKeyword,
|
|
node.semicolon,
|
|
);
|
|
}
|
|
|
|
ImportEnd? _importEndNode;
|
|
|
|
@override
|
|
void visitImportEnd(ImportEnd node) {
|
|
if (node.semicolon != null) {
|
|
handleData(
|
|
FuzzOriginalType.Import,
|
|
FuzzSorterState.importExportSortable,
|
|
node.importKeyword,
|
|
node.semicolon!,
|
|
);
|
|
} else {
|
|
_importEndNode = node;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitRecoverImportHandle(RecoverImportHandle node) {
|
|
if (node.semicolon != null && _importEndNode != null) {
|
|
handleData(
|
|
FuzzOriginalType.Import,
|
|
FuzzSorterState.importExportSortable,
|
|
_importEndNode!.importKeyword,
|
|
node.semicolon!,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitClassDeclarationEnd(ClassDeclarationEnd node) {
|
|
// TODO(jensj): Possibly sort stuff inside of this too.
|
|
handleData(
|
|
FuzzOriginalType.Class,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitEnumDeclarationEnd(EnumDeclarationEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.Enum,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionDeclarationEnd(ExtensionDeclarationEnd node) {
|
|
// TODO(jensj): Possibly sort stuff inside of this too.
|
|
handleData(
|
|
FuzzOriginalType.Extension,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionTypeDeclarationEnd(ExtensionTypeDeclarationEnd node) {
|
|
// TODO(jensj): Possibly sort stuff inside of this too.
|
|
handleData(
|
|
FuzzOriginalType.ExtensionTypeDeclaration,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitLibraryNameEnd(LibraryNameEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.LibraryName,
|
|
FuzzSorterState.nonSortable,
|
|
node.libraryKeyword,
|
|
node.semicolon,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitMetadataEnd(MetadataEnd node) {
|
|
if (metadataStart == null) {
|
|
metadataStart = node.beginToken;
|
|
metadataEndInclusive = node.endToken;
|
|
} else {
|
|
metadataEndInclusive = node.endToken;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitMixinDeclarationEnd(MixinDeclarationEnd node) {
|
|
// TODO(jensj): Possibly sort stuff inside of this too.
|
|
handleData(
|
|
FuzzOriginalType.Mixin,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitNamedMixinApplicationEnd(NamedMixinApplicationEnd node) {
|
|
// TODO(jensj): Possibly sort stuff inside of this too.
|
|
handleData(
|
|
FuzzOriginalType.Mixin,
|
|
FuzzSorterState.sortableRest,
|
|
node.begin,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitPartEnd(PartEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.Part,
|
|
FuzzSorterState.nonSortable,
|
|
node.partKeyword,
|
|
node.semicolon,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitPartOfEnd(PartOfEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.PartOf,
|
|
FuzzSorterState.nonSortable,
|
|
node.partKeyword,
|
|
node.semicolon,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitTopLevelFieldsEnd(TopLevelFieldsEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.TopLevelFields,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitTopLevelMethodEnd(TopLevelMethodEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.TopLevelMethod,
|
|
FuzzSorterState.sortableRest,
|
|
node.beginToken,
|
|
node.endToken,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void visitTypedefEnd(TypedefEnd node) {
|
|
handleData(
|
|
FuzzOriginalType.TypeDef,
|
|
FuzzSorterState.sortableRest,
|
|
node.typedefKeyword,
|
|
node.endToken,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SemiForceExperimentalInvalidationIncrementalCompiler
|
|
extends IncrementalCompiler {
|
|
@override
|
|
final TestRecorderForTesting recorderForTesting =
|
|
new TestRecorderForTesting();
|
|
|
|
@override
|
|
bool skipExperimentalInvalidationChecksForTesting = true;
|
|
|
|
SemiForceExperimentalInvalidationIncrementalCompiler.fromComponent(
|
|
CompilerContext context,
|
|
Component? componentToInitializeFrom,
|
|
) : super.fromComponent(context, componentToInitializeFrom);
|
|
}
|
|
|
|
class _FakeFileSystem extends FileSystem {
|
|
bool redirectAndRecord = true;
|
|
final Map<Uri, Uint8List?> data = {};
|
|
final FileSystem fs;
|
|
_FakeFileSystem(this.fs);
|
|
|
|
@override
|
|
FileSystemEntity entityForUri(Uri uri) {
|
|
return new _FakeFileSystemEntity(this, uri);
|
|
}
|
|
}
|
|
|
|
class _FakeFileSystemEntity extends FileSystemEntity {
|
|
final _FakeFileSystem fs;
|
|
@override
|
|
final Uri uri;
|
|
_FakeFileSystemEntity(this.fs, this.uri);
|
|
|
|
Future<void> _ensureCachedIfOk() async {
|
|
if (fs.data.containsKey(uri)) return;
|
|
if (!fs.redirectAndRecord) {
|
|
throw "Asked for file in non-recording mode that wasn't known";
|
|
}
|
|
|
|
FileSystemEntity f = fs.fs.entityForUri(uri);
|
|
if (!await f.exists()) {
|
|
fs.data[uri] = null;
|
|
return;
|
|
}
|
|
fs.data[uri] = await f.readAsBytes();
|
|
}
|
|
|
|
@override
|
|
Future<bool> exists() async {
|
|
await _ensureCachedIfOk();
|
|
Uint8List? data = fs.data[uri];
|
|
if (data == null) return false;
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> existsAsyncIfPossible() => exists();
|
|
|
|
@override
|
|
Future<Uint8List> readAsBytes() async {
|
|
await _ensureCachedIfOk();
|
|
Uint8List? data = fs.data[uri];
|
|
if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
|
|
return data;
|
|
}
|
|
|
|
@override
|
|
Future<Uint8List> readAsBytesAsyncIfPossible() => readAsBytes();
|
|
|
|
@override
|
|
Future<String> readAsString() async {
|
|
await _ensureCachedIfOk();
|
|
Uint8List? data = fs.data[uri];
|
|
if (data == null) throw new FileSystemException(uri, "File doesn't exist.");
|
|
return utf8.decode(data);
|
|
}
|
|
}
|
|
|
|
Target createTarget(FolderOptions folderOptions, FastaContext context) {
|
|
TestTargetFlags targetFlags = new TestTargetFlags(
|
|
forceLateLoweringsForTesting: folderOptions.forceLateLowerings,
|
|
forceLateLoweringSentinelForTesting:
|
|
folderOptions.forceLateLoweringSentinel,
|
|
forceStaticFieldLoweringForTesting: folderOptions.forceStaticFieldLowering,
|
|
forceNoExplicitGetterCallsForTesting:
|
|
folderOptions.forceNoExplicitGetterCalls,
|
|
forceConstructorTearOffLoweringForTesting:
|
|
folderOptions.forceConstructorTearOffLowering,
|
|
isClosureContextLoweringEnabled: folderOptions.forceClosureContextLowering,
|
|
supportedDartLibraries: {'_supported.by.target'},
|
|
unsupportedDartLibraries: {'unsupported.by.target'},
|
|
);
|
|
Target target;
|
|
switch (folderOptions.target) {
|
|
case "vm":
|
|
target = new TestVmTarget(targetFlags);
|
|
break;
|
|
case "none":
|
|
target = new TestTargetWrapper(new NoneTarget(targetFlags), targetFlags);
|
|
break;
|
|
case "dart2js":
|
|
target = new TestDart2jsTarget(
|
|
'dart2js',
|
|
targetFlags,
|
|
options: dart2jsOptions.CompilerOptions.parse(
|
|
folderOptions.defines?.values.toList() ?? [],
|
|
),
|
|
);
|
|
break;
|
|
case "dartdevc":
|
|
target = new TestDevCompilerTarget(targetFlags);
|
|
break;
|
|
case "wasm":
|
|
target = new TestWasmTarget(targetFlags);
|
|
break;
|
|
default:
|
|
throw new ArgumentError(
|
|
"Unsupported test target '${folderOptions.target}'.",
|
|
);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
Set<Uri> createUserLibrariesImportUriSet(
|
|
Component component,
|
|
UriTranslator uriTranslator, {
|
|
Set<Library> excludedLibraries = const {},
|
|
}) {
|
|
Set<Uri> knownUris = component.libraries
|
|
.map((Library library) => library.importUri)
|
|
.toSet();
|
|
Set<Uri> userLibraries = component.libraries
|
|
.where(
|
|
(Library library) =>
|
|
!library.importUri.isScheme('dart') &&
|
|
!library.importUri.isScheme('package') &&
|
|
!excludedLibraries.contains(library),
|
|
)
|
|
.map((Library library) => library.importUri)
|
|
.toSet();
|
|
// Mark custom "dart:" libraries defined in the test-specific libraries.json
|
|
// file as user libraries.
|
|
userLibraries.addAll(
|
|
uriTranslator.dartLibraries.allLibraries.map(
|
|
(LibraryInfo info) => info.importUri,
|
|
),
|
|
);
|
|
return userLibraries.intersection(knownUris);
|
|
}
|
|
|
|
enum CompileMode {
|
|
/// Compiles only the outline of the test and its linked dependencies.
|
|
outline,
|
|
|
|
/// Fully compiles the test but compiles only the outline of its linked
|
|
/// dependencies.
|
|
///
|
|
/// This mimics how modular compilation is performed with dartdevc.
|
|
modular,
|
|
|
|
/// Fully compiles the test and its linked dependencies.
|
|
full,
|
|
}
|
|
|
|
CompileMode compileModeFromName(String? name) {
|
|
for (CompileMode mode in CompileMode.values) {
|
|
if (name == mode.name) {
|
|
return mode;
|
|
}
|
|
}
|
|
return CompileMode.outline;
|
|
}
|
|
|
|
class Outline extends Step<TestDescription, ComponentResult, FastaContext> {
|
|
final CompileMode compileMode;
|
|
|
|
const Outline(this.compileMode, {this.updateComments = false});
|
|
|
|
final bool updateComments;
|
|
|
|
@override
|
|
String get name {
|
|
switch (compileMode) {
|
|
case CompileMode.outline:
|
|
return "outline";
|
|
case CompileMode.modular:
|
|
return "modular";
|
|
case CompileMode.full:
|
|
return "compile";
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
TestDescription description,
|
|
FastaContext context,
|
|
) async {
|
|
CompilationSetup compilationSetup = createCompilationSetup(
|
|
description,
|
|
context,
|
|
);
|
|
|
|
if (compilationSetup.testOptions.linkDependencies.isNotEmpty &&
|
|
compilationSetup.testOptions.component == null) {
|
|
// Compile linked dependency.
|
|
ProcessedOptions linkOptions = compilationSetup.options;
|
|
await CompilerContext.runWithOptions(linkOptions, (
|
|
CompilerContext c,
|
|
) async {
|
|
Target backendTarget = linkOptions.target;
|
|
if (backendTarget is TestTarget) {
|
|
backendTarget.performModularTransformations = true;
|
|
}
|
|
linkOptions.inputs.clear();
|
|
linkOptions.inputs.addAll(
|
|
compilationSetup.testOptions.linkDependencies.toList(),
|
|
);
|
|
InternalCompilerResult internalCompilerResult =
|
|
await generateKernelInternal(
|
|
c,
|
|
buildSummary: compileMode != CompileMode.full,
|
|
serializeIfBuildingSummary: false,
|
|
buildComponent: compileMode == CompileMode.full,
|
|
includeHierarchyAndCoreTypes: true,
|
|
retainDataForTesting: true,
|
|
allowVerificationErrorForTesting: true,
|
|
);
|
|
Component p = internalCompilerResult.component!;
|
|
internalCompilerResult.kernelTargetForTesting!;
|
|
if (backendTarget is TestTarget) {
|
|
backendTarget.performModularTransformations = false;
|
|
}
|
|
if (compilationSetup.testOptions.errors != null) {
|
|
compilationSetup.errors.addAll(compilationSetup.testOptions.errors!);
|
|
}
|
|
|
|
compilationSetup.testOptions.component = p;
|
|
List<Library> keepLibraries = <Library>[];
|
|
for (Library lib in p.libraries) {
|
|
if (compilationSetup.testOptions.linkDependencies.contains(
|
|
lib.importUri,
|
|
)) {
|
|
keepLibraries.add(lib);
|
|
}
|
|
}
|
|
p.libraries.clear();
|
|
p.libraries.addAll(keepLibraries);
|
|
compilationSetup.testOptions.errors = compilationSetup.errors.toList();
|
|
compilationSetup.errors.clear();
|
|
});
|
|
}
|
|
|
|
try {
|
|
return await CompilerContext.runWithOptions(compilationSetup.options, (
|
|
CompilerContext c,
|
|
) async {
|
|
Component? alsoAppend = compilationSetup.testOptions.component;
|
|
if (description.uri.pathSegments.last.endsWith(".no_link.dart")) {
|
|
alsoAppend = null;
|
|
}
|
|
|
|
Set<Library>? excludedLibraries;
|
|
if (compileMode == CompileMode.modular) {
|
|
excludedLibraries = alsoAppend?.libraries.toSet();
|
|
}
|
|
excludedLibraries ??= const {};
|
|
|
|
Component p;
|
|
KernelTarget sourceTarget;
|
|
compilationSetup.options.inputs.clear();
|
|
compilationSetup.options.inputs.add(description.uri);
|
|
InternalCompilerResult internalCompilerResult =
|
|
await generateKernelInternal(
|
|
c,
|
|
buildSummary: compileMode == CompileMode.outline,
|
|
serializeIfBuildingSummary: false,
|
|
buildComponent: compileMode != CompileMode.outline,
|
|
retainDataForTesting: true,
|
|
additionalDillModulesForTesting: alsoAppend != null
|
|
? [alsoAppend]
|
|
: null,
|
|
allowVerificationErrorForTesting: true,
|
|
);
|
|
p = internalCompilerResult.component!;
|
|
sourceTarget = internalCompilerResult.kernelTargetForTesting!;
|
|
|
|
Set<Uri> userLibraries = createUserLibrariesImportUriSet(
|
|
p,
|
|
sourceTarget.uriTranslator,
|
|
excludedLibraries: excludedLibraries,
|
|
);
|
|
|
|
return pass(
|
|
new ComponentResult(
|
|
description,
|
|
p,
|
|
userLibraries,
|
|
compilationSetup,
|
|
sourceTarget,
|
|
),
|
|
);
|
|
});
|
|
} catch (e, s) {
|
|
return reportCrash(e, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Transform extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
const Transform();
|
|
|
|
@override
|
|
String get name => "transform component";
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
return await result.sourceTarget.context.runInContext((_) async {
|
|
Component component = result.component;
|
|
KernelTarget sourceTarget = result.sourceTarget;
|
|
Target backendTarget = sourceTarget.backendTarget;
|
|
if (backendTarget is TestTarget) {
|
|
backendTarget.performModularTransformations = true;
|
|
}
|
|
try {
|
|
sourceTarget.runBuildTransformations();
|
|
} finally {
|
|
if (backendTarget is TestTarget) {
|
|
backendTarget.performModularTransformations = false;
|
|
}
|
|
}
|
|
if (backendTarget is TestTarget &&
|
|
backendTarget.hasGlobalTransformation) {
|
|
component = backendTarget.performGlobalTransformations(
|
|
sourceTarget,
|
|
component,
|
|
);
|
|
// Clear the currently cached platform since the global transformation
|
|
// might have modified it.
|
|
context.clearPlatformCache(backendTarget);
|
|
}
|
|
|
|
return pass(
|
|
new ComponentResult(
|
|
result.description,
|
|
component,
|
|
result.userLibraries,
|
|
result.compilationSetup,
|
|
sourceTarget,
|
|
),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
class Verify extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
final VerificationStage stage;
|
|
|
|
const Verify(this.stage);
|
|
|
|
@override
|
|
String get name => "verify";
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) async {
|
|
FolderOptions folderOptions = context.suiteFolderOptions
|
|
.computeFolderOptions(result.description);
|
|
|
|
if (folderOptions.noVerify) {
|
|
return pass(result);
|
|
}
|
|
|
|
Component component = result.component;
|
|
StringBuffer messages = new StringBuffer();
|
|
void Function(CfeDiagnosticMessage)? previousOnDiagnostics =
|
|
result.options.rawOptionsForTesting.onDiagnostic;
|
|
result.options.rawOptionsForTesting.onDiagnostic =
|
|
(CfeDiagnosticMessage message) {
|
|
if (messages.isNotEmpty) {
|
|
messages.write("\n");
|
|
}
|
|
messages.writeAll(message.plainTextFormatted, "\n");
|
|
};
|
|
|
|
Result<ComponentResult> verifyResult = await result.sourceTarget.context
|
|
.runInContext((compilerContext) async {
|
|
compilerContext.uriToSource.addAll(component.uriToSource);
|
|
List<LocatedMessage> verificationErrors = verifyComponent(
|
|
compilerContext,
|
|
stage,
|
|
component,
|
|
skipPlatform: true,
|
|
);
|
|
assert(verificationErrors.isEmpty || messages.isNotEmpty);
|
|
if (messages.isEmpty) {
|
|
return pass(result);
|
|
} else {
|
|
return new Result<ComponentResult>(
|
|
null,
|
|
context.expectationSet["VerificationError"],
|
|
"$messages",
|
|
);
|
|
}
|
|
});
|
|
result.options.rawOptionsForTesting.onDiagnostic = previousOnDiagnostics;
|
|
return verifyResult;
|
|
}
|
|
}
|
|
|
|
mixin TestTarget on Target {
|
|
bool performModularTransformations = false;
|
|
|
|
@override
|
|
void performModularTransformationsOnLibraries(
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
List<Library> libraries,
|
|
Map<String, String>? environmentDefines,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex? referenceFromIndex, {
|
|
void Function(String msg)? logger,
|
|
ChangedStructureNotifier? changedStructureNotifier,
|
|
}) {
|
|
if (performModularTransformations) {
|
|
super.performModularTransformationsOnLibraries(
|
|
component,
|
|
coreTypes,
|
|
hierarchy,
|
|
libraries,
|
|
environmentDefines,
|
|
diagnosticReporter,
|
|
referenceFromIndex,
|
|
logger: logger,
|
|
);
|
|
}
|
|
}
|
|
|
|
bool get hasGlobalTransformation => false;
|
|
|
|
Component performGlobalTransformations(
|
|
KernelTarget kernelTarget,
|
|
Component component,
|
|
) => component;
|
|
}
|
|
|
|
class TestVmTarget extends VmTarget with TestTarget, TestTargetMixin {
|
|
@override
|
|
final TestTargetFlags flags;
|
|
|
|
TestVmTarget(this.flags) : super(flags);
|
|
}
|
|
|
|
class TestWasmTarget extends WasmTarget with TestTarget, TestTargetMixin {
|
|
@override
|
|
final TestTargetFlags flags;
|
|
|
|
TestWasmTarget(this.flags);
|
|
}
|
|
|
|
class EnsureNoErrors
|
|
extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
const EnsureNoErrors();
|
|
|
|
@override
|
|
String get name => "check errors";
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) {
|
|
List<Iterable<String>> errors = result.compilationSetup.errors;
|
|
return new Future.value(
|
|
errors.isEmpty
|
|
? pass(result)
|
|
: fail(
|
|
result,
|
|
"Unexpected errors:\n"
|
|
"${errors.map((error) => error.join('\n')).join('\n\n')}",
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MatchHierarchy
|
|
extends Step<ComponentResult, ComponentResult, FastaContext> {
|
|
const MatchHierarchy();
|
|
|
|
@override
|
|
String get name => "check hierarchy";
|
|
|
|
@override
|
|
Future<Result<ComponentResult>> run(
|
|
ComponentResult result,
|
|
FastaContext context,
|
|
) {
|
|
Component component = result.component;
|
|
Uri uri = component.uriToSource.keys.firstWhere(
|
|
(uri) => uri.isScheme("file"),
|
|
);
|
|
KernelTarget target = result.sourceTarget;
|
|
ClassHierarchyBuilder hierarchy = target.loader.hierarchyBuilder;
|
|
StringBuffer sb = new StringBuffer();
|
|
for (ClassHierarchyNode node in hierarchy.classNodes.values) {
|
|
sb.writeln(node);
|
|
}
|
|
return context.match<ComponentResult>(
|
|
".hierarchy.expect",
|
|
"$sb",
|
|
uri,
|
|
result,
|
|
);
|
|
}
|
|
}
|
|
|
|
class NoneConstantsBackendWithJs extends NoneConstantsBackend {
|
|
const NoneConstantsBackendWithJs({required bool supportsUnevaluatedConstants})
|
|
: super(supportsUnevaluatedConstants: supportsUnevaluatedConstants);
|
|
|
|
@override
|
|
NumberSemantics get numberSemantics => NumberSemantics.js;
|
|
}
|
|
|
|
class TestDart2jsTarget extends Dart2jsTarget with TestTarget, TestTargetMixin {
|
|
@override
|
|
final TestTargetFlags flags;
|
|
|
|
TestDart2jsTarget(
|
|
String name,
|
|
this.flags, {
|
|
dart2jsOptions.CompilerOptions? options,
|
|
}) : super(name, flags, options: options);
|
|
}
|
|
|
|
class TestDevCompilerTarget extends DevCompilerTarget
|
|
with TestTarget, TestTargetMixin {
|
|
@override
|
|
final TestTargetFlags flags;
|
|
|
|
TestDevCompilerTarget(this.flags) : super(flags);
|
|
}
|
|
|
|
class _LibraryFinder extends RecursiveVisitor {
|
|
Set<Library> allLibraries = {};
|
|
|
|
@override
|
|
void visitLibrary(Library node) {
|
|
allLibraries.add(node);
|
|
super.visitLibrary(node);
|
|
}
|
|
|
|
@override
|
|
void defaultMemberReference(Member node) {
|
|
try {
|
|
// This call sometimes fail:
|
|
// node.enclosingLibrary;
|
|
// TODO(jensj): Figure out why it fails.
|
|
// It happens - currently - on these tests:
|
|
// strong/macros/augment_concrete
|
|
// strong/macros/extend_augmented
|
|
// strong/macros/multiple_augment_class
|
|
TreeNode? parent = node.parent;
|
|
while (parent != null && parent is! Library) {
|
|
parent = parent.parent;
|
|
}
|
|
if (parent is Library) {
|
|
allLibraries.add(parent);
|
|
}
|
|
} catch (e) {
|
|
throw "Error for $node with parent ${node.parent}: $e";
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitClassReference(Class node) {
|
|
allLibraries.add(node.enclosingLibrary);
|
|
}
|
|
|
|
@override
|
|
void visitTypedefReference(Typedef node) {
|
|
allLibraries.add(node.enclosingLibrary);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionReference(Extension node) {
|
|
allLibraries.add(node.enclosingLibrary);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionTypeDeclarationReference(ExtensionTypeDeclaration node) {
|
|
allLibraries.add(node.enclosingLibrary);
|
|
}
|
|
}
|