| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:async' show Completer; | |
| import 'dart:convert' show JsonEncoder; | |
| import 'dart:typed_data'; | |
| import 'package:_fe_analyzer_shared/src/parser/experimental_features.dart' | |
| show ExperimentalFeatures, ExperimentalFeaturesExtension; | |
| import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart' | |
| show ScannerConfiguration; | |
| import 'package:_fe_analyzer_shared/src/scanner/token.dart' | |
| show LanguageVersionToken; | |
| import 'package:front_end/src/api_prototype/terminal_color_support.dart' | |
| as colors | |
| show enableColors, enableColorsRaw; | |
| import 'package:kernel/binary/ast_from_binary.dart' | |
| show | |
| BinaryBuilderWithMetadata, | |
| CompilationModeError, | |
| InvalidKernelSdkVersionError, | |
| InvalidKernelVersionError, | |
| SubComponentView; | |
| import 'package:kernel/canonical_name.dart' | |
| show CanonicalNameError, CanonicalNameSdkError; | |
| import 'package:kernel/class_hierarchy.dart' | |
| show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy; | |
| import 'package:kernel/kernel.dart' | |
| show | |
| Class, | |
| Component, | |
| ConstantExpression, | |
| DartType, | |
| DynamicType, | |
| Expression, | |
| ExtensionType, | |
| Field, | |
| FunctionNode, | |
| InterfaceType, | |
| Library, | |
| LibraryDependency, | |
| LibraryPart, | |
| Name, | |
| NamedNode, | |
| NamedParameter, | |
| Node, | |
| Nullability, | |
| PositionalParameter, | |
| PositionalParameterList, | |
| Procedure, | |
| ProcedureKind, | |
| Reference, | |
| ReturnStatement, | |
| Source, | |
| Supertype, | |
| TreeNode, | |
| TypeParameter, | |
| TypeParameterList, | |
| TypeParameterType, | |
| Variable, | |
| Version, | |
| VisitorDefault, | |
| VisitorVoidMixin, | |
| loadComponentSourceFromBytes; | |
| import 'package:kernel/kernel.dart' as kernel show Combinator; | |
| import 'package:kernel/reference_from_index.dart'; | |
| import 'package:kernel/target/changed_structure_notifier.dart' | |
| show ChangedStructureNotifier; | |
| import 'package:kernel/type_algebra.dart' show Substitution; | |
| import 'package:kernel/util/graph.dart' as kernelGraph; | |
| import 'package:package_config/package_config.dart' | |
| show Package, PackageConfig, LanguageVersion; | |
| import '../api_prototype/experimental_flags.dart'; | |
| import '../api_prototype/file_system.dart' show FileSystem, FileSystemEntity; | |
| import '../api_prototype/incremental_kernel_generator.dart' | |
| show | |
| IncrementalCompilerResult, | |
| IncrementalKernelGenerator, | |
| isLegalIdentifier; | |
| import '../api_prototype/language_version.dart' | |
| show Version, scanBytesForLanguageVersionAnnotation; | |
| import '../api_prototype/lowering_predicates.dart' | |
| show | |
| isExtensionThisName, | |
| syntheticThisName, | |
| hasUnnamedExtensionNamePrefix, | |
| extractQualifiedNameFromExtensionMethodName; | |
| import '../api_prototype/memory_file_system.dart' show MemoryFileSystem; | |
| import '../builder/builder.dart' show Builder; | |
| import '../builder/compilation_unit.dart' | |
| show CompilationUnit, SourceCompilationUnit; | |
| import '../builder/declaration_builders.dart' | |
| show ClassBuilder, ExtensionBuilder, ExtensionTypeDeclarationBuilder; | |
| import '../builder/formal_parameter_builder.dart'; | |
| import '../builder/library_builder.dart' show LibraryBuilder; | |
| import '../builder/member_builder.dart' show MemberBuilder; | |
| import '../codes/diagnostic.dart' as diag; | |
| import '../dill/dill_class_builder.dart' show DillClassBuilder; | |
| import '../dill/dill_library_builder.dart' show DillLibraryBuilder; | |
| import '../dill/dill_loader.dart' show DillLoader; | |
| import '../dill/dill_target.dart' show DillTarget; | |
| import '../kernel/benchmarker.dart' show BenchmarkPhases, Benchmarker; | |
| import '../kernel/dart_scope_calculator.dart' show DartScope, DartScopeBuilder2; | |
| import '../kernel/expression_compilation_data.dart'; | |
| import '../kernel/external_ast_helper.dart' as extern; | |
| import '../kernel/hierarchy/hierarchy_builder.dart' show ClassHierarchyBuilder; | |
| import '../kernel/internal_ast.dart' | |
| show | |
| InternalVariableGet, | |
| InternalVariableSet, | |
| InternalVariable, | |
| InternalConstVariable, | |
| InternalExpression, | |
| InternalInvalidExpression; | |
| import '../kernel/internal_ast_helper.dart' as intern; | |
| import '../kernel/kernel_target.dart' show BuildResult, KernelTarget; | |
| import '../source/check_helper.dart'; | |
| import '../source/source_compilation_unit.dart' show SourceCompilationUnitImpl; | |
| import '../source/source_library_builder.dart' | |
| show ImplicitLanguageVersion, SourceLibraryBuilder; | |
| import '../source/source_loader.dart'; | |
| import '../type_inference/inference_results.dart' | |
| show ExpressionInferenceResult; | |
| import '../type_inference/object_access_target.dart' | |
| show | |
| ObjectAccessTarget, | |
| ObjectAccessTargetKind, | |
| ExpressionEvaluationParameterTarget; | |
| import '../util/error_reporter_file_copier.dart' show saveAsGzip; | |
| import '../util/experiment_environment_getter.dart' | |
| show | |
| enableIncrementalCompilerBenchmarking, | |
| enableIncrementalCompilerDepsScanAndLoad, | |
| getExperimentEnvironment, | |
| getEnvironmentValue, | |
| pathIncrementalCompilerDepsScanAndLoad; | |
| import '../util/expression_evaluation_helpers.dart' | |
| show ExpressionEvaluationHelper, OverwrittenInterfaceMember; | |
| import '../util/textual_outline.dart' show textualOutline; | |
| import 'builder_graph.dart' show BuilderGraph; | |
| import 'combinator.dart' show CombinatorBuilder; | |
| import 'compiler_context.dart' show CompilerContext; | |
| import 'hybrid_file_system.dart' show HybridFileSystem; | |
| import 'incremental_compiler_cache_impl.dart' show IncrementalCompilerCacheImpl; | |
| import 'incremental_compiler_utils.dart' show MD5Ish; | |
| import 'incremental_serializer.dart' show IncrementalSerializer; | |
| import 'library_graph.dart' show LibraryGraph; | |
| import 'loader.dart' show Loader; | |
| import 'lookup_result.dart' show LookupResult; | |
| import 'messages.dart' | |
| show DiagnosticMessageFromJson, Message, ProblemReporting; | |
| import 'name_space.dart' show NameSpace, areNameSpacesEquivalent; | |
| import 'processed_options.dart' show ProcessedOptions; | |
| import 'ticker.dart' show Ticker; | |
| import 'uri_translator.dart' show UriTranslator; | |
| import 'uris.dart' show getPartImportUri; | |
| final Uri dartFfiUri = Uri.parse("dart:ffi"); | |
| class IncrementalCompiler implements IncrementalKernelGenerator { | |
| final CompilerContext context; | |
| final Ticker _ticker; | |
| final bool _resetTicker; | |
| final bool outlineOnly; | |
| Set<Uri?> _invalidatedUris = new Set<Uri?>(); | |
| bool _invalidatedBecauseOfPackageUpdate = false; | |
| DillTarget? _dillLoadedData; | |
| List<DillLibraryBuilder>? _platformBuilders; | |
| // Coverage-ignore(suite): Not run. | |
| List<DillLibraryBuilder>? get platformBuildersForTesting => _platformBuilders; | |
| Map<Uri, DillLibraryBuilder>? _userBuilders; | |
| // Coverage-ignore(suite): Not run. | |
| Map<Uri, DillLibraryBuilder>? get userBuildersForTesting => _userBuilders; | |
| final _InitializationStrategy _initializationStrategy; | |
| Uri? _previousPackagesUri; | |
| Map<String, Package>? _previousPackagesMap; | |
| Map<String, Package>? _currentPackagesMap; | |
| bool _hasToCheckPackageUris = false; | |
| final bool _initializedForExpressionCompilationOnly; | |
| bool _computeDeltaRunOnce = false; | |
| List<Component>? _modulesToLoad; | |
| final IncrementalSerializer? _incrementalSerializer; | |
| final _ComponentProblems _componentProblems = new _ComponentProblems(); | |
| IncrementalCompilerCache? incrementalCompilerCache; | |
| // This will be set if the right environment variable is set | |
| // (enableIncrementalCompilerBenchmarking). | |
| Benchmarker? _benchmarker; | |
| RecorderForTesting? get recorderForTesting => null; | |
| static final Uri debugExprUri = new Uri( | |
| scheme: "org-dartlang-debug", | |
| path: "synthetic_debug_expression", | |
| ); | |
| IncrementalKernelTarget? _lastGoodKernelTarget; | |
| Set<Library>? _previousSourceBuilders; | |
| /// Guard against multiple computeDelta calls at the same time (possibly | |
| /// caused by lacking awaits etc). | |
| Completer<dynamic>? _currentlyCompiling; | |
| new fromComponent( | |
| this.context, | |
| Component? _componentToInitializeFrom, [ | |
| bool? outlineOnly, | |
| this._incrementalSerializer, | |
| ]) : _ticker = context.options.ticker, | |
| _resetTicker = true, | |
| _previousPackagesUri = context.options.packagesUriRaw, | |
| _initializationStrategy = new _InitializationStrategy.fromComponent( | |
| _componentToInitializeFrom, | |
| ), | |
| this.outlineOnly = outlineOnly ?? false, | |
| this._initializedForExpressionCompilationOnly = false { | |
| _enableExperimentsBasedOnEnvironment(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| new( | |
| this.context, [ | |
| Uri? _initializeFromDillUri, | |
| bool? outlineOnly, | |
| this._incrementalSerializer, | |
| ]) : _ticker = context.options.ticker, | |
| _resetTicker = true, | |
| _previousPackagesUri = context.options.packagesUriRaw, | |
| _initializationStrategy = new _InitializationStrategy.fromUri( | |
| _initializeFromDillUri, | |
| ), | |
| this.outlineOnly = outlineOnly ?? false, | |
| this._initializedForExpressionCompilationOnly = false { | |
| _enableExperimentsBasedOnEnvironment(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| new experimentalCacheForTesting( | |
| this.context, { | |
| this._incrementalSerializer, | |
| required this.incrementalCompilerCache, | |
| }) : _ticker = context.options.ticker, | |
| _resetTicker = true, | |
| _previousPackagesUri = context.options.packagesUriRaw, | |
| _initializationStrategy = new _InitializationFromExperimentalCache(), | |
| this.outlineOnly = false, | |
| this._initializedForExpressionCompilationOnly = false { | |
| _enableExperimentsBasedOnEnvironment(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| new forExpressionCompilationOnly( | |
| this.context, | |
| Component? _componentToInitializeFrom, [ | |
| bool? resetTicker, | |
| ]) : _ticker = context.options.ticker, | |
| this._resetTicker = resetTicker ?? true, | |
| _previousPackagesUri = context.options.packagesUriRaw, | |
| _initializationStrategy = new _InitializationStrategy.fromComponent( | |
| _componentToInitializeFrom, | |
| ), | |
| this.outlineOnly = false, | |
| this._incrementalSerializer = null, | |
| this._initializedForExpressionCompilationOnly = true { | |
| _enableExperimentsBasedOnEnvironment(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| bool get initializedFromDillForTesting => | |
| _initializationStrategy.initializedFromDillForTesting; | |
| // Coverage-ignore(suite): Not run. | |
| bool get initializedIncrementalSerializerForTesting => | |
| _initializationStrategy.initializedIncrementalSerializerForTesting; | |
| // Coverage-ignore(suite): Not run. | |
| DillTarget? get dillTargetForTesting => _dillLoadedData; | |
| IncrementalKernelTarget? get kernelTargetForTesting => _lastGoodKernelTarget; | |
| // Coverage-ignore(suite): Not run. | |
| bool get skipExperimentalInvalidationChecksForTesting => false; | |
| // Coverage-ignore(suite): Not run. | |
| /// Returns the [Package] used for the package [packageName] in the most | |
| /// recent compilation. | |
| Package? getPackageForPackageName(String packageName) => | |
| _currentPackagesMap?[packageName]; | |
| // Coverage-ignore(suite): Not run. | |
| /// Returns the [Library] with the given [importUri] from the most recent | |
| /// compilation. | |
| Library? lookupLibrary(Uri importUri) => _lastGoodKernelTarget?.loader | |
| .lookupLoadedLibraryBuilder(importUri) | |
| ?.library; | |
| void _enableExperimentsBasedOnEnvironment({Set<String>? enabledExperiments}) { | |
| // Note that these are all experimental. Use at your own risk. | |
| enabledExperiments ??= getExperimentEnvironment(); | |
| if (enabledExperiments.contains(enableIncrementalCompilerBenchmarking)) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _benchmarker = new Benchmarker(); | |
| } | |
| } | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| void setExperimentalFeaturesForTesting(Set<String> features) { | |
| _enableExperimentsBasedOnEnvironment(enabledExperiments: features); | |
| } | |
| @override | |
| Future<IncrementalCompilerResult> computeDelta({ | |
| List<Uri>? entryPoints, | |
| bool fullComponent = false, | |
| bool trackNeededDillLibraries = false, | |
| }) async { | |
| while (_currentlyCompiling != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| await _currentlyCompiling!.future; | |
| } | |
| _currentlyCompiling = new Completer(); | |
| if (_resetTicker) { | |
| _ticker.reset(); | |
| } | |
| List<Uri>? entryPointsSavedForLaterOverwrite = entryPoints; | |
| return context.runInContext<IncrementalCompilerResult>(( | |
| CompilerContext c, | |
| ) async { | |
| if (!context.options.haveBeenValidated) { | |
| await context.options.validateOptions(errorOnMissingInput: false); | |
| } | |
| List<Uri> entryPoints = | |
| entryPointsSavedForLaterOverwrite ?? context.options.inputs; | |
| if (_computeDeltaRunOnce && _initializedForExpressionCompilationOnly) { | |
| throw new StateError( | |
| "Initialized for expression compilation: " | |
| "cannot do another general compile.", | |
| ); | |
| } | |
| _computeDeltaRunOnce = true; | |
| IncrementalKernelTarget? lastGoodKernelTarget = _lastGoodKernelTarget; | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.reset(); | |
| // Initial setup: Load platform, initialize from dill or component etc. | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_setupPackages); | |
| UriTranslator uriTranslator = await _setupPackagesAndUriTranslator(c); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_ensurePlatform); | |
| IncrementalCompilerData data = await _ensurePlatformAndInitialize( | |
| uriTranslator, | |
| c, | |
| entryPoints, | |
| ); | |
| // Figure out what to keep and what to throw away. | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_invalidate); | |
| Set<Uri?> invalidatedUris = this._invalidatedUris.toSet(); | |
| _invalidateNotKeptUserBuilders(invalidatedUris); | |
| ReusageResult? reusedResult = _computeReusedLibraries( | |
| lastGoodKernelTarget, | |
| _userBuilders, | |
| invalidatedUris, | |
| uriTranslator, | |
| ); | |
| // Experimental invalidation initialization (e.g. figure out if we can). | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_experimentalInvalidation); | |
| ExperimentalInvalidation? experimentalInvalidation = | |
| await _initializeExperimentalInvalidation( | |
| reusedResult, | |
| c, | |
| uriTranslator, | |
| context | |
| .options | |
| .target | |
| .incrementalCompilerIncludeMixinApplicationInvalidatedLibraries, | |
| ); | |
| recorderForTesting?.recordRebuildBodiesCount( | |
| experimentalInvalidation?.missingSources.length ?? 0, | |
| ); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_rewriteEntryPointsIfPart); | |
| _rewriteEntryPointsIfPart( | |
| entryPoints, | |
| reusedResult, | |
| experimentalInvalidation != null, | |
| ); | |
| // Cleanup: After (potentially) removing builders we have stuff to cleanup | |
| // to not leak, and we might need to re-create the dill target. | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_cleanup); | |
| _cleanupRemovedBuilders( | |
| lastGoodKernelTarget, | |
| reusedResult, | |
| uriTranslator, | |
| ); | |
| _recreateDillTargetIfPackageWasUpdated(uriTranslator, c); | |
| ClassHierarchy? hierarchy = lastGoodKernelTarget?.loader.hierarchy; | |
| _cleanupHierarchy(hierarchy, experimentalInvalidation, reusedResult); | |
| List<DillLibraryBuilder> reusedLibraries = reusedResult.reusedLibraries; | |
| reusedResult = null; | |
| if (lastGoodKernelTarget != null) { | |
| _ticker.logMs( | |
| "Decided to reuse ${reusedLibraries.length}" | |
| " of ${lastGoodKernelTarget.loader.loadedLibraryBuilders.length}" | |
| " libraries", | |
| ); | |
| } | |
| // For modular compilation we can be asked to load components and track | |
| // which libraries we actually use for the compilation. Set that up now. | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_loadEnsureLoadedComponents); | |
| _loadEnsureLoadedComponents(reusedLibraries); | |
| if (trackNeededDillLibraries) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _resetTrackingOfUsedLibraries(hierarchy); | |
| } | |
| // For each computeDelta call we create a new kernel target which needs | |
| // to be setup, and in the case of experimental invalidation some of the | |
| // builders needs to be patched up. | |
| IncrementalKernelTarget currentKernelTarget; | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_setupInLoop); | |
| currentKernelTarget = _setupNewKernelTarget( | |
| c, | |
| uriTranslator, | |
| hierarchy, | |
| reusedLibraries, | |
| experimentalInvalidation, | |
| entryPoints, | |
| _dillLoadedData!, | |
| ); | |
| Map<DillLibraryBuilder, CompilationUnit>? rebuildBodiesMap = | |
| _experimentalInvalidationCreateRebuildBodiesBuilders( | |
| currentKernelTarget, | |
| experimentalInvalidation, | |
| uriTranslator, | |
| ); | |
| entryPoints = currentKernelTarget.setEntryPoints(entryPoints); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_precompileMacros); | |
| await currentKernelTarget.computeNeededPrecompilations(); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase( | |
| BenchmarkPhases.incremental_experimentalInvalidationPatchUpScopes, | |
| ); | |
| _experimentalInvalidationPatchUpScopes( | |
| experimentalInvalidation, | |
| rebuildBodiesMap, | |
| ); | |
| rebuildBodiesMap = null; | |
| // Checkpoint: Build the actual outline. | |
| // Note that the [Component] is not the "full" component. | |
| // It is a component consisting of all newly compiled libraries and all | |
| // libraries loaded from .dill files or directly from components. | |
| // Technically, it's the combination of | |
| // `currentKernelTarget.loader.libraries` and | |
| // `_dillLoadedData.loader.libraries`. | |
| BuildResult buildResult = await currentKernelTarget.buildOutlines(); | |
| Component? componentWithDill = buildResult.component; | |
| if (!outlineOnly) { | |
| // Checkpoint: Build the actual bodies. | |
| buildResult = await currentKernelTarget.buildComponent( | |
| verify: c.options.verify, | |
| ); | |
| componentWithDill = buildResult.component; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| else if (componentWithDill != null) { | |
| context.options.target.performOutlineTransformations( | |
| componentWithDill, | |
| libraries: currentKernelTarget.loader.libraries, | |
| changedStructureNotifier: | |
| currentKernelTarget.changedStructureNotifier, | |
| ); | |
| } | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_hierarchy); | |
| hierarchy ??= currentKernelTarget.loader.hierarchy; | |
| if (currentKernelTarget.classHierarchyChanges != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| hierarchy.applyTreeChanges( | |
| [], | |
| [], | |
| currentKernelTarget.classHierarchyChanges!, | |
| ); | |
| } | |
| if (currentKernelTarget.classMemberChanges != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| hierarchy.applyMemberChanges( | |
| currentKernelTarget.classMemberChanges!, | |
| findDescendants: true, | |
| ); | |
| } | |
| recorderForTesting?.recordNonFullComponent(componentWithDill!); | |
| Set<Library>? neededDillLibraries; | |
| if (trackNeededDillLibraries) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _benchmarker?.enterPhase( | |
| BenchmarkPhases.incremental_performDillUsageTracking, | |
| ); | |
| // Perform actual dill usage tracking. | |
| neededDillLibraries = _performDillUsageTracking( | |
| currentKernelTarget, | |
| hierarchy, | |
| ); | |
| } | |
| // If we actually got a result we can throw away the | |
| // [lastGoodKernelTarget] and the list of invalidated uris. | |
| // TODO(jensj,johnniwinther): Given the code below, [componentWithDill] is | |
| // assumed always to be non-null. | |
| if (componentWithDill != null) { | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_releaseAncillaryResources); | |
| this._invalidatedUris.clear(); | |
| _hasToCheckPackageUris = false; | |
| lastGoodKernelTarget?.loader.releaseAncillaryResources(); | |
| lastGoodKernelTarget = null; | |
| } | |
| // Compute which libraries to output and which (previous) errors/warnings | |
| // we have to reissue. In the process do some cleanup too. | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_releaseAncillaryResources); | |
| List<Library> compiledLibraries = new List<Library>.of( | |
| currentKernelTarget.loader.libraries, | |
| ); | |
| Map<Uri, Source> uriToSource = componentWithDill!.uriToSource; | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase( | |
| BenchmarkPhases.incremental_experimentalCompilationPostCompilePatchup, | |
| ); | |
| _experimentalCompilationPostCompilePatchup( | |
| experimentalInvalidation, | |
| uriToSource, | |
| ); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase( | |
| BenchmarkPhases | |
| .incremental_calculateOutputLibrariesAndIssueLibraryProblems, | |
| ); | |
| Set<LibraryBuilder> cleanedUpBuilders = {}; | |
| List<Library> outputLibraries = | |
| _calculateOutputLibrariesAndIssueLibraryProblems( | |
| currentKernelTarget, | |
| data.component != null || fullComponent, | |
| compiledLibraries, | |
| entryPoints, | |
| reusedLibraries, | |
| hierarchy, | |
| uriTranslator, | |
| uriToSource, | |
| c, | |
| cleanedUpBuilders: cleanedUpBuilders, | |
| ); | |
| if (experimentalInvalidation != null && | |
| experimentalInvalidation.invalidatedMixinApplicationLibraries != | |
| null) { | |
| outputLibraries = { | |
| ...outputLibraries, | |
| ...experimentalInvalidation.invalidatedMixinApplicationLibraries!, | |
| }.toList(); | |
| } | |
| List<String> problemsAsJson = _componentProblems.reissueProblems( | |
| context, | |
| currentKernelTarget, | |
| componentWithDill, | |
| ); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase( | |
| BenchmarkPhases.incremental_convertSourceLibraryBuildersToDill, | |
| ); | |
| _previousSourceBuilders = _convertSourceLibraryBuildersToDill( | |
| currentKernelTarget, | |
| experimentalInvalidation, | |
| cleanedUpBuilders: cleanedUpBuilders, | |
| ); | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.enterPhase(BenchmarkPhases.incremental_end); | |
| experimentalInvalidation = null; | |
| // Output result. | |
| Procedure? mainMethod = componentWithDill.mainMethod; | |
| Component result = | |
| context.options.target.configureComponent( | |
| new Component( | |
| libraries: outputLibraries, | |
| uriToSource: uriToSource, | |
| ), | |
| ) | |
| ..setMainMethodAndMode(mainMethod?.reference, true) | |
| ..problemsAsJson = problemsAsJson; | |
| // Copy the metadata *just created*. This will likely not contain metadata | |
| // about other libraries. | |
| result.metadata.addAll(componentWithDill.metadata); | |
| if (outlineOnly) { | |
| // Coverage-ignore-block(suite): Not run. | |
| context.options.target.performOutlineComponentOperations(result); | |
| } | |
| // We're now done. Allow any waiting compile to start. | |
| Completer<dynamic> currentlyCompilingLocal = _currentlyCompiling!; | |
| _currentlyCompiling = null; | |
| currentlyCompilingLocal.complete(); | |
| _lastGoodKernelTarget = currentKernelTarget; | |
| _benchmarker | |
| // Coverage-ignore(suite): Not run. | |
| ?.stop(); | |
| if (_benchmarker != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| // Report. | |
| JsonEncoder encoder = new JsonEncoder.withIndent(" "); | |
| print(encoder.convert(_benchmarker)); | |
| } | |
| _ticker.logMs("Compile finished"); | |
| _cacheCompiledLibraries(compiledLibraries, result, uriTranslator); | |
| return new IncrementalCompilerResult( | |
| result, | |
| classHierarchy: currentKernelTarget.loader.hierarchy, | |
| coreTypes: currentKernelTarget.loader.coreTypes, | |
| neededDillLibraries: neededDillLibraries, | |
| loadedComponents: data.loadedModules ?? const [], | |
| ); | |
| }); | |
| } | |
| void _cacheCompiledLibraries( | |
| List<Library> compiledLibraries, | |
| Component fromComponent, | |
| UriTranslator uriTranslator, | |
| ) { | |
| IncrementalCompilerCache? incrementalCompilerCache = | |
| this.incrementalCompilerCache; | |
| if (incrementalCompilerCache == null) return; | |
| // Coverage-ignore-block(suite): Not run. | |
| // Component.problemsAsJson aren't (currently?) serialized (it wouldn't be | |
| // right to serialize it on every library so it would have to be serialized | |
| // on only some of them and then loading should deduplicate etc). | |
| if ((fromComponent.problemsAsJson?.length ?? 0) > 0) return; | |
| // Make sure we can serialize individual libraries. | |
| fromComponent.computeCanonicalNames(); | |
| TransitiveDependenciesCalculator transitiveDependenciesCalculator = | |
| new TransitiveDependenciesCalculator( | |
| _currentPackagesMap, | |
| fromComponent.uriToSource, | |
| compiledLibraries, | |
| ); | |
| List<String?>? transitiveDependencies = | |
| transitiveDependenciesCalculator.transitiveDependencies; | |
| if (transitiveDependencies == null) { | |
| return; | |
| } | |
| List<List<Library>> strongComponents = | |
| transitiveDependenciesCalculator.strongComponents; | |
| Set<Library> compiledLibrariesSet = compiledLibraries.toSet(); | |
| for ( | |
| int strongComponentIndex = 0; | |
| strongComponentIndex < strongComponents.length; | |
| strongComponentIndex++ | |
| ) { | |
| List<Library> component = strongComponents[strongComponentIndex]; | |
| // Only save new ones. | |
| int countInCompiledLibraries = 0; | |
| for (Library library in component) { | |
| if (compiledLibrariesSet.contains(library)) countInCompiledLibraries++; | |
| } | |
| if (countInCompiledLibraries == 0) continue; | |
| if (countInCompiledLibraries != component.length) { | |
| return; | |
| } | |
| String? transitiveDepsHash = transitiveDependencies[strongComponentIndex]; | |
| if (transitiveDepsHash == null) { | |
| continue; | |
| } | |
| incrementalCompilerCache.cacheLibraries( | |
| component, | |
| transitiveDepsHash, | |
| fromComponent, | |
| ); | |
| } | |
| // Make the component good again. | |
| fromComponent.adoptChildren(); | |
| _ticker.logMs("Cached dills"); | |
| } | |
| void _rewriteEntryPointsIfPart( | |
| List<Uri> entryPoints, | |
| ReusageResult reusedResult, | |
| bool doingAdvancedInvalidation, | |
| ) { | |
| for (int i = 0; i < entryPoints.length; i++) { | |
| Uri entryPoint = entryPoints[i]; | |
| LibraryBuilder? parent = reusedResult.partUriToParent[entryPoint]; | |
| if (parent == null) continue; | |
| // Only do the translation if we're doing advanced invalidation | |
| // (i.e. no outline change, i.e. if it was a part with a specific parent | |
| // it still is) or we reuse the parent (i.e. that library and its parts | |
| // were not changed). | |
| if (doingAdvancedInvalidation || | |
| // TODO(jensj): .contains on a list is O(n). | |
| // It will only be done for each entry point that's a part though, | |
| // i.e. most likely very rarely. | |
| reusedResult.reusedLibraries.contains(parent)) { | |
| entryPoints[i] = parent.importUri; | |
| } | |
| } | |
| } | |
| /// Convert every SourceLibraryBuilder to a DillLibraryBuilder. | |
| /// As we always do this, this will only be the new ones. | |
| /// | |
| /// If doing experimental invalidation that means that some of the old dill | |
| /// library builders might have links (via export scopes) to the | |
| /// source builders and they will thus be patched up here too. | |
| /// | |
| /// Returns the set of Libraries that now has new (dill) builders. | |
| Set<Library> _convertSourceLibraryBuildersToDill( | |
| IncrementalKernelTarget nextGoodKernelTarget, | |
| ExperimentalInvalidation? experimentalInvalidation, { | |
| required Set<LibraryBuilder> cleanedUpBuilders, | |
| }) { | |
| bool changed = false; | |
| Set<Library> newDillLibraryBuilders = new Set<Library>(); | |
| _userBuilders ??= {}; | |
| Map<LibraryBuilder, CompilationUnit>? convertedLibraries; | |
| for (SourceLibraryBuilder builder | |
| in nextGoodKernelTarget.loader.sourceLibraryBuilders) { | |
| if (cleanedUpBuilders.contains(builder)) { | |
| continue; | |
| } | |
| DillLibraryBuilder dillBuilder = _dillLoadedData!.loader.appendLibrary( | |
| builder.library, | |
| ); | |
| nextGoodKernelTarget.loader.registerLoadedDillLibraryBuilder(dillBuilder); | |
| _userBuilders![builder.importUri] = dillBuilder; | |
| newDillLibraryBuilders.add(builder.library); | |
| changed = true; | |
| if (experimentalInvalidation != null) { | |
| convertedLibraries ??= new Map<LibraryBuilder, CompilationUnit>(); | |
| convertedLibraries[builder] = dillBuilder.mainCompilationUnit; | |
| } | |
| } | |
| nextGoodKernelTarget.loader.clearSourceLibraryBuilders(); | |
| if (changed) { | |
| // We suppress finalization errors because they have already been | |
| // reported. | |
| _dillLoadedData!.buildOutlines(suppressFinalizationErrors: true); | |
| assert( | |
| _checkEquivalentScopes( | |
| nextGoodKernelTarget.loader, | |
| _dillLoadedData!.loader, | |
| ), | |
| ); | |
| if (experimentalInvalidation != null) { | |
| /// If doing experimental invalidation that means that some of the old | |
| /// dill library builders might have links (via export scopes) to the | |
| /// source builders. Patch that up. | |
| // Map from old library builder to name space of new content. | |
| Map<LibraryBuilder, NameSpace>? replacementNameSpaceMap = {}; | |
| _experimentalInvalidationFillReplacementMaps( | |
| convertedLibraries!, | |
| replacementNameSpaceMap, | |
| ); | |
| for (DillLibraryBuilder builder | |
| in experimentalInvalidation.originalNotReusedLibraries) { | |
| if (builder.isBuilt) { | |
| builder.patchUpExportScope(replacementNameSpaceMap); | |
| // Clear cached calculations that points (potential) to now replaced | |
| // things. | |
| Iterator<DillClassBuilder> iterator = builder | |
| .filteredMembersIterator(includeDuplicates: true); | |
| while (iterator.moveNext()) { | |
| iterator.current.clearCachedValues(); | |
| } | |
| } | |
| } | |
| replacementNameSpaceMap = null; | |
| } | |
| } | |
| nextGoodKernelTarget.loader.referenceMap.clear(); | |
| nextGoodKernelTarget.loader.hierarchyBuilder.clear(); | |
| nextGoodKernelTarget.loader.membersBuilder.clear(); | |
| nextGoodKernelTarget.loader.referenceFromIndex = null; | |
| convertedLibraries = null; | |
| experimentalInvalidation = null; | |
| if (_userBuilders!.isEmpty) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _userBuilders = null; | |
| } | |
| return newDillLibraryBuilders; | |
| } | |
| bool _checkEquivalentScopes( | |
| SourceLoader sourceLoader, | |
| DillLoader dillLoader, | |
| ) { | |
| for (SourceLibraryBuilder sourceLibraryBuilder | |
| in sourceLoader.sourceLibraryBuilders) { | |
| // Coverage-ignore-block(suite): Not run. | |
| Uri uri = sourceLibraryBuilder.importUri; | |
| DillLibraryBuilder dillLibraryBuilder = dillLoader.lookupLibraryBuilder( | |
| uri, | |
| )!; | |
| assert( | |
| _hasEquivalentScopes(sourceLibraryBuilder, dillLibraryBuilder) == null, | |
| _hasEquivalentScopes(sourceLibraryBuilder, dillLibraryBuilder), | |
| ); | |
| } | |
| return true; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| String? _hasEquivalentScopes( | |
| SourceLibraryBuilder sourceLibraryBuilder, | |
| DillLibraryBuilder dillLibraryBuilder, | |
| ) { | |
| return areNameSpacesEquivalent( | |
| importUri: sourceLibraryBuilder.importUri, | |
| sourceNameSpace: sourceLibraryBuilder.exportNameSpace, | |
| dillNameSpace: dillLibraryBuilder.exportNameSpace, | |
| ); | |
| } | |
| /// Compute which libraries to output and which (previous) errors/warnings we | |
| /// have to reissue. In the process do some cleanup too. | |
| List<Library> _calculateOutputLibrariesAndIssueLibraryProblems( | |
| IncrementalKernelTarget currentKernelTarget, | |
| bool fullComponent, | |
| List<Library> compiledLibraries, | |
| List<Uri> entryPoints, | |
| List<LibraryBuilder> reusedLibraries, | |
| ClassHierarchy hierarchy, | |
| UriTranslator uriTranslator, | |
| Map<Uri, Source> uriToSource, | |
| CompilerContext c, { | |
| required Set<LibraryBuilder> cleanedUpBuilders, | |
| }) { | |
| List<Library> outputLibraries; | |
| Set<Library> allLibraries; | |
| if (fullComponent) { | |
| outputLibraries = _computeTransitiveClosure( | |
| currentKernelTarget, | |
| compiledLibraries, | |
| entryPoints, | |
| reusedLibraries, | |
| hierarchy, | |
| uriTranslator, | |
| uriToSource, | |
| cleanedUpBuilders: cleanedUpBuilders, | |
| ); | |
| allLibraries = outputLibraries.toSet(); | |
| if (!c.options.omitPlatform) { | |
| // Coverage-ignore-block(suite): Not run. | |
| for (int i = 0; i < _platformBuilders!.length; i++) { | |
| Library lib = _platformBuilders![i].library; | |
| outputLibraries.add(lib); | |
| } | |
| } | |
| } else { | |
| // Coverage-ignore-block(suite): Not run. | |
| outputLibraries = <Library>[]; | |
| allLibraries = _computeTransitiveClosure( | |
| currentKernelTarget, | |
| compiledLibraries, | |
| entryPoints, | |
| reusedLibraries, | |
| hierarchy, | |
| uriTranslator, | |
| uriToSource, | |
| inputLibrariesFiltered: outputLibraries, | |
| cleanedUpBuilders: cleanedUpBuilders, | |
| ).toSet(); | |
| } | |
| _reissueLibraryProblems(allLibraries, compiledLibraries); | |
| return outputLibraries; | |
| } | |
| /// If doing experimental compilation, make sure [uriToSource] looks as it | |
| /// would have if we hadn't done experimental compilation. | |
| void _experimentalCompilationPostCompilePatchup( | |
| ExperimentalInvalidation? experimentalInvalidation, | |
| Map<Uri, Source> uriToSource, | |
| ) { | |
| if (experimentalInvalidation != null) { | |
| // uriToSources are created in the outline stage which we skipped for | |
| // some of the libraries. | |
| for (Uri uri in experimentalInvalidation.missingSources) { | |
| // TODO(jensj): KernelTargets "link" takes some "excludeSource" | |
| // setting into account. | |
| uriToSource[uri] = context.uriToSource[uri]!; | |
| } | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| /// Perform dill usage tracking if asked. Use the marking on dill builders as | |
| /// well as the class hierarchy to figure out which dill libraries was | |
| /// actually used by the compilation. | |
| Set<Library> _performDillUsageTracking( | |
| IncrementalKernelTarget target, | |
| ClassHierarchy hierarchy, | |
| ) { | |
| // Which dill builders were built? | |
| Set<Library> neededDillLibraries = {}; | |
| // Propagate data from constant evaluator: Libraries used in the constant | |
| // evaluator - that comes from dill - are marked. | |
| Set<Library> librariesUsedByConstantEvaluator = target.librariesUsed; | |
| for (LibraryBuilder builder in _dillLoadedData!.loader.libraryBuilders) { | |
| if (builder is DillLibraryBuilder) { | |
| if (builder.isBuiltAndMarked || | |
| librariesUsedByConstantEvaluator.contains(builder.library)) { | |
| neededDillLibraries.add(builder.library); | |
| } | |
| } | |
| } | |
| updateNeededDillLibrariesWithHierarchy( | |
| neededDillLibraries, | |
| hierarchy, | |
| target.loader.hierarchyBuilder, | |
| ); | |
| return neededDillLibraries; | |
| } | |
| /// Fill in the replacement maps that describe the replacements that need to | |
| /// happen because of experimental invalidation. | |
| void _experimentalInvalidationFillReplacementMaps( | |
| Map<LibraryBuilder, CompilationUnit> rebuildBodiesMap, | |
| Map<LibraryBuilder, NameSpace> replacementNameSpaceMap, | |
| ) { | |
| for (MapEntry<LibraryBuilder, CompilationUnit> entry | |
| in rebuildBodiesMap.entries) { | |
| CompilationUnit mainCompilationUnit = rebuildBodiesMap[entry.key]!; | |
| replacementNameSpaceMap[entry.key] = | |
| mainCompilationUnit.libraryBuilder.libraryNameSpace; | |
| } | |
| } | |
| /// When doing experimental invalidation, we have some builders that needs to | |
| /// be rebuild special, namely they have to be | |
| /// [currentKernelTarget.loader.read] with references from the original | |
| /// [Library] for things to work. | |
| Map<DillLibraryBuilder, CompilationUnit> | |
| _experimentalInvalidationCreateRebuildBodiesBuilders( | |
| IncrementalKernelTarget currentKernelTarget, | |
| ExperimentalInvalidation? experimentalInvalidation, | |
| UriTranslator uriTranslator, | |
| ) { | |
| // Any builder(s) in [rebuildBodies] should be semi-reused: Create source | |
| // compilation units based on the underlying libraries. | |
| // Maps from old library builder to list of new compilation unit(s). | |
| Map<DillLibraryBuilder, CompilationUnit> rebuildBodiesMap = | |
| new Map<DillLibraryBuilder, CompilationUnit>.identity(); | |
| if (experimentalInvalidation != null) { | |
| for (DillLibraryBuilder library | |
| in experimentalInvalidation.rebuildBodies) { | |
| CompilationUnit newMainCompilationUnit = currentKernelTarget.loader | |
| .readAsEntryPoint( | |
| library.importUri, | |
| fileUri: library.fileUri, | |
| referencesFromIndex: new IndexedLibrary(library.library), | |
| ); | |
| rebuildBodiesMap[library] = newMainCompilationUnit; | |
| } | |
| } | |
| return rebuildBodiesMap; | |
| } | |
| /// When doing experimental invalidation we have to patch up the scopes of the | |
| /// the libraries we're not recompiling but should have recompiled if we | |
| /// didn't do anything special. | |
| void _experimentalInvalidationPatchUpScopes( | |
| ExperimentalInvalidation? experimentalInvalidation, | |
| Map<DillLibraryBuilder, CompilationUnit> rebuildBodiesMap, | |
| ) { | |
| if (experimentalInvalidation != null) { | |
| // Map from old library builder to name space of new content. | |
| Map<LibraryBuilder, NameSpace> replacementNameSpaceMap = {}; | |
| _experimentalInvalidationFillReplacementMaps( | |
| rebuildBodiesMap, | |
| replacementNameSpaceMap, | |
| ); | |
| for (DillLibraryBuilder builder | |
| in experimentalInvalidation.originalNotReusedLibraries) { | |
| // There's only something to patch up if it was build already. | |
| if (builder.isBuilt) { | |
| builder.patchUpExportScope(replacementNameSpaceMap); | |
| } | |
| } | |
| } | |
| } | |
| IncrementalKernelTarget createIncrementalKernelTarget( | |
| CompilerContext context, | |
| FileSystem fileSystem, | |
| bool includeComments, | |
| DillTarget dillTarget, | |
| UriTranslator uriTranslator, | |
| ) { | |
| return new IncrementalKernelTarget( | |
| context, | |
| fileSystem, | |
| includeComments, | |
| dillTarget, | |
| uriTranslator, | |
| ); | |
| } | |
| /// Create a new [IncrementalKernelTarget] object, and add the reused builders | |
| /// to it. | |
| IncrementalKernelTarget _setupNewKernelTarget( | |
| CompilerContext c, | |
| UriTranslator uriTranslator, | |
| ClassHierarchy? hierarchy, | |
| List<DillLibraryBuilder> reusedLibraries, | |
| ExperimentalInvalidation? experimentalInvalidation, | |
| List<Uri> entryPoints, | |
| DillTarget dillLoadedData, | |
| ) { | |
| IncrementalKernelTarget kernelTarget = createIncrementalKernelTarget( | |
| c, | |
| new HybridFileSystem( | |
| new MemoryFileSystem(new Uri(scheme: "org-dartlang-debug", path: "/")), | |
| c.fileSystem, | |
| ), | |
| false, | |
| dillLoadedData, | |
| uriTranslator, | |
| ); | |
| kernelTarget.loader.hierarchy = hierarchy; | |
| dillLoadedData.loader.currentSourceLoader = kernelTarget.loader; | |
| // Re-use the libraries we've deemed re-usable. | |
| for (DillLibraryBuilder library in reusedLibraries) { | |
| kernelTarget.loader.registerLoadedDillLibraryBuilder(library); | |
| } | |
| // The entry point(s) has to be set first for loader.firstUri to be setup | |
| // correctly. | |
| kernelTarget.loader.roots.clear(); | |
| for (Uri entryPoint in entryPoints) { | |
| kernelTarget.loader.roots.add(kernelTarget.getEntryPointUri(entryPoint)); | |
| } | |
| return kernelTarget; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| /// When tracking used libraries we mark them when we use them. To track | |
| /// correctly we have to unmark before the next iteration to not have too much | |
| /// marked and therefore incorrectly marked something as used when it is not. | |
| void _resetTrackingOfUsedLibraries(ClassHierarchy? hierarchy) { | |
| // Reset dill loaders and kernel class hierarchy. | |
| for (LibraryBuilder builder in _dillLoadedData!.loader.libraryBuilders) { | |
| if (builder is DillLibraryBuilder) { | |
| if (builder.isBuiltAndMarked) { | |
| // Clear cached calculations in classes which upon calculation can | |
| // mark things as needed. | |
| Iterator<DillClassBuilder> iterator = builder.filteredMembersIterator( | |
| includeDuplicates: true, | |
| ); | |
| while (iterator.moveNext()) { | |
| iterator.current.clearCachedValues(); | |
| } | |
| builder.isBuiltAndMarked = false; | |
| } | |
| } | |
| } | |
| if (hierarchy is ClosedWorldClassHierarchy) { | |
| hierarchy.resetUsed(); | |
| } | |
| } | |
| /// Cleanup the hierarchy to no longer reference libraries that we are | |
| /// invalidating (or would normally have invalidated if we hadn't done any | |
| /// experimental invalidation). | |
| void _cleanupHierarchy( | |
| ClassHierarchy? hierarchy, | |
| ExperimentalInvalidation? experimentalInvalidation, | |
| ReusageResult reusedResult, | |
| ) { | |
| if (hierarchy != null) { | |
| List<Library> removedLibraries = <Library>[]; | |
| // TODO(jensj): For now remove all the original from the class hierarchy | |
| // to avoid the class hierarchy getting confused. | |
| if (experimentalInvalidation != null) { | |
| for (DillLibraryBuilder builder | |
| in experimentalInvalidation.originalNotReusedLibraries) { | |
| Library lib = builder.library; | |
| removedLibraries.add(lib); | |
| } | |
| } | |
| for (DillLibraryBuilder builder in reusedResult.notReusedLibraries) { | |
| Library lib = builder.library; | |
| removedLibraries.add(lib); | |
| } | |
| hierarchy.applyTreeChanges(removedLibraries, const [], const []); | |
| } | |
| } | |
| /// If the package uris needs to be re-checked the uri translator has changed, | |
| /// and the [DillTarget] needs to get the new uri translator. We do that | |
| /// by creating a new one. | |
| void _recreateDillTargetIfPackageWasUpdated( | |
| UriTranslator uriTranslator, | |
| CompilerContext c, | |
| ) { | |
| if (_hasToCheckPackageUris) { | |
| // Coverage-ignore-block(suite): Not run. | |
| // The package file was changed. | |
| // Make sure the dill loader is on the same page. | |
| DillTarget oldDillLoadedData = _dillLoadedData!; | |
| DillTarget newDillLoadedData = _dillLoadedData = new DillTarget( | |
| c, | |
| _ticker, | |
| uriTranslator, | |
| c.options.target, | |
| benchmarker: _benchmarker, | |
| ); | |
| for (DillLibraryBuilder library | |
| in oldDillLoadedData.loader.libraryBuilders) { | |
| newDillLoadedData.loader.registerLibraryBuilder(library); | |
| } | |
| newDillLoadedData.loader.first = oldDillLoadedData.loader.first; | |
| newDillLoadedData.loader.libraries.addAll( | |
| oldDillLoadedData.loader.libraries, | |
| ); | |
| } | |
| } | |
| /// Builders we don't use again should be removed from places like | |
| /// uriToSource (used in places for dependency tracking), the incremental | |
| /// serializer (they are no longer kept up-to-date) and the DillTarget | |
| /// (to avoid leaks). | |
| /// We also have to remove any component problems belonging to any such | |
| /// no-longer-used library (to avoid re-issuing errors about no longer | |
| /// relevant stuff). | |
| void _cleanupRemovedBuilders( | |
| IncrementalKernelTarget? lastGoodKernelTarget, | |
| ReusageResult reusedResult, | |
| UriTranslator uriTranslator, | |
| ) { | |
| bool removedDillBuilders = false; | |
| for (LibraryBuilder builder in reusedResult.notReusedLibraries) { | |
| _cleanupSourcesForBuilder( | |
| lastGoodKernelTarget, | |
| reusedResult, | |
| builder, | |
| uriTranslator, | |
| context.uriToSource, | |
| ); | |
| _incrementalSerializer | |
| // Coverage-ignore(suite): Not run. | |
| ?.invalidate(builder.fileUri); | |
| LibraryBuilder? dillBuilder = _dillLoadedData!.loader | |
| .deregisterLibraryBuilder(builder.importUri); | |
| if (dillBuilder != null) { | |
| removedDillBuilders = true; | |
| _userBuilders?.remove(builder.importUri); | |
| } | |
| // Remove component problems for libraries we don't reuse. | |
| _componentProblems.removeLibrary(builder.library, uriTranslator); | |
| } | |
| if (removedDillBuilders) { | |
| _makeDillLoaderLibrariesUpToDateWithBuildersMap(); | |
| } | |
| } | |
| bool _importsFfi() { | |
| if (_userBuilders == null) return false; | |
| final Uri dartFfiUri = Uri.parse("dart:ffi"); | |
| for (LibraryBuilder builder in _userBuilders!.values) { | |
| Library lib = builder.library; | |
| for (LibraryDependency dependency in lib.dependencies) { | |
| if (dependency.targetLibrary.importUri == dartFfiUri) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| /// Figure out if we can (and was asked to) do experimental invalidation. | |
| /// Note that this returns (future or) [null] if we're not doing experimental | |
| /// invalidation. | |
| /// | |
| /// Note that - when doing experimental invalidation - [reusedResult] is | |
| /// updated. | |
| Future<ExperimentalInvalidation?> _initializeExperimentalInvalidation( | |
| ReusageResult reusedResult, | |
| CompilerContext c, | |
| UriTranslator uriTranslator, | |
| bool collectMixinsToo, | |
| ) async { | |
| Set<DillLibraryBuilder>? rebuildBodies; | |
| Set<DillLibraryBuilder> originalNotReusedLibraries; | |
| Set<Uri>? missingSources; | |
| if (!context | |
| .options | |
| .globalFeatures | |
| .alternativeInvalidationStrategy | |
| .isEnabled) { | |
| // Coverage-ignore-block(suite): Not run. | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.disabled, | |
| ); | |
| return null; | |
| } | |
| if (_modulesToLoad != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.modulesToLoad, | |
| ); | |
| return null; | |
| } | |
| if (reusedResult.directlyInvalidated.isEmpty) { | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.noDirectlyInvalidated, | |
| ); | |
| return null; | |
| } | |
| if (reusedResult.invalidatedBecauseOfPackageUpdate) { | |
| // Coverage-ignore-block(suite): Not run. | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.packageUpdate, | |
| ); | |
| return null; | |
| } | |
| // If a library that would have been recompiled has one or more problems | |
| // where one of the invalidated uris are mentioned in a context we have to | |
| // bail to get the message updated correctly. | |
| // TODO(jensj): Possibly just treat any such libraries as bodies-only | |
| // changed. | |
| { | |
| Set<Uri>? directlyInvalidatedImportUris; | |
| for (DillLibraryBuilder builder in reusedResult.notReusedLibraries) { | |
| if (builder.library.problemsAsJson == null) continue; | |
| if (reusedResult.directlyInvalidated.contains(builder)) continue; | |
| for (String jsonString in builder.library.problemsAsJson!) { | |
| DiagnosticMessageFromJson message = | |
| new DiagnosticMessageFromJson.fromJson(jsonString); | |
| List<Uri>? relatedUris = message.relatedUris; | |
| if (relatedUris != null) { | |
| if (directlyInvalidatedImportUris == null) { | |
| directlyInvalidatedImportUris = {}; | |
| for (DillLibraryBuilder invalidated | |
| in reusedResult.directlyInvalidated) { | |
| directlyInvalidatedImportUris.add(invalidated.fileUri); | |
| } | |
| } | |
| for (Uri relatedUri in relatedUris) { | |
| if (directlyInvalidatedImportUris.contains(relatedUri)) { | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.problemsInRelatedLibrary, | |
| ); | |
| return null; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Figure out if the file(s) have changed outline, or we can just | |
| // rebuild the bodies. | |
| for (DillLibraryBuilder builder in reusedResult.directlyInvalidated) { | |
| if (builder.library.problemsAsJson != null) { | |
| assert(builder.library.problemsAsJson!.isNotEmpty); | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.problemsInLibrary, | |
| ); | |
| return null; | |
| } | |
| List<Uri> builderUris = [builder.fileUri]; | |
| for (LibraryPart part in builder.library.parts) { | |
| builderUris.add(part.fileUri); | |
| } | |
| for (Uri uri in builderUris) { | |
| Uint8List? previousSource = context.uriToSource[uri]?.source; | |
| if (previousSource == null || previousSource.isEmpty) { | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.noPreviousSource, | |
| ); | |
| return null; | |
| } | |
| ExperimentalFeatures experimentalFeatures = | |
| new ExperimentalFeaturesFromVersion(builder.languageVersion); | |
| ScannerConfiguration scannerConfiguration = experimentalFeatures | |
| .buildScannerConfiguration(); | |
| String? before = textualOutline( | |
| previousSource, | |
| scannerConfiguration, | |
| performModelling: true, | |
| experimentalFeatures: experimentalFeatures, | |
| ); | |
| if (before == null) { | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.noPreviousOutline, | |
| ); | |
| return null; | |
| } | |
| String? now; | |
| FileSystemEntity entity = c.options.fileSystem.entityForUri(uri); | |
| if (await entity.exists()) { | |
| now = textualOutline( | |
| await entity.readAsBytes(), | |
| scannerConfiguration, | |
| performModelling: true, | |
| experimentalFeatures: experimentalFeatures, | |
| ); | |
| } | |
| if (before != now) { | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.outlineChange, | |
| ); | |
| return null; | |
| } | |
| missingSources ??= new Set<Uri>(); | |
| missingSources.add(uri); | |
| } | |
| rebuildBodies ??= new Set<DillLibraryBuilder>(); | |
| rebuildBodies.add(builder); | |
| } | |
| // Special case mixins: Because the VM mixin transformation inlines | |
| // procedures, if the changed file is used as a mixin anywhere else | |
| // we can't only recompile the changed file. | |
| // TODO(jensj): Check for mixins in a smarter and faster way. | |
| Set<Library>? invalidatedMixinApplicationLibraries; | |
| if (!skipExperimentalInvalidationChecksForTesting) { | |
| for (LibraryBuilder builder in reusedResult.notReusedLibraries) { | |
| if (missingSources!.contains(builder.fileUri)) { | |
| // Missing sources will be rebuild, so mixin usage there doesn't | |
| // matter. | |
| continue; | |
| } | |
| Library lib = builder.library; | |
| for (Class c in lib.classes) { | |
| if (!c.isAnonymousMixin && !c.isEliminatedMixin) { | |
| continue; | |
| } | |
| for (Supertype supertype in c.implementedTypes) { | |
| if (missingSources.contains(supertype.classNode.fileUri)) { | |
| // This is probably a mixin from one of the libraries we want | |
| // to rebuild only the body of. | |
| // TODO(jensj): We can probably add this to the rebuildBodies | |
| // list and just rebuild that library too. | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.mixin, | |
| ); | |
| return null; | |
| } | |
| } | |
| if (collectMixinsToo && | |
| c.mixedInClass != null && | |
| missingSources.contains(c.mixedInClass!.fileUri)) { | |
| (invalidatedMixinApplicationLibraries ??= {}).add( | |
| c.enclosingLibrary, | |
| ); | |
| } | |
| } | |
| } | |
| // Special case FFI: Because the VM ffi transformation inlines | |
| // size and position, if the changed file contains ffi structs | |
| // we can't only recompile the changed file. | |
| // TODO(jensj): Come up with something smarter for this. E.g. we might | |
| // check if the FFI-classes are used in other libraries, or as actual | |
| // nested structures in other FFI-classes etc. | |
| // Alternatively (https://github.com/dart-lang/sdk/issues/45899) we might | |
| // do something else entirely that doesn't require special handling. | |
| if (_importsFfi()) { | |
| for (LibraryBuilder builder in rebuildBodies!) { | |
| Library lib = builder.library; | |
| for (LibraryDependency dependency in lib.dependencies) { | |
| Library importLibrary = dependency.targetLibrary; | |
| if (importLibrary.importUri == dartFfiUri) { | |
| // Explicitly imports dart:ffi. | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.importsFfi, | |
| ); | |
| return null; | |
| } | |
| for (Reference exportReference | |
| in importLibrary // Coverage-ignore(suite): Not run. | |
| .additionalExports) { | |
| // Coverage-ignore-block(suite): Not run. | |
| NamedNode? export = exportReference.node; | |
| if (export is Class) { | |
| Class c = export; | |
| if (c.enclosingLibrary.importUri == dartFfiUri) { | |
| // Implicitly imports a dart:ffi class. | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.importsFfiClass, | |
| ); | |
| return null; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| originalNotReusedLibraries = new Set<DillLibraryBuilder>(); | |
| Set<Uri> seenUris = new Set<Uri>(); | |
| for (DillLibraryBuilder builder in reusedResult.notReusedLibraries) { | |
| if (builder.isPart) continue; | |
| if (rebuildBodies!.contains(builder)) continue; | |
| if (!seenUris.add(builder.importUri)) continue; | |
| reusedResult.reusedLibraries.add(builder); | |
| originalNotReusedLibraries.add(builder); | |
| } | |
| reusedResult.notReusedLibraries.clear(); | |
| reusedResult.notReusedLibraries.addAll(rebuildBodies!); | |
| // Now we know we're going to do it --- remove old sources. | |
| for (Uri fileUri in missingSources!) { | |
| context.uriToSource.remove(fileUri); | |
| } | |
| recorderForTesting?.recordAdvancedInvalidationResult( | |
| AdvancedInvalidationResult.bodiesOnly, | |
| ); | |
| return new ExperimentalInvalidation( | |
| rebuildBodies, | |
| originalNotReusedLibraries, | |
| missingSources, | |
| invalidatedMixinApplicationLibraries, | |
| ); | |
| } | |
| /// Get UriTranslator, and figure out if the packages file was (potentially) | |
| /// changed. | |
| Future<UriTranslator> _setupPackagesAndUriTranslator( | |
| CompilerContext c, | |
| ) async { | |
| bool bypassCache = false; | |
| if (!identical(_previousPackagesUri, c.options.packagesUriRaw)) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _previousPackagesUri = c.options.packagesUriRaw; | |
| bypassCache = true; | |
| } else if (this._invalidatedUris.contains(c.options.packagesUri) || | |
| this._invalidatedUris.contains(c.options.packagesUriRaw)) { | |
| bypassCache = true; | |
| } | |
| UriTranslator uriTranslator = await c.options.getUriTranslator( | |
| bypassCache: bypassCache, | |
| ); | |
| _previousPackagesMap = _currentPackagesMap; | |
| _currentPackagesMap = _createPackagesMap(uriTranslator.packages); | |
| // TODO(jensj): We can probably (from the maps above) figure out if anything | |
| // changed and only set this to true if it did. | |
| _hasToCheckPackageUris = _hasToCheckPackageUris || bypassCache; | |
| _ticker.logMs("Read packages file"); | |
| if (_initializedForExpressionCompilationOnly) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _hasToCheckPackageUris = false; | |
| } | |
| return uriTranslator; | |
| } | |
| Map<String, Package> _createPackagesMap(PackageConfig packages) { | |
| Map<String, Package> result = new Map<String, Package>(); | |
| for (Package package in packages.packages) { | |
| result[package.name] = package; | |
| } | |
| return result; | |
| } | |
| /// Load platform and (potentially) initialize from dill, | |
| /// or initialize from component. | |
| Future<IncrementalCompilerData> _ensurePlatformAndInitialize( | |
| UriTranslator uriTranslator, | |
| CompilerContext context, | |
| List<Uri> entryPoints, | |
| ) async { | |
| IncrementalCompilerData data = new IncrementalCompilerData(); | |
| if (_dillLoadedData == null) { | |
| DillTarget dillLoadedData = _dillLoadedData = new DillTarget( | |
| context, | |
| _ticker, | |
| uriTranslator, | |
| context.options.target, | |
| benchmarker: _benchmarker, | |
| ); | |
| int bytesLength = await _initializationStrategy.initialize( | |
| this, | |
| dillLoadedData.loader, | |
| _ticker, | |
| uriTranslator, | |
| _currentPackagesMap!, | |
| context, | |
| data, | |
| _componentProblems, | |
| _incrementalSerializer, | |
| recorderForTesting, | |
| entryPoints, | |
| ); | |
| _appendLibraries(data, bytesLength); | |
| // We suppress finalization errors because they will reported via | |
| // problemsAsJson fields (with better precision). | |
| dillLoadedData.buildOutlines(suppressFinalizationErrors: true); | |
| _userBuilders = {}; | |
| _platformBuilders = []; | |
| for (DillLibraryBuilder builder | |
| in dillLoadedData.loader.libraryBuilders) { | |
| if (builder.importUri.isScheme("dart")) { | |
| _platformBuilders!.add(builder); | |
| } else { | |
| // Coverage-ignore-block(suite): Not run. | |
| _userBuilders![builder.importUri] = builder; | |
| } | |
| } | |
| if (_userBuilders!.isEmpty) _userBuilders = null; | |
| } | |
| data.initializationBytes = null; | |
| return data; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| /// Allows for updating the list of needed libraries. | |
| /// | |
| /// Useful if a class hierarchy has been used externally. | |
| /// Currently there are two different class hierarchies which is unfortunate. | |
| /// For now this method allows the 'ClassHierarchyBuilder' to be null. | |
| /// | |
| /// TODO(jensj,CFE in general): Eventually we should get to a point where we | |
| /// only have one class hierarchy. | |
| /// TODO(jensj): This could probably be a utility method somewhere instead | |
| /// (though handling of the case where all bets are off should probably still | |
| /// live locally). | |
| void updateNeededDillLibrariesWithHierarchy( | |
| Set<Library> neededDillLibraries, | |
| ClassHierarchy hierarchy, [ | |
| ClassHierarchyBuilder? builderHierarchy, | |
| ]) { | |
| if (hierarchy is ClosedWorldClassHierarchy && !hierarchy.allBetsOff) { | |
| Set<Class> classes = new Set<Class>(); | |
| List<Class> worklist = <Class>[]; | |
| // Get all classes touched by kernel class hierarchy. | |
| List<Class> usedClasses = hierarchy.getUsedClasses(); | |
| worklist.addAll(usedClasses); | |
| classes.addAll(usedClasses); | |
| // Get all classes touched by class hierarchy builder. | |
| if (builderHierarchy != null) { | |
| for (Class c in builderHierarchy.classNodes.keys) { | |
| if (classes.add(c)) worklist.add(c); | |
| } | |
| } | |
| // Get all supers etc. | |
| while (worklist.isNotEmpty) { | |
| Class c = worklist.removeLast(); | |
| for (Supertype supertype in c.implementedTypes) { | |
| if (classes.add(supertype.classNode)) { | |
| worklist.add(supertype.classNode); | |
| } | |
| } | |
| if (c.mixedInType != null) { | |
| if (classes.add(c.mixedInType!.classNode)) { | |
| worklist.add(c.mixedInType!.classNode); | |
| } | |
| } | |
| if (c.supertype != null) { | |
| if (classes.add(c.supertype!.classNode)) { | |
| worklist.add(c.supertype!.classNode); | |
| } | |
| } | |
| } | |
| // Add any libraries that was used or was in the "parent-chain" of a | |
| // used class. | |
| for (Class c in classes) { | |
| Library library = c.enclosingLibrary; | |
| // Only add if loaded from a dill file (and wasn't a 'dill' that was | |
| // converted from source builders to dill builders). | |
| if (_dillLoadedData!.loader.containsLibraryBuilder(library.importUri) && | |
| (_previousSourceBuilders == null || | |
| !_previousSourceBuilders!.contains(library))) { | |
| neededDillLibraries.add(library); | |
| } | |
| } | |
| } else { | |
| // Cannot track in other kernel class hierarchies or | |
| // if all bets are off: Add everything (except for the libraries we just | |
| // converted from source builders to dill builders). | |
| neededDillLibraries.clear(); | |
| for (DillLibraryBuilder builder | |
| in _dillLoadedData!.loader.libraryBuilders) { | |
| if (_previousSourceBuilders == null || | |
| !_previousSourceBuilders!.contains(builder.library)) { | |
| neededDillLibraries.add(builder.library); | |
| } | |
| } | |
| } | |
| } | |
| /// Internal method. | |
| void _invalidateNotKeptUserBuilders(Set<Uri?> invalidatedUris) { | |
| if (_modulesToLoad != null && | |
| // Coverage-ignore(suite): Not run. | |
| _userBuilders != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| Set<Library> loadedNotKept = new Set<Library>(); | |
| for (LibraryBuilder builder in _userBuilders!.values) { | |
| loadedNotKept.add(builder.library); | |
| } | |
| for (Component module in _modulesToLoad!) { | |
| loadedNotKept.removeAll(module.libraries); | |
| } | |
| for (Library lib in loadedNotKept) { | |
| invalidatedUris.add(lib.importUri); | |
| } | |
| } | |
| } | |
| /// Internal method. | |
| void _loadEnsureLoadedComponents(List<LibraryBuilder> reusedLibraries) { | |
| if (_modulesToLoad != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| bool loadedAnything = false; | |
| for (Component module in _modulesToLoad!) { | |
| bool usedComponent = false; | |
| for (Library lib in module.libraries) { | |
| if (!_dillLoadedData!.loader.containsLibraryBuilder(lib.importUri)) { | |
| _dillLoadedData!.loader.libraries.add(lib); | |
| _dillLoadedData!.loader.registerKnownLibrary(lib); | |
| reusedLibraries.add( | |
| _dillLoadedData!.loader.read(lib.importUri, -1), | |
| ); | |
| usedComponent = true; | |
| } | |
| } | |
| if (usedComponent) { | |
| _dillLoadedData!.uriToSource.addAll(module.uriToSource); | |
| loadedAnything = true; | |
| } | |
| } | |
| if (loadedAnything) { | |
| // We suppress finalization errors because they will reported via | |
| // problemsAsJson fields (with better precision). | |
| _dillLoadedData!.buildOutlines(suppressFinalizationErrors: true); | |
| _userBuilders = {}; | |
| _platformBuilders = []; | |
| for (DillLibraryBuilder builder | |
| in _dillLoadedData!.loader.libraryBuilders) { | |
| if (builder.importUri.isScheme("dart")) { | |
| _platformBuilders!.add(builder); | |
| } else { | |
| _userBuilders![builder.importUri] = builder; | |
| } | |
| } | |
| if (_userBuilders!.isEmpty) { | |
| _userBuilders = null; | |
| } | |
| } | |
| _modulesToLoad = null; | |
| } | |
| } | |
| bool dontReissueLibraryProblemsFor(Uri? uri) { | |
| return uri == debugExprUri; | |
| } | |
| /// Internal method. | |
| void _reissueLibraryProblems( | |
| Set<Library> allLibraries, | |
| List<Library> compiledLibraries, | |
| ) { | |
| // The newly-compiled libraries have issued problems already. Re-issue | |
| // problems for the libraries that weren't re-compiled (ignore compile | |
| // expression problems) | |
| allLibraries.removeAll(compiledLibraries); | |
| for (Library library in allLibraries) { | |
| if (library.problemsAsJson?.isNotEmpty == true) { | |
| for (String jsonString in library.problemsAsJson!) { | |
| DiagnosticMessageFromJson message = | |
| new DiagnosticMessageFromJson.fromJson(jsonString); | |
| if (dontReissueLibraryProblemsFor(message.uri)) { | |
| continue; | |
| } | |
| context.options.reportDiagnosticMessage(message); | |
| } | |
| } | |
| } | |
| } | |
| /// Internal method. | |
| /// Compute the transitive closure. | |
| /// | |
| /// As a side-effect, this also cleans-up now-unreferenced builders as well as | |
| /// any saved component problems for such builders. | |
| List<Library> _computeTransitiveClosure( | |
| IncrementalKernelTarget currentKernelTarget, | |
| List<Library> inputLibraries, | |
| List<Uri> entryPoints, | |
| List<LibraryBuilder> reusedLibraries, | |
| ClassHierarchy hierarchy, | |
| UriTranslator uriTranslator, | |
| Map<Uri, Source> uriToSource, { | |
| List<Library>? inputLibrariesFiltered, | |
| required Set<LibraryBuilder> cleanedUpBuilders, | |
| }) { | |
| List<Library> result = <Library>[]; | |
| Map<Uri, Uri> partUriToLibraryImportUri = <Uri, Uri>{}; | |
| Map<Uri, Library> libraryMap = <Uri, Library>{}; | |
| Map<Uri, Library> potentiallyReferencedLibraries = <Uri, Library>{}; | |
| Map<Uri, Library> potentiallyReferencedInputLibraries = <Uri, Library>{}; | |
| for (Library library in inputLibraries) { | |
| libraryMap[library.importUri] = library; | |
| if (library.parts.isNotEmpty) { | |
| for (int partIndex = 0; partIndex < library.parts.length; partIndex++) { | |
| LibraryPart part = library.parts[partIndex]; | |
| Uri partImportUri = getPartImportUri(library.importUri, part); | |
| partUriToLibraryImportUri[partImportUri] = library.importUri; | |
| } | |
| } | |
| if (library.importUri.isScheme("dart")) { | |
| result.add(library); | |
| // Coverage-ignore-block(suite): Not run. | |
| inputLibrariesFiltered?.add(library); | |
| } else { | |
| potentiallyReferencedLibraries[library.importUri] = library; | |
| potentiallyReferencedInputLibraries[library.importUri] = library; | |
| } | |
| } | |
| for (LibraryBuilder libraryBuilder in reusedLibraries) { | |
| if (libraryBuilder.importUri.isScheme("dart") && | |
| !libraryBuilder.isSynthetic) { | |
| continue; | |
| } | |
| Library lib = libraryBuilder.library; | |
| potentiallyReferencedLibraries[libraryBuilder.importUri] = lib; | |
| libraryMap[libraryBuilder.importUri] = lib; | |
| } | |
| List<Uri> worklist = <Uri>[]; | |
| for (Uri entry in entryPoints) { | |
| if (libraryMap.containsKey(entry)) { | |
| worklist.add(entry); | |
| } else { | |
| // If the entry is a part redirect to the "main" entry. | |
| Uri? partTranslation = partUriToLibraryImportUri[entry]; | |
| if (partTranslation != null) { | |
| worklist.add(partTranslation); | |
| } | |
| } | |
| } | |
| LibraryGraph graph = new LibraryGraph(libraryMap); | |
| Set<Uri?> partsUsed = new Set<Uri?>(); | |
| while (worklist.isNotEmpty && potentiallyReferencedLibraries.isNotEmpty) { | |
| Uri uri = worklist.removeLast(); | |
| if (libraryMap.containsKey(uri)) { | |
| for (Uri neighbor in graph.neighborsOf(uri)) { | |
| worklist.add(neighbor); | |
| } | |
| libraryMap.remove(uri); | |
| Library? library = potentiallyReferencedLibraries.remove(uri); | |
| if (library != null) { | |
| result.add(library); | |
| if (potentiallyReferencedInputLibraries.remove(uri) != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| inputLibrariesFiltered?.add(library); | |
| } | |
| for (LibraryPart part in library.parts) { | |
| partsUsed.add(part.fileUri); | |
| } | |
| } | |
| } | |
| } | |
| List<Library> removedLibraries = <Library>[]; | |
| bool removedDillBuilders = false; | |
| for (Uri uri in potentiallyReferencedLibraries.keys) { | |
| if (uri.isScheme("package")) continue; | |
| LibraryBuilder? builder = currentKernelTarget.loader | |
| .deregisterLoadedLibraryBuilder(uri); | |
| if (builder != null) { | |
| cleanedUpBuilders.add(builder); | |
| Library lib = builder.library; | |
| removedLibraries.add(lib); | |
| DillLibraryBuilder? removedDillBuilder = _dillLoadedData!.loader | |
| .deregisterLibraryBuilder(uri); | |
| if (removedDillBuilder != null) { | |
| cleanedUpBuilders.add(removedDillBuilder); | |
| removedDillBuilders = true; | |
| } | |
| _cleanupSourcesForBuilder( | |
| currentKernelTarget, | |
| null, | |
| builder, | |
| uriTranslator, | |
| context.uriToSource, | |
| uriToSource, | |
| partsUsed, | |
| ); | |
| _userBuilders?.remove(uri); | |
| _componentProblems.removeLibrary(lib, uriTranslator, partsUsed); | |
| // Technically this isn't necessary as the uri is not a package-uri. | |
| _incrementalSerializer | |
| // Coverage-ignore(suite): Not run. | |
| ?.invalidate(builder.fileUri); | |
| } | |
| } | |
| hierarchy.applyTreeChanges(removedLibraries, const [], const []); | |
| if (removedDillBuilders) { | |
| _makeDillLoaderLibrariesUpToDateWithBuildersMap(); | |
| } | |
| return result; | |
| } | |
| /// If builders was removed from the [dillLoadedData.loader.builders] map | |
| /// the loaders [libraries] list has to be updated too, or those libraries | |
| /// will still hang around and be linked into the Component created internally | |
| /// in the compilation process. | |
| /// This method syncs the [libraries] list with the data in [builders]. | |
| void _makeDillLoaderLibrariesUpToDateWithBuildersMap() { | |
| _dillLoadedData!.loader.libraries.clear(); | |
| for (LibraryBuilder builder in _dillLoadedData!.loader.libraryBuilders) { | |
| _dillLoadedData!.loader.libraries.add(builder.library); | |
| } | |
| } | |
| /// Internal method. | |
| /// | |
| /// [partsUsed] indicates part uris that are used by (other/alive) libraries. | |
| /// Those parts will not be cleaned up. This is useful when a part has been | |
| /// "moved" to be part of another library. | |
| void _cleanupSourcesForBuilder( | |
| IncrementalKernelTarget? lastGoodKernelTarget, | |
| ReusageResult? reusedResult, | |
| LibraryBuilder builder, | |
| UriTranslator uriTranslator, | |
| Map<Uri, Source> uriToSource, [ | |
| Map<Uri, Source>? uriToSourceExtra, | |
| Set<Uri?>? partsUsed, | |
| ]) { | |
| uriToSource.remove(builder.fileUri); | |
| uriToSourceExtra?.remove(builder.fileUri); | |
| Library lib = builder.library; | |
| for (LibraryPart part in lib.parts) { | |
| Uri partFileUri = part.fileUri; | |
| if (partsUsed != null && | |
| // Coverage-ignore(suite): Not run. | |
| partsUsed.contains(partFileUri)) { | |
| continue; | |
| } | |
| // If the builders map contain the "parts" import uri, it's a real library | |
| // (erroneously) used as a part so we don't want to remove that. | |
| if (lastGoodKernelTarget?.loader != null) { | |
| Uri? partImportUri = uriToSource[partFileUri]?.importUri; | |
| if (partImportUri != null && | |
| lastGoodKernelTarget!.loader.containsLoadedLibraryBuilder( | |
| partImportUri, | |
| )) { | |
| continue; | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| else if (reusedResult != null) { | |
| // We've just launched and don't have [lastGoodKernelTarget] yet. Search | |
| // reusedResult for a kept library with this uri. | |
| bool found = false; | |
| for (int i = 0; i < reusedResult.reusedLibraries.length; i++) { | |
| LibraryBuilder reusedLibrary = reusedResult.reusedLibraries[i]; | |
| if (reusedLibrary.fileUri == partFileUri) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (found) { | |
| continue; | |
| } | |
| } | |
| uriToSource.remove(partFileUri); | |
| // Coverage-ignore-block(suite): Not run. | |
| uriToSourceExtra?.remove(partFileUri); | |
| } | |
| } | |
| /// Internal method. | |
| void _appendLibraries(IncrementalCompilerData data, int bytesLength) { | |
| if (data.component != null) { | |
| _dillLoadedData!.loader.appendLibraries( | |
| data.component!, | |
| byteCount: bytesLength, | |
| ); | |
| } | |
| List<Component>? loadedModules = data.loadedModules; | |
| if (loadedModules != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| for (Component module in loadedModules) { | |
| _dillLoadedData!.loader.appendLibraries(module); | |
| } | |
| } | |
| _ticker.logMs("Appended libraries"); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| /// Attempt to translates a package-uri (or dart uri) to a file uri via the | |
| /// package setup. | |
| /// | |
| /// If the uri cannot be translated it will be returned unchanged. | |
| Uri translateUri(Uri uri) { | |
| IncrementalKernelTarget? lastGoodKernelTarget = this._lastGoodKernelTarget; | |
| if (lastGoodKernelTarget != null && | |
| (uri.isScheme("package") || uri.isScheme("dart"))) { | |
| return lastGoodKernelTarget.uriTranslator.translate(uri, false) ?? uri; | |
| } | |
| return uri; | |
| } | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| Future<Procedure?> compileExpression( | |
| String expression, | |
| Map<String, DartType> inputDefinitions, | |
| List<TypeParameter> typeDefinitions, | |
| String syntheticProcedureName, | |
| Uri libraryUri, { | |
| Set<String>? definitionsAddedByUser, | |
| String? className, | |
| String? methodName, | |
| int offset = TreeNode.noOffset, | |
| String? scriptUri, | |
| bool isStatic = false, | |
| }) async { | |
| IncrementalKernelTarget? lastGoodKernelTarget = this._lastGoodKernelTarget; | |
| assert(_dillLoadedData != null && lastGoodKernelTarget != null); | |
| return await context.runInContext((_) async { | |
| LibraryBuilder libraryBuilder; | |
| { | |
| CompilationUnit? compilationUnit = | |
| lastGoodKernelTarget!.loader.lookupCompilationUnit(libraryUri) ?? | |
| lastGoodKernelTarget.loader.lookupCompilationUnitByFileUri( | |
| libraryUri, | |
| ); | |
| if (compilationUnit == null) { | |
| // TODO(johnniwinther): Report an error? | |
| return null; | |
| } | |
| libraryBuilder = compilationUnit.libraryBuilder; | |
| } | |
| Library library = libraryBuilder.library; | |
| Class? cls; | |
| if (className != null) { | |
| for (Class c in library.classes) { | |
| if (c.name == className) { | |
| cls = c; | |
| break; | |
| } | |
| } | |
| } | |
| // TODO(jensj): If the found class is an eliminated mixin (and we thus do | |
| // some rewriting below) possibly we need to provide the alternative | |
| // library too to be able to figure out uris properly. | |
| _ExpressionCompilationScopeData scopeData = | |
| _computeExpressionCompilationScopeData( | |
| cls: cls, | |
| methodName: methodName, | |
| scriptUri: scriptUri, | |
| offset: offset, | |
| lastGoodKernelTarget: lastGoodKernelTarget, | |
| library: libraryBuilder.library, | |
| typeDefinitions: typeDefinitions, | |
| definitionsAddedByUser: definitionsAddedByUser, | |
| inputDefinitions: inputDefinitions, | |
| ); | |
| if (cls != null && cls.isEliminatedMixin) { | |
| // For an eliminated mixin we found the right types above, but need to | |
| // be in the origin class/library for the actual compilation for proper | |
| // lookup of static and private stuff, language version, imports etc. | |
| Class origin = cls.implementedTypes.last.classNode; | |
| Library originLibrary = origin.enclosingLibrary; | |
| CompilationUnit? compilationUnit = lastGoodKernelTarget.loader | |
| .lookupCompilationUnit(originLibrary.importUri); | |
| if (compilationUnit == null) { | |
| // TODO(johnniwinther): Report an error? | |
| // This should _not_ happen. | |
| return null; | |
| } | |
| libraryBuilder = compilationUnit.libraryBuilder; | |
| library = libraryBuilder.library; | |
| assert(originLibrary == library); | |
| cls = origin; | |
| className = cls.name; | |
| libraryUri = originLibrary.importUri; | |
| } | |
| _ticker.logMs("Loaded library $libraryUri"); | |
| _ExpressionCompilationExtensionData extensionData = | |
| _computeExpressionCompilationExtensionData( | |
| scopeData: scopeData, | |
| libraryBuilder: libraryBuilder, | |
| typeDefinitions: typeDefinitions, | |
| ); | |
| int? offsetToUse = | |
| extensionData.foundBuilder?.fileOffset ?? cls?.fileOffset; | |
| if (className != null) { | |
| Builder? scopeMember = libraryBuilder.libraryNameSpace | |
| .lookup(className!) | |
| ?.getable; | |
| if (scopeMember is ClassBuilder) { | |
| assert(cls == scopeMember.cls, "$cls != ${scopeMember.cls}"); | |
| } else { | |
| return null; | |
| } | |
| } | |
| lastGoodKernelTarget.loader.resetSeenMessages(); | |
| if (_expressionCompilationHasNamingErrors( | |
| typeDefinitions: typeDefinitions, | |
| scopeData: scopeData, | |
| extensionData: extensionData, | |
| lastGoodKernelTarget: lastGoodKernelTarget, | |
| libraryUri: libraryUri, | |
| )) { | |
| return null; | |
| } | |
| // Setup scope first in two-step process: | |
| // 1) Create a new SourceLibraryBuilder, add imports and setup (import) | |
| // scope. | |
| // 2) Create a new SourceLibraryBuilder, using a nested scope of the scope | |
| // we just created as the scope. The import scopes have been setup via | |
| // the parent chain. | |
| // This is done to create the correct "layering" (i.e. definitions from | |
| // the "self" library first, then imports while not having dill builders | |
| // directly in the scope of a source builder (which can crash things in | |
| // some circumstances). | |
| SourceCompilationUnit debugCompilationUnit = | |
| new SourceCompilationUnitImpl( | |
| importUri: libraryUri, | |
| fileUri: debugExprUri, | |
| originImportUri: libraryUri, | |
| packageLanguageVersion: new ImplicitLanguageVersion( | |
| libraryBuilder.languageVersion, | |
| ), | |
| loader: lastGoodKernelTarget.loader, | |
| resolveInLibrary: libraryBuilder, | |
| conditionalImportSupported: | |
| libraryBuilder.conditionalImportSupported, | |
| importability: libraryBuilder.importability, | |
| forPatchLibrary: false, | |
| referenceIsPartOwner: null, | |
| packageUri: null, | |
| augmentationRoot: null, | |
| isAugmenting: false, | |
| indexedLibrary: null, | |
| mayImplementRestrictedTypes: false, | |
| ); | |
| debugCompilationUnit.markLanguageVersionFinal(); | |
| SourceLibraryBuilder debugLibrary = debugCompilationUnit.createLibrary(); | |
| debugLibrary.buildNameSpace(); | |
| debugLibrary.buildScopes(lastGoodKernelTarget.loader.coreLibrary); | |
| _ticker.logMs("Created debug library"); | |
| if (libraryBuilder is DillLibraryBuilder) { | |
| for (LibraryDependency dependency | |
| in libraryBuilder.library.dependencies) { | |
| if (!dependency.isImport) continue; | |
| List<CombinatorBuilder>? combinators; | |
| for (kernel.Combinator combinator in dependency.combinators) { | |
| combinators ??= <CombinatorBuilder>[]; | |
| combinators.add( | |
| combinator.isShow | |
| ? new CombinatorBuilder.show(combinator.names) | |
| : new CombinatorBuilder.hide(combinator.names), | |
| ); | |
| } | |
| debugLibrary.compilationUnit.addSyntheticImport( | |
| importUri: dependency.importedLibraryReference.asLibrary.importUri, | |
| prefix: dependency.name, | |
| combinators: combinators, | |
| deferred: dependency.isDeferred, | |
| ); | |
| } | |
| debugLibrary.buildInitialScopes(); | |
| debugLibrary.addImportsToScope(); | |
| _ticker.logMs("Added imports"); | |
| } | |
| debugCompilationUnit = new SourceCompilationUnitImpl( | |
| importUri: libraryUri, | |
| fileUri: debugExprUri, | |
| originImportUri: libraryUri, | |
| packageLanguageVersion: new ImplicitLanguageVersion( | |
| libraryBuilder.languageVersion, | |
| ), | |
| loader: lastGoodKernelTarget.loader, | |
| resolveInLibrary: libraryBuilder, | |
| parentScope: debugCompilationUnit.compilationUnitScope, | |
| parentExtensionScope: debugCompilationUnit.extensionScope, | |
| conditionalImportSupported: libraryBuilder.conditionalImportSupported, | |
| importability: libraryBuilder.importability, | |
| forPatchLibrary: false, | |
| referenceIsPartOwner: null, | |
| packageUri: null, | |
| augmentationRoot: null, | |
| isAugmenting: false, | |
| indexedLibrary: null, | |
| mayImplementRestrictedTypes: false, | |
| ); | |
| debugCompilationUnit.markLanguageVersionFinal(); | |
| debugLibrary = debugCompilationUnit.createLibrary(); | |
| HybridFileSystem hfs = | |
| lastGoodKernelTarget.fileSystem as HybridFileSystem; | |
| MemoryFileSystem fs = hfs.memory; | |
| fs.entityForUri(debugExprUri).writeAsStringSync(expression); | |
| lastGoodKernelTarget.buildSyntheticLibrariesUntilBuildScopes([ | |
| debugLibrary, | |
| ]); | |
| lastGoodKernelTarget.buildSyntheticLibrariesUntilComputeDefaultTypes([ | |
| debugLibrary, | |
| ]); | |
| lastGoodKernelTarget.loader.finishTypeParameters( | |
| [debugLibrary], | |
| lastGoodKernelTarget.objectClassBuilder, | |
| lastGoodKernelTarget.dynamicType, | |
| ); | |
| debugLibrary.buildOutlineNodes(lastGoodKernelTarget.loader.coreLibrary); | |
| ClassHierarchy hierarchy = lastGoodKernelTarget.loader.hierarchy; | |
| ExpressionEvaluationHelper expressionEvaluationHelper = | |
| new ExpressionEvaluationHelperImpl( | |
| scopeData.extraKnownVariables, | |
| hierarchy, | |
| ); | |
| _ExpressionCompilationParameters parameters = | |
| _computeExpressionCompilationParameters( | |
| scopeData: scopeData, | |
| extensionData: extensionData, | |
| fileOffset: offsetToUse ?? libraryBuilder.library.fileOffset, | |
| ); | |
| ExpressionCompilationData | |
| expressionCompilationData = new ExpressionCompilationData( | |
| fileOffset: TreeNode.noOffset, | |
| typeParameters: typeDefinitions, | |
| positionalParameters: parameters.positionalParametersUsedForCompiling, | |
| extraParametersIfNotShadowing: parameters.extraParametersIfNotShadowing, | |
| extraKnownVariables: scopeData.extraKnownVariables, | |
| ); | |
| Expression compiledExpression = await lastGoodKernelTarget.loader | |
| .buildExpression( | |
| debugLibrary, | |
| className ?? extensionData.extensionName, | |
| (className != null && !isStatic) || | |
| parameters.extensionThis != null, | |
| expressionCompilationData, | |
| parameters.extensionThis, | |
| expressionEvaluationHelper, | |
| ); | |
| lastGoodKernelTarget.uriToSource.remove(debugExprUri); | |
| lastGoodKernelTarget.loader.sourceBytes.remove(debugExprUri); | |
| Procedure procedure = new Procedure( | |
| new Name(syntheticProcedureName), | |
| ProcedureKind.Method, | |
| new FunctionNode( | |
| new ReturnStatement(compiledExpression), | |
| typeParameters: new TypeParameterList.from(typeDefinitions), | |
| positionalParameters: new PositionalParameterList.from( | |
| parameters.allPositionalParameters, | |
| ), | |
| ), | |
| isStatic: isStatic, | |
| fileUri: debugLibrary.fileUri, | |
| containsSuperCalls: expressionCompilationData.containsSuperCalls, | |
| ); | |
| procedure.parent = cls ?? libraryBuilder.library; | |
| // Make sure the library has a canonical name. | |
| Component c = new Component(libraries: [debugLibrary.library]); | |
| c.computeCanonicalNames(); | |
| _ticker.logMs("Built debug library"); | |
| lastGoodKernelTarget.runProcedureTransformations(procedure); | |
| return procedure; | |
| }); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| static _ExpressionCompilationParameters | |
| _computeExpressionCompilationParameters({ | |
| required _ExpressionCompilationScopeData scopeData, | |
| required _ExpressionCompilationExtensionData extensionData, | |
| required int fileOffset, | |
| }) { | |
| // Prepare for having a different set of parameters for compiling vs in | |
| // the output (for when the VM tells us about a variable we don't actually | |
| // have). | |
| _ExpressionCompilationParameters result = | |
| new _ExpressionCompilationParameters(); | |
| for (MapEntry<String, DartType> def in scopeData.usedDefinitions.entries) { | |
| String name = def.key; | |
| if (scopeData.renamedPrivateNamedParameter.contains(name)) { | |
| // We rename it here so scopes will be correct. | |
| name = "_$name"; | |
| } | |
| DartType type = def.value; | |
| PositionalParameter variable = extern.createPositionalParameter( | |
| parameterName: name, | |
| type: type, | |
| fileOffset: fileOffset, | |
| ); | |
| result.allPositionalParameters.add(variable); | |
| if (!identical(name, def.key)) { | |
| // Include under the public name too. | |
| result.extraParametersIfNotShadowing[def.key] = variable; | |
| } | |
| // If the VM tells us we have #this --- let's assume we do even if we | |
| // didn't find it. | |
| if (extensionData.isExtensionOrExtensionTypeInstanceMember && | |
| isExtensionThisName(name) && | |
| result.extensionThis == null) { | |
| // The `#this` variable is special. | |
| result.extensionThis = variable..isLowered = true; | |
| result.positionalParametersUsedForCompiling.add(variable); | |
| } else if (!scopeData.removedDefinitionNames.contains(name)) { | |
| // If this definition hasn't been removed we use it for compiling. | |
| result.positionalParametersUsedForCompiling.add(variable); | |
| } else { | |
| // Pass the variables not in scope so that we can compile using | |
| // these if we would have otherwise created a compile time error. | |
| result.extraParametersIfNotShadowing[name] = variable; | |
| } | |
| } | |
| return result; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| static bool _expressionCompilationHasNamingErrors({ | |
| required List<TypeParameter> typeDefinitions, | |
| required _ExpressionCompilationScopeData scopeData, | |
| required _ExpressionCompilationExtensionData extensionData, | |
| required IncrementalKernelTarget lastGoodKernelTarget, | |
| required Uri libraryUri, | |
| }) { | |
| bool foundErrors = false; | |
| for (TypeParameter typeParam in typeDefinitions) { | |
| if (!isLegalIdentifier(typeParam.name!)) { | |
| lastGoodKernelTarget.loader.addProblem( | |
| diag.incrementalCompilerIllegalTypeParameter.withArguments( | |
| typeParameterName: '$typeParam', | |
| ), | |
| typeParam.fileOffset, | |
| 0, | |
| libraryUri, | |
| ); | |
| foundErrors = true; | |
| } | |
| } | |
| for (String name in scopeData.usedDefinitions.keys) { | |
| if (isLegalIdentifier(name)) continue; | |
| if (isExtensionThisName(name) && | |
| extensionData.isExtensionOrExtensionTypeInstanceMember) { | |
| // Accept #this for "instance members" on extensions and | |
| // extension types. | |
| continue; | |
| } | |
| lastGoodKernelTarget.loader.addProblem( | |
| diag.incrementalCompilerIllegalParameter.withArguments( | |
| parameterName: name, | |
| ), | |
| // TODO: pass variable declarations instead of | |
| // parameter names for proper location detection. | |
| // https://github.com/dart-lang/sdk/issues/44158 | |
| -1, | |
| -1, | |
| libraryUri, | |
| ); | |
| foundErrors = true; | |
| } | |
| return foundErrors; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| static _ExpressionCompilationExtensionData | |
| _computeExpressionCompilationExtensionData({ | |
| required _ExpressionCompilationScopeData scopeData, | |
| required LibraryBuilder libraryBuilder, | |
| required List<TypeParameter> typeDefinitions, | |
| }) { | |
| _ExpressionCompilationExtensionData result = | |
| new _ExpressionCompilationExtensionData(); | |
| if (scopeData.usedMethodName == null) return result; | |
| final String usedMethodName = scopeData.usedMethodName!; | |
| int indexOfDot = usedMethodName.indexOf("."); | |
| if (indexOfDot < 0) return result; | |
| String beforeDot = usedMethodName.substring(0, indexOfDot); | |
| String afterDot = usedMethodName.substring(indexOfDot + 1); | |
| Builder? builder = libraryBuilder.libraryNameSpace | |
| .lookup(beforeDot) | |
| ?.getable; | |
| if (builder == null && hasUnnamedExtensionNamePrefix(beforeDot)) { | |
| // If the name looks like an unnamed extension, try to find if we | |
| // can find such a builder. | |
| ExtensionBuilder? foundExtensionBuilder; | |
| libraryBuilder.libraryExtensions.forEachLocalExtension(( | |
| ExtensionBuilder extension, | |
| ) { | |
| if (extension.name == beforeDot) { | |
| foundExtensionBuilder = extension; | |
| } | |
| }); | |
| builder = foundExtensionBuilder; | |
| } | |
| result.extensionName = beforeDot; | |
| if (builder is ExtensionBuilder) { | |
| result.foundBuilder = builder; | |
| Builder? subBuilder = builder.lookupLocalMember(afterDot)?.getable; | |
| if (subBuilder is MemberBuilder) { | |
| if (subBuilder.isExtensionInstanceMember) { | |
| result.isExtensionOrExtensionTypeInstanceMember = true; | |
| } | |
| } | |
| } else if (builder is ExtensionTypeDeclarationBuilder) { | |
| result.foundBuilder = builder; | |
| Builder? subBuilder = builder.lookupLocalMember(afterDot)?.getable; | |
| if (subBuilder is MemberBuilder) { | |
| if (subBuilder.isExtensionTypeInstanceMember) { | |
| List<PositionalParameter>? positionals = | |
| subBuilder.invokeTarget?.function?.positionalParameters; | |
| if (positionals != null && | |
| positionals.isNotEmpty && | |
| isExtensionThisName(positionals.first.parameterName) && | |
| scopeData.usedDefinitions.containsKey(syntheticThisName)) { | |
| // If we setup the extensionType (and later the | |
| // `extensionThis`) we should also set the type correctly | |
| // (at least in a non-static setting). | |
| if (scopeData.substitution == null || | |
| scopeData.substitution!.isEmpty) { | |
| // Re-do substitutions if the old one is empty - in case the | |
| // finding of scope didn't find the right thing (e.g. | |
| // sometimes the VM claims to be on the offset for a method | |
| // name while having data as if it is inside the method). | |
| scopeData.substitution = | |
| _calculateExpressionEvaluationTypeParameterSubstitution( | |
| typeDefinitions, | |
| subBuilder.invokeTarget?.function?.typeParameters, | |
| ); | |
| } | |
| scopeData.usedDefinitions[syntheticThisName] = scopeData | |
| .substitution! | |
| .substituteType(positionals.first.type); | |
| } | |
| result.isExtensionOrExtensionTypeInstanceMember = true; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| static _ExpressionCompilationScopeData | |
| _computeExpressionCompilationScopeData({ | |
| required Class? cls, | |
| required String? methodName, | |
| required String? scriptUri, | |
| required int offset, | |
| required IncrementalKernelTarget lastGoodKernelTarget, | |
| required Library library, | |
| required List<TypeParameter> typeDefinitions, | |
| required Set<String>? definitionsAddedByUser, | |
| required Map<String, DartType> inputDefinitions, | |
| }) { | |
| _ExpressionCompilationScopeData result = | |
| new _ExpressionCompilationScopeData( | |
| usedDefinitions: new Map<String, DartType>.of(inputDefinitions), | |
| usedMethodName: methodName, | |
| ); | |
| if (scriptUri == null || offset == TreeNode.noOffset) return result; | |
| Uri? scriptUriAsUri = Uri.tryParse(scriptUri); | |
| if (scriptUriAsUri == null) return result; | |
| scriptUriAsUri = _processScriptUri( | |
| scriptUriAsUri, | |
| lastGoodKernelTarget.uriTranslator, | |
| library, | |
| ); | |
| DartScope foundScope = DartScopeBuilder2.findScopeFromOffsetAndClass( | |
| library, | |
| scriptUriAsUri, | |
| cls, | |
| offset, | |
| ); | |
| if (foundScope.member != null && | |
| (foundScope.member!.isExtensionMember || | |
| foundScope.member!.isExtensionTypeMember)) { | |
| result.usedMethodName = extractQualifiedNameFromExtensionMethodName( | |
| foundScope.member!.name.text, | |
| keepUnnamedExtensionNamePrefix: true, | |
| ); | |
| } | |
| Substitution substitution = result.substitution = | |
| _calculateExpressionEvaluationTypeParameterSubstitution( | |
| typeDefinitions, | |
| foundScope.typeParameters, | |
| ); | |
| final bool alwaysInlineConstants = lastGoodKernelTarget | |
| .backendTarget | |
| .constantsBackend | |
| .alwaysInlineConstants; | |
| // For now, if any definition is (or contains) an Extension Type, | |
| // we'll overwrite the given (runtime?) definitions so we know about | |
| // the extension type. If any definition is said to be dynamic we'll | |
| // overwrite as well because that mostly means that the value is | |
| // currently null. This can also mean that the VM can't send over the | |
| // information - this for instance happens for function types. | |
| for (MapEntry<String, Variable> def in foundScope.variables.entries) { | |
| if (definitionsAddedByUser != null && | |
| definitionsAddedByUser.contains(def.key)) { | |
| // Don't try to overwrite types of "fake" definitions added by | |
| // the user - even if it shadows real variables. | |
| continue; | |
| } | |
| DartType? existingType = result.usedDefinitions[def.key]; | |
| if (existingType != null && | |
| def.value is NamedParameter && | |
| (def.value as NamedParameter).isRenamedPrivateNamedParameter) { | |
| // We have to rename this for correct scope lookups. | |
| result.renamedPrivateNamedParameter.add(def.key); | |
| } | |
| if (existingType == null) { | |
| // We found a variable, but we weren't told about it. | |
| // For now we'll only do something special if it's a const | |
| // variable that will be inlined. | |
| if (alwaysInlineConstants && | |
| def.value.isConst && | |
| def.value.initializer is ConstantExpression) { | |
| InternalConstVariable variable = intern.createConstVariable( | |
| name: def.key, | |
| type: substitution.substituteType(def.value.type), | |
| hasDeclaredInitializer: true, | |
| fileOffset: def.value.fileOffset, | |
| value: def.value.initializer, | |
| ); | |
| result.extraKnownVariables.add(variable); | |
| } else if (def.value.isInitializingFormal || | |
| def.value.isSuperInitializingFormal) { | |
| // An (super) initializing formal parameter of a constructor | |
| // should not shadow the field it was used to initialize, | |
| // so we'll ignore it. | |
| } else { | |
| // Non-const variable we should know about but wasn't told | |
| // about. Maybe the variable was optimized out? Maybe it wasn't | |
| // captured? Either way there's something shadowing any fields | |
| // etc. | |
| result.extraKnownVariables.add( | |
| intern.createLocalVariable( | |
| name: def.key, | |
| type: substitution.substituteType(def.value.type), | |
| fileOffset: def.value.fileOffset, | |
| ), | |
| ); | |
| } | |
| } else if (existingType is DynamicType || | |
| _ExtensionTypeFinder.isOrContainsExtensionType(def.value.type)) { | |
| result.usedDefinitions[def.key] = substitution.substituteType( | |
| def.value.type, | |
| ); | |
| } else if (existingType is InterfaceType && | |
| existingType.classNode.enclosingLibrary.importUri.isScheme("dart")) { | |
| // The VM tells us about a type from the platform. | |
| // We use the static type instead because the compiler for | |
| // instance special case int in certain places which is - by the | |
| // VM - often described as _Smi. | |
| DartType usedType = substitution.substituteType(def.value.type); | |
| if (existingType.nullability == Nullability.nonNullable && | |
| usedType.nullability == Nullability.nullable) { | |
| // If a statically nullable type is known to be non-null, | |
| // we keep that information though. | |
| usedType = usedType.toNonNull(); | |
| } | |
| result.usedDefinitions[def.key] = usedType; | |
| } | |
| } | |
| for (String name in result.usedDefinitions.keys) { | |
| if (definitionsAddedByUser != null && | |
| definitionsAddedByUser.contains(name)) { | |
| // Don't remove user-provided "fake" definitions. | |
| continue; | |
| } | |
| if (!foundScope.variables.containsKey(name)) { | |
| result.removedDefinitionNames.add(name); | |
| } | |
| } | |
| return result; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| static Substitution _calculateExpressionEvaluationTypeParameterSubstitution( | |
| List<TypeParameter> typeDefinitions, | |
| List<TypeParameter>? typeParameters, | |
| ) { | |
| Map<TypeParameter, TypeParameterType> substitutionMap = {}; | |
| Map<String, TypeParameter> typeDefinitionNamesMap = {}; | |
| for (TypeParameter typeDefinition in typeDefinitions) { | |
| if (typeDefinition.name != null) { | |
| typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition; | |
| } | |
| } | |
| if (typeParameters != null) { | |
| for (TypeParameter typeParameter in typeParameters) { | |
| TypeParameter? match = typeDefinitionNamesMap[typeParameter.name]; | |
| if (match != null) { | |
| substitutionMap[typeParameter] = new TypeParameterType( | |
| match, | |
| match.computeNullabilityFromBound(), | |
| ); | |
| } | |
| } | |
| } | |
| return Substitution.fromMap(substitutionMap); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| bool _packagesEqual(Package? a, Package? b) { | |
| if (a == null || b == null) return false; | |
| if (a.name != b.name) return false; | |
| if (a.root != b.root) return false; | |
| if (a.packageUriRoot != b.packageUriRoot) return false; | |
| if (a.languageVersion != b.languageVersion) return false; | |
| if (a.extraData != b.extraData) return false; | |
| return true; | |
| } | |
| /// Internal method. | |
| ReusageResult _computeReusedLibraries( | |
| IncrementalKernelTarget? lastGoodKernelTarget, | |
| Map<Uri, DillLibraryBuilder>? _userBuilders, | |
| Set<Uri?> invalidatedUris, | |
| UriTranslator uriTranslator, | |
| ) { | |
| Set<Uri> seenUris = new Set<Uri>(); | |
| List<DillLibraryBuilder> reusedLibraries = []; | |
| for (int i = 0; i < _platformBuilders!.length; i++) { | |
| DillLibraryBuilder builder = _platformBuilders![i]; | |
| if (!seenUris.add(builder.importUri)) continue; | |
| reusedLibraries.add(builder); | |
| } | |
| if (lastGoodKernelTarget == null && _userBuilders == null) { | |
| return new ReusageResult.reusedLibrariesOnly(reusedLibraries); | |
| } | |
| bool invalidatedBecauseOfPackageUpdate = _invalidatedBecauseOfPackageUpdate; | |
| _invalidatedBecauseOfPackageUpdate = false; | |
| Set<DillLibraryBuilder> directlyInvalidated = {}; | |
| Set<DillLibraryBuilder> notReusedLibraries = {}; | |
| // Maps all non-platform LibraryBuilders from their import URI. | |
| Map<Uri, DillLibraryBuilder> builders = {}; | |
| Map<Uri?, LibraryBuilder> partUriToParent = {}; | |
| // Invalidated URIs translated back to their import URI (package:, dart:, | |
| // etc.). | |
| List<Uri> invalidatedImportUris = []; | |
| bool isInvalidated(Uri importUri, Uri? fileUri) { | |
| // Check translation of package uris before checking direct invalidation | |
| // to ensure proper setting of [invalidatedBecauseOfPackageUpdate]. | |
| if (_hasToCheckPackageUris && | |
| // Coverage-ignore(suite): Not run. | |
| importUri.isScheme("package")) { | |
| // Coverage-ignore-block(suite): Not run. | |
| // Get package name, check if the base URI has changed for the package, | |
| // if it has, translate the URI again, | |
| // otherwise the URI cannot have changed. | |
| String path = importUri.path; | |
| int firstSlash = path.indexOf('/'); | |
| String packageName = path.substring(0, firstSlash); | |
| Package? previousPackage = _previousPackagesMap?[packageName]; | |
| Package? currentPackage = _currentPackagesMap![packageName]; | |
| if (_previousPackagesMap == null || | |
| !_packagesEqual(previousPackage, currentPackage)) { | |
| // TODO(jensj): If the package has changed do we need to check the | |
| // uri/language version before bailing? If for instance we don't care | |
| // about the "extraData" field, we should probably just not check that | |
| // in "_packagesEqual". | |
| Uri? newFileUri = uriTranslator.translate(importUri, false); | |
| if (newFileUri != fileUri || | |
| previousPackage?.languageVersion != | |
| currentPackage?.languageVersion) { | |
| invalidatedBecauseOfPackageUpdate = true; | |
| return true; | |
| } | |
| } | |
| } | |
| if (invalidatedUris.contains(importUri)) return true; | |
| if (importUri != fileUri && invalidatedUris.contains(fileUri)) { | |
| return true; | |
| } | |
| if (builders[importUri]?.isSynthetic ?? false) return true; | |
| return false; | |
| } | |
| void addBuilderAndInvalidateUris( | |
| Uri uri, | |
| DillLibraryBuilder libraryBuilder, | |
| ) { | |
| if (uri.isScheme("dart") && !libraryBuilder.isSynthetic) { | |
| if (seenUris.add(libraryBuilder.importUri)) { | |
| reusedLibraries.add(libraryBuilder); | |
| } | |
| return; | |
| } | |
| builders[uri] = libraryBuilder; | |
| if (isInvalidated(uri, libraryBuilder.library.fileUri)) { | |
| invalidatedImportUris.add(uri); | |
| } | |
| for (LibraryPart part in libraryBuilder.library.parts) { | |
| Uri partImportUri = getPartImportUri(libraryBuilder.importUri, part); | |
| Uri partFileUri = part.fileUri; | |
| partUriToParent[partImportUri] = libraryBuilder; | |
| partUriToParent[partFileUri] = libraryBuilder; | |
| if (isInvalidated(partImportUri, partFileUri)) { | |
| invalidatedImportUris.add(partImportUri); | |
| if (builders[partImportUri] == null) { | |
| // Only add if entry doesn't already exist. | |
| // For good cases it shouldn't exist, but if one library claims | |
| // another library is a part (when it's not) we don't want to | |
| // overwrite the real library builder. | |
| builders[partImportUri] = libraryBuilder; | |
| } | |
| } | |
| } | |
| } | |
| if (lastGoodKernelTarget != null) { | |
| // [lastGoodKernelTarget] already contains the builders from | |
| // [userBuilders]. | |
| for (LibraryBuilder libraryBuilder | |
| in lastGoodKernelTarget.loader.loadedLibraryBuilders) { | |
| addBuilderAndInvalidateUris( | |
| libraryBuilder.importUri, | |
| libraryBuilder as DillLibraryBuilder, | |
| ); | |
| } | |
| } else { | |
| // Coverage-ignore-block(suite): Not run. | |
| // [lastGoodKernelTarget] was null so we explicitly have to add the | |
| // builders from [userBuilders] (which cannot be null as we checked | |
| // initially that one of them was non-null). | |
| _userBuilders!.forEach(addBuilderAndInvalidateUris); | |
| } | |
| recorderForTesting?.recordInvalidatedImportUris(invalidatedImportUris); | |
| for (Uri uri in invalidatedImportUris) { | |
| directlyInvalidated.add(builders[uri]!); | |
| } | |
| BuilderGraph graph = new BuilderGraph(builders); | |
| // Compute direct dependencies for each import URI (the reverse of the | |
| // edges returned by `graph.neighborsOf`). | |
| Map<Uri, Set<Uri>> directDependencies = <Uri, Set<Uri>>{}; | |
| for (Uri vertex in graph.vertices) { | |
| for (Uri neighbor in graph.neighborsOf(vertex)) { | |
| (directDependencies[neighbor] ??= new Set<Uri>()).add(vertex); | |
| } | |
| } | |
| // Remove all dependencies of [invalidatedImportUris] from builders. | |
| List<Uri> workList = invalidatedImportUris; | |
| while (workList.isNotEmpty) { | |
| Uri removed = workList.removeLast(); | |
| DillLibraryBuilder? current = builders.remove(removed); | |
| // [current] is null if the corresponding key (URI) has already been | |
| // removed. | |
| if (current != null) { | |
| Set<Uri>? s = directDependencies[current.importUri]; | |
| if (current.importUri != removed) { | |
| // Coverage-ignore-block(suite): Not run. | |
| if (s == null) { | |
| s = directDependencies[removed]; | |
| } else { | |
| s.addAll(directDependencies[removed]!); | |
| } | |
| } | |
| if (s != null) { | |
| // [s] is null for leaves. | |
| for (Uri dependency in s) { | |
| workList.add(dependency); | |
| } | |
| } | |
| notReusedLibraries.add(current); | |
| } | |
| } | |
| // Builders contain mappings from part uri to builder, meaning the same | |
| // builder can exist multiple times in the values list. | |
| for (DillLibraryBuilder builder in builders.values) { | |
| if (builder.isPart) continue; | |
| if (!seenUris.add(builder.importUri)) continue; | |
| reusedLibraries.add(builder); | |
| } | |
| return new ReusageResult( | |
| notReusedLibraries, | |
| directlyInvalidated, | |
| invalidatedBecauseOfPackageUpdate, | |
| reusedLibraries, | |
| partUriToParent, | |
| ); | |
| } | |
| @override | |
| void invalidate(Uri? uri) { | |
| _invalidatedUris.add(uri); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| void _invalidateSet(Set<Uri> uris) { | |
| _invalidatedUris.addAll(uris); | |
| } | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| void invalidateAllSources() { | |
| IncrementalKernelTarget? lastGoodKernelTarget = this._lastGoodKernelTarget; | |
| if (lastGoodKernelTarget != null) { | |
| Set<Uri> uris = new Set<Uri>.of( | |
| lastGoodKernelTarget.loader.loadedLibraryImportUris, | |
| ); | |
| uris.removeAll(_dillLoadedData!.loader.libraryImportUris); | |
| if (_previousSourceBuilders != null) { | |
| for (Library library in _previousSourceBuilders!) { | |
| uris.add(library.importUri); | |
| } | |
| } | |
| _invalidatedUris.addAll(uris); | |
| } | |
| } | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| void setModulesToLoadOnNextComputeDelta(List<Component> components) { | |
| _modulesToLoad = components.toList(); | |
| } | |
| } | |
| class _ExpressionCompilationParameters { | |
| PositionalParameter? extensionThis; | |
| List<PositionalParameter> positionalParametersUsedForCompiling = []; | |
| Map<String, PositionalParameter> extraParametersIfNotShadowing = {}; | |
| List<PositionalParameter> allPositionalParameters = []; | |
| } | |
| class _ExpressionCompilationExtensionData { | |
| bool isExtensionOrExtensionTypeInstanceMember = false; | |
| String? extensionName; | |
| Builder? foundBuilder; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class _ExpressionCompilationScopeData { | |
| final Map<String, DartType> usedDefinitions; | |
| final Set<String> removedDefinitionNames = {}; | |
| final Set<String> renamedPrivateNamedParameter = {}; | |
| final List<InternalVariable> extraKnownVariables = []; | |
| String? usedMethodName; | |
| Substitution? substitution; | |
| new({required this.usedDefinitions, required this.usedMethodName}); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class ExpressionEvaluationHelperImpl implements ExpressionEvaluationHelper { | |
| final Set<InternalVariable> knownButUnavailable = {}; | |
| final ClassHierarchy hierarchy; | |
| final Map<String, FormalParameterBuilder> extraParametersIfNotShadowing = {}; | |
| new(List<InternalVariable> extraKnown, this.hierarchy) { | |
| for (InternalVariable variable in extraKnown) { | |
| if (variable.isConst) { | |
| // We allow const variables - these are inlined (we check | |
| // `alwaysInlineConstants` in `compileExpression`). | |
| continue; | |
| } | |
| knownButUnavailable.add(variable); | |
| } | |
| } | |
| @override | |
| ExpressionInferenceResult? visitVariableGet( | |
| InternalVariableGet node, | |
| DartType typeContext, | |
| ProblemReporting problemReporting, | |
| CompilerContext compilerContext, | |
| Uri fileUri, | |
| ) { | |
| if (knownButUnavailable.contains(node.variable)) { | |
| return _returnKnownVariableUnavailable( | |
| node, | |
| node.variable, | |
| problemReporting, | |
| compilerContext, | |
| fileUri, | |
| ); | |
| } | |
| return null; | |
| } | |
| @override | |
| ExpressionInferenceResult? visitVariableSet( | |
| InternalVariableSet node, | |
| DartType typeContext, | |
| ProblemReporting problemReporting, | |
| CompilerContext compilerContext, | |
| Uri fileUri, | |
| ) { | |
| if (knownButUnavailable.contains(node.variable)) { | |
| return _returnKnownVariableUnavailable( | |
| node, | |
| node.variable, | |
| problemReporting, | |
| compilerContext, | |
| fileUri, | |
| ); | |
| } | |
| return null; | |
| } | |
| ExpressionInferenceResult _returnKnownVariableUnavailable( | |
| InternalExpression node, | |
| InternalVariable variable, | |
| ProblemReporting problemReporting, | |
| CompilerContext compilerContext, | |
| Uri fileUri, | |
| ) { | |
| return new ExpressionInferenceResult( | |
| variable.type, | |
| extern.createInvalidExpressionFromErrorText( | |
| problemReporting.buildProblem( | |
| compilerContext: compilerContext, | |
| message: diag.expressionEvaluationKnownVariableUnavailable | |
| .withArguments(variableName: variable.cosmeticName!), | |
| fileUri: fileUri, | |
| fileOffset: node.fileOffset, | |
| length: variable.cosmeticName!.length, | |
| errorHasBeenReported: node is InternalInvalidExpression, | |
| ), | |
| ), | |
| ); | |
| } | |
| @override | |
| OverwrittenInterfaceMember? overwriteFindInterfaceMember({ | |
| required ObjectAccessTarget target, | |
| required DartType receiverType, | |
| required Name name, | |
| required bool setter, | |
| bool? isImplicitThis, | |
| }) { | |
| // On a missing target, rewrite to a dynamic target instead. | |
| if (target.kind == ObjectAccessTargetKind.missing) { | |
| if (isImplicitThis ?? false) { | |
| FormalParameterBuilder? registered = | |
| extraParametersIfNotShadowing[name.text]; | |
| if (registered != null) { | |
| return new OverwrittenInterfaceMember( | |
| target: new ExpressionEvaluationParameterTarget( | |
| registered.variable, | |
| ), | |
| name: name, | |
| ); | |
| } | |
| } | |
| // On a private name, try to find a descendant of receiverType | |
| // that has the name. | |
| ClassHierarchy hierarchy = this.hierarchy; | |
| if (name.isPrivate && | |
| receiverType is InterfaceType && | |
| hierarchy is ClosedWorldClassHierarchy) { | |
| // Find all libraries that contains a subtype of this type with a | |
| // textually matching name. | |
| ClassHierarchySubtypes subtypeInformation = hierarchy | |
| .computeSubtypesInformation(); | |
| Set<Library> foundMatchInLibrary = {}; | |
| Set<Class> visited = {}; | |
| nextSubtype: | |
| for (Class cls in subtypeInformation.getSubtypesOf( | |
| receiverType.classNode, | |
| )) { | |
| if (cls.isAbstract) continue; | |
| Class? clsOrSuper = cls; | |
| while (clsOrSuper != null) { | |
| if (!visited.add(clsOrSuper)) break; | |
| for (Procedure procedure in clsOrSuper.procedures) { | |
| if (procedure.name.text == name.text) { | |
| // Name match. | |
| if (procedure.isAbstract) continue; | |
| if (setter && !procedure.isSetter) { | |
| continue; | |
| } else if (!setter && procedure.isSetter) { | |
| continue; | |
| } | |
| foundMatchInLibrary.add(clsOrSuper.enclosingLibrary); | |
| continue nextSubtype; | |
| } | |
| } | |
| for (Field field in clsOrSuper.fields) { | |
| if (field.name.text == name.text) { | |
| // Name match. | |
| if (setter && !field.hasSetter) continue; | |
| foundMatchInLibrary.add(clsOrSuper.enclosingLibrary); | |
| continue nextSubtype; | |
| } | |
| } | |
| clsOrSuper = clsOrSuper.superclass; | |
| } | |
| } | |
| // If we only found one such library we overwrite the name so the VM | |
| // will mangle the names right and find the wanted target. | |
| if (foundMatchInLibrary.length == 1 && | |
| name.library != foundMatchInLibrary.first) { | |
| name = new Name(name.text, foundMatchInLibrary.first); | |
| } | |
| } | |
| return new OverwrittenInterfaceMember( | |
| target: const ObjectAccessTarget.dynamic(), | |
| name: name, | |
| ); | |
| } | |
| return null; | |
| } | |
| @override | |
| void registerAdditionalScopeLookupResult( | |
| String name, | |
| FormalParameterBuilder result, | |
| ) { | |
| extraParametersIfNotShadowing[name] = result; | |
| } | |
| @override | |
| LookupResult? additionalScopeLookup(String name) { | |
| return extraParametersIfNotShadowing[name]; | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class _ExtensionTypeFinder extends VisitorDefault<void> with VisitorVoidMixin { | |
| static bool isOrContainsExtensionType(DartType type) { | |
| if (type is ExtensionType) return true; | |
| _ExtensionTypeFinder finder = new _ExtensionTypeFinder(); | |
| type.accept(finder); | |
| return finder._foundExtensionType; | |
| } | |
| @override | |
| void visitExtensionType(ExtensionType node) { | |
| _foundExtensionType = true; | |
| } | |
| @override | |
| void defaultNode(Node node) { | |
| if (_foundExtensionType) return; | |
| node.visitChildren(this); | |
| } | |
| bool _foundExtensionType = false; | |
| } | |
| class PackageChangedError { | |
| const new(); | |
| } | |
| class InitializeFromComponentError { | |
| final String message; | |
| const new(this.message); | |
| @override | |
| String toString() => message; | |
| } | |
| class IncrementalCompilerData { | |
| Component? component = null; | |
| List<int>? initializationBytes = null; | |
| List<Component>? loadedModules = null; | |
| } | |
| class ReusageResult { | |
| final Set<DillLibraryBuilder> notReusedLibraries; | |
| final Set<DillLibraryBuilder> directlyInvalidated; | |
| final bool invalidatedBecauseOfPackageUpdate; | |
| final List<DillLibraryBuilder> reusedLibraries; | |
| final Map<Uri?, LibraryBuilder> partUriToParent; | |
| new reusedLibrariesOnly(this.reusedLibraries) | |
| : notReusedLibraries = const {}, | |
| directlyInvalidated = const {}, | |
| invalidatedBecauseOfPackageUpdate = false, | |
| partUriToParent = const {}; | |
| new( | |
| this.notReusedLibraries, | |
| this.directlyInvalidated, | |
| this.invalidatedBecauseOfPackageUpdate, | |
| this.reusedLibraries, | |
| this.partUriToParent, | |
| ); | |
| } | |
| class ExperimentalInvalidation { | |
| final Set<DillLibraryBuilder> rebuildBodies; | |
| final Set<DillLibraryBuilder> originalNotReusedLibraries; | |
| final Set<Uri> missingSources; | |
| final Set<Library>? invalidatedMixinApplicationLibraries; | |
| new( | |
| this.rebuildBodies, | |
| this.originalNotReusedLibraries, | |
| this.missingSources, | |
| this.invalidatedMixinApplicationLibraries, | |
| ); | |
| } | |
| class IncrementalKernelTarget extends KernelTarget | |
| implements ChangedStructureNotifier { | |
| Set<Class>? classHierarchyChanges; | |
| Set<Class>? classMemberChanges; | |
| Set<Library> librariesUsed = {}; | |
| new( | |
| CompilerContext compilerContext, | |
| FileSystem fileSystem, | |
| bool includeComments, | |
| DillTarget dillTarget, | |
| UriTranslator uriTranslator, | |
| ) : super( | |
| compilerContext, | |
| fileSystem, | |
| includeComments, | |
| dillTarget, | |
| uriTranslator, | |
| ); | |
| @override | |
| ChangedStructureNotifier get changedStructureNotifier => this; | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| void registerClassMemberChange(Class c) { | |
| classMemberChanges ??= new Set<Class>(); | |
| classMemberChanges!.add(c); | |
| } | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| void registerClassHierarchyChange(Class cls) { | |
| classHierarchyChanges ??= <Class>{}; | |
| classHierarchyChanges!.add(cls); | |
| } | |
| @override | |
| void markLibrariesUsed(Set<Library> visitedLibraries) { | |
| librariesUsed.addAll(visitedLibraries); | |
| } | |
| } | |
| abstract class _InitializationStrategy { | |
| const new(); | |
| factory fromComponent(Component? component) { | |
| return component != null | |
| ? new _InitializationFromComponent(component) | |
| : const _InitializationFromSdkSummary(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| factory fromUri(Uri? uri) { | |
| return uri != null | |
| ? new _InitializationFromUri(uri) | |
| : getExperimentEnvironment().contains( | |
| enableIncrementalCompilerDepsScanAndLoad, | |
| ) | |
| ? new _InitializationFromExperimentalCache() | |
| : const _InitializationFromSdkSummary(); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| bool get initializedFromDillForTesting => false; | |
| // Coverage-ignore(suite): Not run. | |
| bool get initializedIncrementalSerializerForTesting => false; | |
| Future<int> initialize( | |
| IncrementalCompiler incrementalCompiler, | |
| Loader loaderForProblemReporting, | |
| Ticker ticker, | |
| UriTranslator uriTranslator, | |
| Map<String, Package> currentPackagesMap, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| _ComponentProblems componentProblems, | |
| IncrementalSerializer? incrementalSerializer, | |
| RecorderForTesting? recorderForTesting, | |
| List<Uri> entryPoints, | |
| ); | |
| } | |
| class _InitializationFromSdkSummary extends _InitializationStrategy { | |
| const new(); | |
| @override | |
| // Coverage-ignore(suite): Not run. | |
| Future<int> initialize( | |
| IncrementalCompiler incrementalCompiler, | |
| Loader loaderForProblemReporting, | |
| Ticker ticker, | |
| UriTranslator uriTranslator, | |
| Map<String, Package> currentPackagesMap, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| _ComponentProblems componentProblems, | |
| IncrementalSerializer? incrementalSerializer, | |
| RecorderForTesting? recorderForTesting, | |
| List<Uri> entryPoints, | |
| ) async { | |
| ProcessedOptions options = context.options; | |
| Uint8List? summaryBytes = await options.loadSdkSummaryBytes(); | |
| int bytesLength = _prepareSummary( | |
| ticker, | |
| summaryBytes, | |
| uriTranslator, | |
| context, | |
| data, | |
| ); | |
| if (options.hasAdditionalDillModules) { | |
| List<Component> loadedModules = await options.loadAdditionalDillModules( | |
| data.component?.root, | |
| ); | |
| data.loadedModules = loadedModules; | |
| } | |
| return bytesLength; | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| int _prepareSummary( | |
| Ticker ticker, | |
| Uint8List? summaryBytes, | |
| UriTranslator uriTranslator, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| ) { | |
| int bytesLength = 0; | |
| data.component = context.options.target.configureComponent(new Component()); | |
| if (summaryBytes != null) { | |
| ticker.logMs("Read ${context.options.sdkSummary}"); | |
| new BinaryBuilderWithMetadata( | |
| summaryBytes, | |
| disableLazyReading: false, | |
| disableLazyClassReading: true, | |
| ).readComponent(data.component!); | |
| ticker.logMs("Deserialized ${context.options.sdkSummary}"); | |
| bytesLength += summaryBytes.length; | |
| } | |
| return bytesLength; | |
| } | |
| } | |
| class _InitializationFromComponent extends _InitializationStrategy { | |
| Component? _componentToInitializeFrom; | |
| new(Component componentToInitializeFrom) | |
| : _componentToInitializeFrom = componentToInitializeFrom; | |
| @override | |
| Future<int> initialize( | |
| IncrementalCompiler incrementalCompiler, | |
| Loader loaderForProblemReporting, | |
| Ticker ticker, | |
| UriTranslator uriTranslator, | |
| Map<String, Package> currentPackagesMap, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| _ComponentProblems componentProblems, | |
| IncrementalSerializer? incrementalSerializer, | |
| RecorderForTesting? recorderForTesting, | |
| List<Uri> entryPoints, | |
| ) { | |
| Component? componentToInitializeFrom = _componentToInitializeFrom; | |
| _componentToInitializeFrom = null; | |
| if (componentToInitializeFrom == null) { | |
| throw const InitializeFromComponentError("Initialized twice."); | |
| } | |
| ticker.logMs("About to initializeFromComponent"); | |
| Component component = data.component = | |
| new Component( | |
| libraries: componentToInitializeFrom.libraries, | |
| uriToSource: componentToInitializeFrom.uriToSource, | |
| )..setMainMethodAndMode( | |
| componentToInitializeFrom | |
| .mainMethod | |
| // Coverage-ignore(suite): Not run. | |
| ?.reference, | |
| true, | |
| ); | |
| componentProblems.saveComponentProblems(component); | |
| bool foundDartCore = false; | |
| for (int i = 0; i < component.libraries.length; i++) { | |
| Library library = component.libraries[i]; | |
| if (library.importUri.isScheme("dart") && | |
| library.importUri.path == "core") { | |
| foundDartCore = true; | |
| break; | |
| } | |
| } | |
| if (!foundDartCore) { | |
| throw const InitializeFromComponentError( | |
| "Did not find dart:core when " | |
| "tried to initialize from component.", | |
| ); | |
| } | |
| ticker.logMs("Ran initializeFromComponent"); | |
| return new Future<int>.value(0); | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class _InitializationFromUri extends _InitializationFromSdkSummary { | |
| Uri initializeFromDillUri; | |
| new(this.initializeFromDillUri); | |
| @override | |
| Future<int> initialize( | |
| IncrementalCompiler incrementalCompiler, | |
| Loader loaderForProblemReporting, | |
| Ticker ticker, | |
| UriTranslator uriTranslator, | |
| Map<String, Package> currentPackagesMap, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| _ComponentProblems componentProblems, | |
| IncrementalSerializer? incrementalSerializer, | |
| RecorderForTesting? recorderForTesting, | |
| List<Uri> entryPoints, | |
| ) async { | |
| Uint8List? summaryBytes = await context.options.loadSdkSummaryBytes(); | |
| int bytesLength = _prepareSummary( | |
| ticker, | |
| summaryBytes, | |
| uriTranslator, | |
| context, | |
| data, | |
| ); | |
| try { | |
| bytesLength += await _initializeFromDill( | |
| incrementalCompiler, | |
| ticker, | |
| initializeFromDillUri, | |
| uriTranslator, | |
| currentPackagesMap, | |
| context, | |
| data, | |
| componentProblems, | |
| incrementalSerializer, | |
| ); | |
| } catch (e, st) { | |
| // We might have loaded x out of y libraries into the component. | |
| // To avoid any unforeseen problems start over. | |
| bytesLength = _prepareSummary( | |
| ticker, | |
| summaryBytes, | |
| uriTranslator, | |
| context, | |
| data, | |
| ); | |
| if (e is InvalidKernelVersionError || | |
| e is InvalidKernelSdkVersionError || | |
| e is PackageChangedError || | |
| e is CanonicalNameSdkError || | |
| e is CompilationModeError) { | |
| // Don't report any warning. | |
| } else { | |
| Uri? gzInitializedFrom; | |
| if (context.options.writeFileOnCrashReport) { | |
| gzInitializedFrom = saveAsGzip( | |
| data.initializationBytes!, | |
| "initialize_from.dill", | |
| ); | |
| recorderForTesting?.recordTemporaryFile(gzInitializedFrom); | |
| } | |
| if (e is CanonicalNameError) { | |
| Message message = gzInitializedFrom != null | |
| ? diag.initializeFromDillNotSelfContained.withArguments( | |
| previousCompilationUri: initializeFromDillUri.toString(), | |
| gzFileUri: gzInitializedFrom, | |
| ) | |
| : diag.initializeFromDillNotSelfContainedNoDump.withArguments( | |
| previousCompilationUri: initializeFromDillUri.toString(), | |
| ); | |
| loaderForProblemReporting.addProblem( | |
| message, | |
| TreeNode.noOffset, | |
| 1, | |
| null, | |
| ); | |
| } else { | |
| // Unknown error: Report problem as such. | |
| Message message = gzInitializedFrom != null | |
| ? diag.initializeFromDillUnknownProblem.withArguments( | |
| previousCompilationUri: initializeFromDillUri.toString(), | |
| exception: "$e", | |
| stackTrace: "$st", | |
| gzFileUri: gzInitializedFrom, | |
| ) | |
| : diag.initializeFromDillUnknownProblemNoDump.withArguments( | |
| previousCompilationUri: initializeFromDillUri.toString(), | |
| exception: "$e", | |
| stackTrace: "$st", | |
| ); | |
| loaderForProblemReporting.addProblem( | |
| message, | |
| TreeNode.noOffset, | |
| 1, | |
| null, | |
| ); | |
| } | |
| } | |
| } | |
| return bytesLength; | |
| } | |
| bool _initializedFromDill = false; | |
| bool _initializedIncrementalSerializer = false; | |
| @override | |
| bool get initializedFromDillForTesting => _initializedFromDill; | |
| @override | |
| bool get initializedIncrementalSerializerForTesting => | |
| _initializedIncrementalSerializer; | |
| // This procedure will try to load the dill file and will crash if it cannot. | |
| Future<int> _initializeFromDill( | |
| IncrementalCompiler incrementalCompiler, | |
| Ticker ticker, | |
| Uri initializeFromDillUri, | |
| UriTranslator uriTranslator, | |
| Map<String, Package> currentPackagesMap, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| _ComponentProblems _componentProblems, | |
| IncrementalSerializer? incrementalSerializer, | |
| ) async { | |
| int bytesLength = 0; | |
| FileSystemEntity entity = context.options.fileSystem.entityForUri( | |
| initializeFromDillUri, | |
| ); | |
| if (await entity.exists()) { | |
| Uint8List initializationBytes = await entity.readAsBytes(); | |
| if (initializationBytes.isNotEmpty) { | |
| ticker.logMs("Read $initializeFromDillUri"); | |
| data.initializationBytes = initializationBytes; | |
| // We're going to output all we read here so lazy loading it | |
| // doesn't make sense. | |
| List<SubComponentView> views = | |
| new BinaryBuilderWithMetadata( | |
| initializationBytes, | |
| disableLazyReading: true, | |
| ).readComponent( | |
| data.component!, | |
| checkCanonicalNames: true, | |
| createView: true, | |
| )!; | |
| List<Uri>? invalidateUrisBecauseOfLanguageVersionChange; | |
| late Version defaultSdkVersion = | |
| KernelTarget.calculateCurrentSdkVersion(context.options); | |
| // Check the any package-urls still point to the same file | |
| // (e.g. the package still exists and hasn't been updated). | |
| // Also verify NNBD settings. | |
| for (Library lib in data.component!.libraries) { | |
| if (lib.importUri.isScheme("package")) { | |
| if (uriTranslator.translate(lib.importUri, false) != lib.fileUri) { | |
| // Package has been removed or updated. | |
| // This library should be thrown away. | |
| // Everything that depends on it should be thrown away. | |
| // TODO(jensj): Anything that doesn't depend on it can be kept. | |
| // For now just don't initialize from this dill. | |
| throw const PackageChangedError(); | |
| } | |
| // Find out if the language version has changed. | |
| String path = lib.importUri.path; | |
| int firstSlash = path.indexOf('/'); | |
| String packageName = path.substring(0, firstSlash); | |
| Package? currentPackage = currentPackagesMap[packageName]; | |
| if (currentPackage == null) { | |
| // This shouldn't happen as we checked the uri above. | |
| throw const PackageChangedError(); | |
| } | |
| LanguageVersion? usedPackageVersion = | |
| currentPackage.languageVersion; | |
| bool languageVersionChanged = false; | |
| if (usedPackageVersion != null) { | |
| if (lib.languageVersion.major != usedPackageVersion.major || | |
| lib.languageVersion.minor != usedPackageVersion.minor) { | |
| languageVersionChanged = true; | |
| } | |
| } else { | |
| if (lib.languageVersion.major != defaultSdkVersion.major || | |
| lib.languageVersion.minor != defaultSdkVersion.minor) { | |
| languageVersionChanged = true; | |
| } | |
| } | |
| if (languageVersionChanged) { | |
| // Package language version can have been overwritten by @dart | |
| // annotation which wouldn't by itself constitute a change. | |
| LanguageVersionToken? annotationVersion = | |
| await scanBytesForLanguageVersionAnnotation( | |
| context.options.fileSystem, | |
| lib.fileUri, | |
| ); | |
| if (annotationVersion != null) { | |
| if (lib.languageVersion.major == annotationVersion.major && | |
| lib.languageVersion.minor == annotationVersion.minor) { | |
| languageVersionChanged = false; | |
| } | |
| } | |
| } | |
| if (languageVersionChanged) { | |
| (invalidateUrisBecauseOfLanguageVersionChange ??= []).add( | |
| lib.importUri, | |
| ); | |
| } | |
| } | |
| } | |
| // We loaded and checked the dill file. If we found any urls that need | |
| // invalidation do that now. | |
| if (invalidateUrisBecauseOfLanguageVersionChange != null) { | |
| incrementalCompiler._invalidatedBecauseOfPackageUpdate = true; | |
| for (Uri uri in invalidateUrisBecauseOfLanguageVersionChange) { | |
| incrementalCompiler.invalidate(uri); | |
| } | |
| } | |
| // Only initialize the incremental serializer when we know we'll | |
| // actually use the data loaded from dill. | |
| _initializedIncrementalSerializer = | |
| incrementalSerializer?.initialize(initializationBytes, views) ?? | |
| false; | |
| _initializedFromDill = true; | |
| bytesLength += initializationBytes.length; | |
| _componentProblems.saveComponentProblems(data.component!); | |
| } | |
| } | |
| return bytesLength; | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class _InitializationFromExperimentalCache | |
| extends _InitializationFromSdkSummary { | |
| @override | |
| Future<int> initialize( | |
| IncrementalCompiler incrementalCompiler, | |
| Loader loaderForProblemReporting, | |
| Ticker ticker, | |
| UriTranslator uriTranslator, | |
| Map<String, Package> currentPackagesMap, | |
| CompilerContext context, | |
| IncrementalCompilerData data, | |
| _ComponentProblems componentProblems, | |
| IncrementalSerializer? incrementalSerializer, | |
| RecorderForTesting? recorderForTesting, | |
| List<Uri> entryPoints, | |
| ) async { | |
| ProcessedOptions options = context.options; | |
| Uint8List? summaryBytes = await options.loadSdkSummaryBytes(); | |
| int bytesLength = _prepareSummary( | |
| ticker, | |
| summaryBytes, | |
| uriTranslator, | |
| context, | |
| data, | |
| ); | |
| if (options.hasAdditionalDillModules) { | |
| List<Component> loadedModules = await options.loadAdditionalDillModules( | |
| data.component?.root, | |
| ); | |
| data.loadedModules = loadedModules; | |
| } | |
| IncrementalCompilerCache? cache = | |
| incrementalCompiler.incrementalCompilerCache; | |
| if (cache == null) { | |
| String? path = getEnvironmentValue( | |
| pathIncrementalCompilerDepsScanAndLoad, | |
| ); | |
| if (path != null) { | |
| cache = incrementalCompiler.incrementalCompilerCache = | |
| new IncrementalCompilerCacheImpl(path); | |
| } | |
| } | |
| if (cache != null && cache.mainDirectoryExists) { | |
| await loadLibrariesFromCache( | |
| cache, | |
| context, | |
| ticker, | |
| data, | |
| incrementalCompiler, | |
| uriTranslator, | |
| entryPoints, | |
| ); | |
| } | |
| return bytesLength; | |
| } | |
| Future<void> loadLibrariesFromCache( | |
| IncrementalCompilerCache cache, | |
| CompilerContext context, | |
| Ticker ticker, | |
| IncrementalCompilerData data, | |
| IncrementalCompiler incrementalCompiler, | |
| UriTranslator uriTranslator, | |
| List<Uri> entryPoints, | |
| ) async { | |
| Component? dataComponent = data.component; | |
| if (dataComponent == null || dataComponent.libraries.isEmpty) return; | |
| DillTarget dillLoadedData = new DillTarget( | |
| context, | |
| // Create new ticker to avoid the extra prints when verbose if on - we add | |
| // a ticker entry below. | |
| new Ticker(isVerbose: false), | |
| uriTranslator, | |
| context.options.target, | |
| ); | |
| dillLoadedData.loader.appendLibraries(dataComponent); | |
| List<Component>? loadedModules = data.loadedModules; | |
| Set<Library>? loadedModulesLibraries; | |
| if (loadedModules != null) { | |
| loadedModulesLibraries = {}; | |
| for (Component module in loadedModules) { | |
| dillLoadedData.loader.appendLibraries(module); | |
| for (Library library in module.libraries) { | |
| loadedModulesLibraries.add(library); | |
| } | |
| } | |
| } | |
| dillLoadedData.buildOutlines(suppressFinalizationErrors: true); | |
| IncrementalKernelTarget kernelTarget = incrementalCompiler | |
| ._setupNewKernelTarget( | |
| context, | |
| uriTranslator, | |
| null, | |
| [], | |
| null, | |
| entryPoints, | |
| dillLoadedData, | |
| ); | |
| kernelTarget.setEntryPoints(entryPoints); | |
| context.options.temporarilySkipReporting = true; | |
| bool? enableColorsRaw = colors.enableColorsRaw; | |
| colors.enableColors = false; | |
| await kernelTarget.computeNeededPrecompilations(onlyDirectives: true); | |
| BuildResult buildResult = await kernelTarget.buildOutlines( | |
| nameRoot: dataComponent.root, | |
| ); | |
| colors.enableColors = enableColorsRaw; | |
| context.options.temporarilySkipReporting = false; | |
| Component? buildComponent = buildResult.component; | |
| if (buildComponent == null) { | |
| ticker.logMs("Error when calculating dependencies"); | |
| return; | |
| } | |
| // Compute canonical names so we can link correctly when loading below. | |
| buildComponent.computeCanonicalNames(); | |
| List<Library> libraries = buildComponent.libraries; | |
| ticker.logMs( | |
| "Calculated dependencies to and for ${libraries.length} libraries", | |
| ); | |
| // TODO(jensj): We have to handle defines too. One way could be to keep | |
| // track of them during compile (e.g. wrap the "map" and record access and | |
| // answers sort of like how the VM one is implemented), save it along with | |
| // the cached dill and verify that for the used stuff we get the same | |
| // answer. If we can get a list of everything (which we currently can't) | |
| // we could possibly also just encode it into the folder the cache is saved | |
| // in just like we should with other settings. | |
| TransitiveDependenciesCalculator transitiveDependenciesCalculator = | |
| new TransitiveDependenciesCalculator( | |
| incrementalCompiler._currentPackagesMap, | |
| buildComponent.uriToSource, | |
| libraries, | |
| ); | |
| List<String?>? transitiveDependencies = | |
| transitiveDependenciesCalculator.transitiveDependencies; | |
| if (transitiveDependencies == null) { | |
| ticker.logMs("Error on source calculating transitive dependencies."); | |
| return; | |
| } | |
| List<List<Library>> strongComponents = | |
| transitiveDependenciesCalculator.strongComponents; | |
| bool loadedEverything = true; | |
| for ( | |
| int strongComponentIndex = 0; | |
| strongComponentIndex < strongComponents.length; | |
| strongComponentIndex++ | |
| ) { | |
| List<Library> component = strongComponents[strongComponentIndex]; | |
| Set<Uri> compareTheseFiles = {}; | |
| for (Library library in component) { | |
| compareTheseFiles.add(library.fileUri); | |
| for (LibraryPart part in library.parts) { | |
| compareTheseFiles.add(part.fileUri); | |
| } | |
| } | |
| if (loadedModulesLibraries != null) { | |
| // Either all should be in a loaded module or none should be. | |
| int countInLoadedModules = 0; | |
| for (Library library in component) { | |
| if (loadedModulesLibraries.contains(library)) countInLoadedModules++; | |
| } | |
| if (countInLoadedModules == component.length) continue; | |
| if (countInLoadedModules != 0) { | |
| incrementalCompiler._invalidateSet(compareTheseFiles); | |
| loadedEverything = false; | |
| continue; | |
| } | |
| } | |
| { | |
| int countInPlatform = 0; | |
| for (Library library in component) { | |
| if (library.importUri.isScheme("dart")) countInPlatform++; | |
| } | |
| if (countInPlatform == component.length) continue; | |
| if (countInPlatform != 0) { | |
| incrementalCompiler._invalidateSet(compareTheseFiles); | |
| loadedEverything = false; | |
| continue; | |
| } | |
| } | |
| String? transitiveDepsHash = transitiveDependencies[strongComponentIndex]; | |
| if (transitiveDepsHash == null) { | |
| incrementalCompiler._invalidateSet(compareTheseFiles); | |
| loadedEverything = false; | |
| continue; | |
| } | |
| dataComponent.libraries.addAll(component); | |
| // TODO(jensj): Depending on how we invalidate things here we should | |
| // probably also disable advanced invalidation - at least for now. | |
| // If a library isn't loaded the content is completely wrong because we | |
| // only scanned and parsed the directives. | |
| // We could opt to load cached libraries that has changed if they still | |
| // have the same outline - and if that's the only way we invalidate | |
| // advanced invalidation should be enabled. | |
| Uint8List? cachedData = cache.getCachedDillBytes(transitiveDepsHash); | |
| if (cachedData == null) { | |
| incrementalCompiler._invalidateSet(compareTheseFiles); | |
| loadedEverything = false; | |
| continue; | |
| } | |
| // No need to compare the files - the hash matched. | |
| assert( | |
| areSourcesUnchanged(compareTheseFiles, buildComponent, cachedData), | |
| ); | |
| // Try to load the dill. | |
| try { | |
| new BinaryBuilderWithMetadata( | |
| cachedData, | |
| disableLazyReading: false, | |
| disableLazyClassReading: true, | |
| ).readComponent(dataComponent); | |
| } catch (_) { | |
| incrementalCompiler._invalidateSet(compareTheseFiles); | |
| loadedEverything = false; | |
| } | |
| } | |
| if (!loadedEverything) { | |
| // TODO(jensj): Not necessarily invalidated because of package update. | |
| incrementalCompiler._invalidatedBecauseOfPackageUpdate = true; | |
| } | |
| ticker.logMs("Compared and loaded cached files"); | |
| } | |
| /// Compare the files in [compareTheseFiles] between the current (via | |
| /// [buildComponent]) and the cached (via [cachedData]). | |
| /// Returns true if the sources are the same and false otherwise. | |
| bool areSourcesUnchanged( | |
| Set<Uri> compareTheseFiles, | |
| Component buildComponent, | |
| Uint8List cachedData, | |
| ) { | |
| Component cachedSources = loadComponentSourceFromBytes(cachedData); | |
| for (Uri uri in compareTheseFiles) { | |
| Source? cachedSource = cachedSources.uriToSource[uri]; | |
| Source? currentSource = buildComponent.uriToSource[uri]; | |
| if (cachedSource == null || currentSource == null) { | |
| return false; | |
| } | |
| Uint8List cachedSourceBytes = cachedSource.source; | |
| Uint8List currentSourceBytes = currentSource.source; | |
| if (cachedSourceBytes.length != currentSourceBytes.length) { | |
| return false; | |
| } | |
| for (int i = 0; i < cachedSourceBytes.length; ++i) { | |
| if (cachedSourceBytes[i] != currentSourceBytes[i]) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| class _ComponentProblems { | |
| Map<Uri, List<DiagnosticMessageFromJson>> _remainingComponentProblems = | |
| new Map<Uri, List<DiagnosticMessageFromJson>>(); | |
| /// [partsUsed] indicates part uris that are used by (other/alive) libraries. | |
| /// Those parts will not be removed from the component problems. | |
| /// This is useful when a part has been "moved" to be part of another library. | |
| void removeLibrary( | |
| Library lib, | |
| UriTranslator uriTranslator, [ | |
| Set<Uri?>? partsUsed, | |
| ]) { | |
| if (_remainingComponentProblems.isNotEmpty) { | |
| _remainingComponentProblems.remove(lib.fileUri); | |
| // Remove parts too. | |
| for (LibraryPart part in lib.parts) { | |
| // Coverage-ignore-block(suite): Not run. | |
| _remainingComponentProblems.remove(part.fileUri); | |
| } | |
| } | |
| } | |
| /// Re-issue problems on the component and return the filtered list. | |
| List<String> reissueProblems( | |
| CompilerContext context, | |
| IncrementalKernelTarget currentKernelTarget, | |
| Component componentWithDill, | |
| ) { | |
| // These problems have already been reported. | |
| Set<String> issuedProblems = new Set<String>(); | |
| if (componentWithDill.problemsAsJson != null) { | |
| issuedProblems.addAll(componentWithDill.problemsAsJson!); | |
| } | |
| // Report old problems that wasn't reported again. | |
| for (MapEntry<Uri, List<DiagnosticMessageFromJson>> entry | |
| in _remainingComponentProblems.entries) { | |
| List<DiagnosticMessageFromJson> messages = entry.value; | |
| for (int i = 0; i < messages.length; i++) { | |
| DiagnosticMessageFromJson message = messages[i]; | |
| if (issuedProblems.add(message.toJsonString())) { | |
| context.options.reportDiagnosticMessage(message); | |
| } | |
| } | |
| } | |
| // Save any new component-problems. | |
| _addProblemsAsJson(componentWithDill.problemsAsJson); | |
| return new List<String>.of(issuedProblems); | |
| } | |
| void saveComponentProblems(Component component) { | |
| _addProblemsAsJson(component.problemsAsJson); | |
| } | |
| void _addProblemsAsJson(List<String>? problemsAsJson) { | |
| if (problemsAsJson != null) { | |
| for (String jsonString in problemsAsJson) { | |
| DiagnosticMessageFromJson message = | |
| new DiagnosticMessageFromJson.fromJson(jsonString); | |
| assert( | |
| message.uri != null || | |
| // Coverage-ignore(suite): Not run. | |
| (message.involvedFiles != null && | |
| message.involvedFiles!.isNotEmpty), | |
| jsonString, | |
| ); | |
| if (message.uri != null) { | |
| List<DiagnosticMessageFromJson> messages = | |
| _remainingComponentProblems[message.uri!] ??= | |
| <DiagnosticMessageFromJson>[]; | |
| messages.add(message); | |
| } | |
| if (message.involvedFiles != null) { | |
| // Coverage-ignore-block(suite): Not run. | |
| // This indexes the same message under several uris - this way it will | |
| // be issued as long as it's a problem. It will because of | |
| // deduplication when we re-issue these (in reissueComponentProblems) | |
| // only be reported once. | |
| for (Uri uri in message.involvedFiles!) { | |
| List<DiagnosticMessageFromJson> messages = | |
| _remainingComponentProblems[uri] ??= | |
| <DiagnosticMessageFromJson>[]; | |
| messages.add(message); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| /// Translate a script uri provided as a package uri to a file uri. | |
| /// Otherwise return as is. | |
| /// | |
| /// ``` | |
| /// // Returns as-is for non-package uri. | |
| /// DartDocTest( | |
| /// _processScriptUri( | |
| /// Uri.parse("file://a/b/c.dart"), | |
| /// UriTranslator.forTesting(), | |
| /// Library( | |
| /// Uri.parse("package:foo/c.dart"), | |
| /// fileUri: Uri.parse("file://a/b/c.dart"), | |
| /// ), | |
| /// ), | |
| /// Uri.parse("file://a/b/c.dart"), | |
| /// ) | |
| /// | |
| /// // Returns libraries file-uri if import uri matches. | |
| /// DartDocTest( | |
| /// _processScriptUri( | |
| /// Uri.parse("package:foo/c.dart"), | |
| /// UriTranslator.forTesting(), | |
| /// Library( | |
| /// Uri.parse("package:foo/c.dart"), | |
| /// fileUri: Uri.parse("file://a/b/c.dart"), | |
| /// ), | |
| /// ), | |
| /// Uri.parse("file://a/b/c.dart"), | |
| /// ) | |
| /// | |
| /// // Can find uri from part. | |
| /// DartDocTest( | |
| /// _processScriptUri( | |
| /// Uri.parse("package:foo/e.dart"), | |
| /// UriTranslator.forTesting(), | |
| /// Library( | |
| /// Uri.parse("package:foo/c.dart"), | |
| /// fileUri: Uri.parse("file://a/b/c.dart"), | |
| /// parts: [ | |
| /// LibraryPart([], "d.dart", Uri.parse("file://a/b/d.dart")), | |
| /// LibraryPart([], "e.dart", Uri.parse("file://a/b/e.dart")), | |
| /// LibraryPart([], "f.dart", Uri.parse("file://a/b/f.dart")), | |
| /// ], | |
| /// ), | |
| /// ), | |
| /// Uri.parse("file://a/b/e.dart"), | |
| /// ) | |
| /// | |
| /// // Part uri can be package uri too though. | |
| /// DartDocTest( | |
| /// _processScriptUri( | |
| /// Uri.parse("package:foo/e.dart"), | |
| /// UriTranslator.forTesting(), | |
| /// Library( | |
| /// Uri.parse("package:foo/c.dart"), | |
| /// fileUri: Uri.parse("file://a/b/c.dart"), | |
| /// parts: [ | |
| /// LibraryPart([], "d.dart", Uri.parse("file://a/b/d.dart")), | |
| /// LibraryPart( | |
| /// [], | |
| /// "package:foo/e.dart", | |
| /// Uri.parse("file://foo/lib/e.dart"), | |
| /// ), | |
| /// LibraryPart([], "f.dart", Uri.parse("file://a/b/f.dart")), | |
| /// ], | |
| /// ), | |
| /// ), | |
| /// Uri.parse("file://foo/lib/e.dart"), | |
| /// ); | |
| /// ``` | |
| Uri _processScriptUri( | |
| Uri scriptUriAsUri, | |
| UriTranslator uriTranslator, | |
| Library library, | |
| ) { | |
| if (!scriptUriAsUri.isScheme("package")) return scriptUriAsUri; | |
| // Methods etc saves file uris, so try to convert the script uri to | |
| // a file uri. | |
| Uri? fileUri = uriTranslator.translate(scriptUriAsUri, false); | |
| if (fileUri != null) return fileUri; | |
| // If the result above is null, the packages file likely doesn't | |
| // exist (anymore). | |
| // For non-parts the library should have the answer directly. | |
| if (library.importUri == scriptUriAsUri) { | |
| return library.fileUri; | |
| } | |
| // If we still don't have an answer it's probably a part file. | |
| for (LibraryPart part in library.parts) { | |
| Uri partImportUri = getPartImportUri(library.importUri, part); | |
| if (partImportUri == scriptUriAsUri) { | |
| return part.fileUri; | |
| } | |
| } | |
| // We failed somehow. It's not the library, nor from a part. | |
| // This shouldn't happen. | |
| return scriptUriAsUri; | |
| } | |
| /// Result of advanced invalidation used for testing. | |
| enum AdvancedInvalidationResult { | |
| /// Advanced invalidation is disabled. | |
| disabled, | |
| /// Requested to load modules, advanced invalidation is not supported. | |
| modulesToLoad, | |
| /// Nothing directly invalidated, no need for advanced invalidation. | |
| // TODO(johnniwinther): Split this into multiple values that describe what led | |
| // to there being no directly invalidated libraries. | |
| noDirectlyInvalidated, | |
| /// Package config has been updated, advanced invalidation is not supported. | |
| packageUpdate, | |
| /// Problems in invalidated library, advanced invalidation is not supported. | |
| problemsInLibrary, | |
| /// Problems in related library, advanced invalidation is not supported. | |
| problemsInRelatedLibrary, | |
| /// No previous source for invalidated library, can't compare to new source. | |
| noPreviousSource, | |
| /// No textual outline computed for previous source, can't compare to new | |
| /// source. | |
| noPreviousOutline, | |
| /// Textual outline has changed. | |
| outlineChange, | |
| /// Invalidated library contains class/mixin declaration used as mixin. | |
| mixin, | |
| /// Invalidated library imports 'dart:ffi'. | |
| importsFfi, | |
| /// Invalidated library imports library that exports class(es) from | |
| /// 'dart:ffi'. | |
| importsFfiClass, | |
| /// Only bodies need to be rebuilt. This mean that advanced invalidation | |
| /// succeeded. | |
| bodiesOnly, | |
| } | |
| class RecorderForTesting { | |
| const new(); | |
| // Coverage-ignore(suite): Not run. | |
| void recordAdvancedInvalidationResult(AdvancedInvalidationResult result) {} | |
| // Coverage-ignore(suite): Not run. | |
| void recordNonFullComponent(Component component) {} | |
| // Coverage-ignore(suite): Not run. | |
| void recordInvalidatedImportUris(List<Uri> uris) {} | |
| // Coverage-ignore(suite): Not run. | |
| void recordRebuildBodiesCount(int count) {} | |
| // Coverage-ignore(suite): Not run. | |
| void recordTemporaryFile(Uri uri) {} | |
| } | |
| abstract interface class IncrementalCompilerCache { | |
| bool get mainDirectoryExists; | |
| Uint8List? getCachedDillBytes(String transitiveDepsHash); | |
| void cacheLibraries( | |
| List<Library> libraries, | |
| String transitiveDepsHash, | |
| Component fromComponent, | |
| ); | |
| } | |
| // Coverage-ignore(suite): Not run. | |
| class TransitiveDependenciesCalculator { | |
| late final List<List<Library>> strongComponents; | |
| final Map<String, Package>? currentPackagesMap; | |
| final Map<Uri, Source> uriToSource; | |
| late final List<String?> _transitiveDependencies; | |
| bool _error = false; | |
| new(this.currentPackagesMap, this.uriToSource, Iterable<Library> libraries) { | |
| kernelGraph.LibraryGraph graph = new kernelGraph.LibraryGraph(libraries); | |
| strongComponents = kernelGraph.computeStrongComponents(graph); | |
| _transitiveDependencies = new List.filled(strongComponents.length, null); | |
| _calculateTransitiveHashes(); | |
| } | |
| List<String?>? get transitiveDependencies => | |
| _error ? null : _transitiveDependencies; | |
| LanguageVersion? _getPackageLanguageVersion(Uri importUri) { | |
| if (!importUri.isScheme("package")) return null; | |
| String path = importUri.path; | |
| int firstSlash = path.indexOf('/'); | |
| if (firstSlash == -1) return null; | |
| String packageName = path.substring(0, firstSlash); | |
| Package? package = currentPackagesMap?[packageName]; | |
| return package?.languageVersion; | |
| } | |
| void _calculateTransitiveHashes() { | |
| Map<Library, int> libraryToStrongComponentIndex = {}; | |
| for ( | |
| int componentNumber = 0; | |
| componentNumber < strongComponents.length; | |
| componentNumber++ | |
| ) { | |
| List<Library> component = strongComponents[componentNumber]; | |
| for (Library library in component) { | |
| if (library.importUri.isScheme("dart")) continue; | |
| libraryToStrongComponentIndex[library] = componentNumber; | |
| } | |
| } | |
| List<Set<int>> directDependencies = new List.generate( | |
| strongComponents.length, | |
| (_) => {}, | |
| growable: false, | |
| ); | |
| for (List<Library> component in strongComponents) { | |
| for (Library library in component) { | |
| int? thisLibraryComponent = libraryToStrongComponentIndex[library]; | |
| if (thisLibraryComponent == null) continue; | |
| Set<int> componentDeps = directDependencies[thisLibraryComponent]; | |
| for (LibraryDependency dependency in library.dependencies) { | |
| int? componentNumber = | |
| libraryToStrongComponentIndex[dependency.targetLibrary]; | |
| if (componentNumber == null || | |
| componentNumber == thisLibraryComponent) { | |
| continue; | |
| } | |
| componentDeps.add(componentNumber); | |
| } | |
| } | |
| } | |
| for (int i = 0; i < strongComponents.length; i++) { | |
| _fillTransitiveDeps(i, directDependencies); | |
| } | |
| } | |
| void _fillTransitiveDeps( | |
| int componentNumber, | |
| List<Set<int>> directDependencies, | |
| ) { | |
| if (_transitiveDependencies[componentNumber] != null) return; | |
| List<String> hashMe = []; | |
| List<Library> component = strongComponents[componentNumber]; | |
| for (Library lib in component) { | |
| if (lib.importUri.isScheme("dart")) continue; | |
| LanguageVersion? version = _getPackageLanguageVersion(lib.importUri); | |
| hashMe.add("${lib.fileUri}=v$version"); | |
| Source? source = uriToSource[lib.fileUri]; | |
| if (source == null) { | |
| _error = true; | |
| return; | |
| } | |
| // TODO(jensj): To support advanced invalidation this should be the | |
| // textual outline instead. | |
| hashMe.add("${lib.fileUri}=${MD5Ish.hashBytes(source.source)}"); | |
| for (LibraryPart part in lib.parts) { | |
| source = uriToSource[part.fileUri]; | |
| if (source == null) { | |
| _error = true; | |
| return; | |
| } | |
| hashMe.add("${part.fileUri}=${MD5Ish.hashBytes(source.source)}"); | |
| } | |
| } | |
| Set<int> directDeps = directDependencies[componentNumber]; | |
| for (int dep in directDeps) { | |
| _fillTransitiveDeps(dep, directDependencies); | |
| String directTransitiveHash = _transitiveDependencies[dep]!; | |
| hashMe.add(directTransitiveHash); | |
| } | |
| hashMe.sort(); | |
| String hashMeString = hashMe.join("\n"); | |
| String hash = MD5Ish.hashString(hashMeString); | |
| _transitiveDependencies[componentNumber] = hash; | |
| } | |
| } |