blob: 77ae64b7051a1dc8618656767ceab4465e85332f [file] [log] [blame]
// 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.
/// @docImport 'package:analyzer/src/generated/engine.dart';
library;
import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/binary/binary_reader.dart';
import 'package:analyzer/src/binary/binary_writer.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/analysis/analysis_options_map.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/driver_event.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/library_graph.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/exception/exception.dart';
import 'package:analyzer/src/fine/library_manifest.dart';
import 'package:analyzer/src/fine/requirements.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/summary2/bundle_reader.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/reference.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:collection/collection.dart';
/// Context information necessary to analyze one or more libraries within an
/// [AnalysisDriver].
///
/// Currently this is implemented as a wrapper around [AnalysisContext].
class LibraryContext {
final LibraryContextTestData? testData;
final PerformanceLog logger;
final ByteStore byteStore;
final StreamController<Object>? eventsController;
final FileSystemState fileSystemState;
final File? packagesFile;
final bool withFineDependencies;
final SummaryDataStore store = SummaryDataStore();
late final AnalysisContextImpl analysisContext;
late final LinkedElementFactory elementFactory;
Set<LibraryCycle> loadedBundles = Set.identity();
LibraryContext({
required this.testData,
required AnalysisSessionImpl analysisSession,
required this.logger,
required this.byteStore,
required this.eventsController,
required this.fileSystemState,
required AnalysisOptionsMap analysisOptionsMap,
required DeclaredVariables declaredVariables,
required SourceFactory sourceFactory,
required this.packagesFile,
required this.withFineDependencies,
required SummaryDataStore? externalSummaries,
}) {
testData?.instance = this;
analysisContext = AnalysisContextImpl(
analysisOptionsMap: analysisOptionsMap,
declaredVariables: declaredVariables,
sourceFactory: sourceFactory,
);
elementFactory = LinkedElementFactory(
analysisContext,
analysisSession,
Reference.root(),
);
if (externalSummaries != null) {
for (var bundle in externalSummaries.bundles) {
elementFactory.addBundle(
BundleReader(
elementFactory: elementFactory,
resolutionBytes: bundle.resolutionBytes,
unitsInformativeBytes: {},
libraryManifests: {},
),
);
}
}
}
/// Computes a [LibraryFragmentImpl] for the given library/unit pair.
LibraryFragmentImpl computeUnitElement(
LibraryFileKind library,
FileState unit,
) {
var libraryElement = elementFactory.libraryOfUri2(library.file.uri);
return libraryElement.fragments.singleWhere(
(fragment) => fragment.source.uri == unit.uri,
);
}
/// Notifies this object that it is about to be discarded.
///
/// Returns the keys of the artifacts that are no longer used.
Set<String> dispose() {
var keys = unloadAll();
elementFactory.dispose();
testData?.instance = null;
return keys;
}
/// Get the [LibraryElementImpl] for the given library.
LibraryElementImpl getLibraryElement(Uri uri) {
_createElementFactoryTypeProvider();
return elementFactory.libraryOfUri2(uri);
}
/// Load data required to access elements of the given [targetLibrary].
void load({
required LibraryFileKind targetLibrary,
required OperationPerformanceImpl performance,
}) {
var libraryCycle = performance.run('libraryCycle', (performance) {
fileSystemState.newFileOperationPerformance = performance;
try {
return targetLibrary.libraryCycle;
} finally {
fileSystemState.newFileOperationPerformance = null;
}
});
if (loadedBundles.contains(libraryCycle)) {
return;
}
performance.run('loadBundle', (performance) {
_loadBundle(cycle: libraryCycle, performance: performance);
});
// There might be a rare (and wrong) situation, when the external summaries
// already include the [targetLibrary]. When this happens, [loadBundle]
// exists without doing any work. But the type provider must be created.
_createElementFactoryTypeProvider();
}
/// Remove libraries represented by the [removed] files.
/// If we need these libraries later, we will relink and reattach them.
void remove(Set<FileState> removed, Set<String> removedKeys) {
elementFactory.removeLibraries(removed.map((e) => e.uri).toSet());
loadedBundles.removeWhere((cycle) {
var cycleFiles = cycle.libraries.map((e) => e.file);
if (cycleFiles.any(removed.contains)) {
removedKeys.add(cycle.linkedKey);
return true;
}
return false;
});
}
/// Unloads all loaded bundles.
///
/// Returns the keys of the artifacts that are no longer used.
Set<String> unloadAll() {
var keySet = <String>{};
var uriSet = <Uri>{};
for (var cycle in loadedBundles) {
keySet.add(cycle.linkedKey);
uriSet.addAll(cycle.libraries.map((e) => e.file.uri));
}
elementFactory.removeLibraries(uriSet);
loadedBundles.clear();
return keySet;
}
/// Ensure that type provider is created.
void _createElementFactoryTypeProvider() {
if (!analysisContext.hasTypeProvider) {
elementFactory.createTypeProviders(
elementFactory.dartCoreElement,
elementFactory.dartAsyncElement,
);
}
}
/// Recursively load the linked bundle for [cycle], link if not available.
///
/// Uses the same [performance] during recursion, so has single aggregate
/// set of operations.
void _loadBundle({
required LibraryCycle cycle,
required OperationPerformanceImpl performance,
}) {
if (!loadedBundles.add(cycle)) return;
addToLogRing('[load][cycle: $cycle]');
performance.getDataInt('cycleCount').increment();
performance.getDataInt('libraryCount').add(cycle.libraries.length);
for (var directDependency in cycle.directDependencies) {
_loadBundle(cycle: directDependency, performance: performance);
}
var unitsInformativeBytes = <Uri, Uint8List>{};
for (var library in cycle.libraries) {
for (var file in library.files) {
unitsInformativeBytes[file.uri] = file.unlinked2.informativeBytes;
}
}
var probe = _probeLinkedBundle(cycle: cycle, performance: performance);
var linkedBytes = probe.linkedBytes;
if (linkedBytes == null) {
testData?.linkedCycles.add(
cycle.libraries.map((e) => e.file.path).toSet(),
);
Uint8List newLinkedBytes;
try {
if (withFineDependencies) {
var requirements = RequirementsManifest();
globalResultRequirements = requirements;
var linkResult = performance.run('link', (performance) {
return link(
elementFactory: elementFactory,
apiSignature: cycle.nonTransitiveApiSignature,
performance: performance,
inputLibraries: cycle.libraries,
);
});
newLinkedBytes = linkResult.resolutionBytes;
var newLibraryManifests = <Uri, LibraryManifestHandle>{};
performance.run('computeManifests', (performance) {
var inputManifests = performance.run('inputManifests', (_) {
return probe.libraryManifests.mapValue((h) => h.instance);
});
newLibraryManifests = LibraryManifestBuilder(
elementFactory: elementFactory,
inputLibraries: cycle.libraries,
inputManifests: inputManifests,
).computeManifests(performance: performance);
elementFactory.libraryManifests.addAll(newLibraryManifests);
});
requirements.addExports(
elementFactory: elementFactory,
libraryUriSet: cycle.libraryUris,
);
globalResultRequirements?.stopRecording();
globalResultRequirements = null;
requirements.removeReqForLibs(cycle.libraryUris);
assert(requirements.assertSerialization());
var newEntry = _LinkedBundleCacheEntry(
nonTransitiveApiSignature: cycle.nonTransitiveApiSignature,
requirementsDigest: requirements.toDigest(),
requirementsBytes: requirements.toBytes(),
libraryManifests: newLibraryManifests,
linkedBytes: newLinkedBytes,
);
performance.run('writeCacheEntry', (performance) {
newEntry.write(
byteStore: byteStore,
key: cycle.linkedKey,
performance: performance,
);
});
eventsController?.add(
LinkLibraryCycle(
elementFactory: elementFactory,
cycle: cycle,
requirements: requirements,
),
);
} else {
var linkResult = performance.run('link', (performance) {
return link(
elementFactory: elementFactory,
apiSignature: cycle.nonTransitiveApiSignature,
performance: performance,
inputLibraries: cycle.libraries,
);
});
newLinkedBytes = linkResult.resolutionBytes;
var requirements = RequirementsManifest();
var newEntry = _LinkedBundleCacheEntry(
nonTransitiveApiSignature: cycle.nonTransitiveApiSignature,
requirementsDigest: requirements.toDigest(),
requirementsBytes: requirements.toBytes(),
libraryManifests: {},
linkedBytes: newLinkedBytes,
);
performance.run('writeCacheEntry', (performance) {
newEntry.write(
byteStore: byteStore,
key: cycle.linkedKey,
performance: performance,
);
testData?.forCycle(cycle).putKeys.add(cycle.linkedKey);
});
eventsController?.add(
LinkLibraryCycle(
elementFactory: elementFactory,
cycle: cycle,
requirements: null,
),
);
}
} catch (exception, stackTrace) {
_throwLibraryCycleLinkException(cycle, exception, stackTrace);
}
} else {
testData?.forCycle(cycle).getKeys.add(cycle.linkedKey);
performance.getDataInt('libraryLoadCount').add(cycle.libraries.length);
// TODO(scheglov): Take / clear parsed units in files.
eventsController?.add(ReuseLinkedBundle(cycle: cycle));
var bundleReader = performance.run('bundleReader', (performance) {
return BundleReader(
elementFactory: elementFactory,
unitsInformativeBytes: unitsInformativeBytes,
resolutionBytes: linkedBytes,
libraryManifests: probe.libraryManifests,
);
});
elementFactory.addBundle(bundleReader);
elementFactory.libraryManifests.addAll(probe.libraryManifests);
addToLogRing('[load][addedBundle][cycle: $cycle]');
}
}
/// Returns a previously linked bundle entry for [cycle] if present and
/// reusable; otherwise returns `null`. Always returns the last known
/// [LibraryManifest]s from the stored entry (if any) so they can be reused
/// to preserve item versions during relinking.
_LinkedBundleProbeResult _probeLinkedBundle({
required LibraryCycle cycle,
required OperationPerformanceImpl performance,
}) {
return performance.run('probeLinkedBundle', (performance) {
var entry = performance.run('readCacheEntry', (performance) {
return _LinkedBundleCacheEntry.read(
byteStore: byteStore,
key: cycle.linkedKey,
performance: performance,
);
});
// Nothing cached at all.
if (entry == null) {
return _LinkedBundleProbeResult(
libraryManifests: {},
linkedBytes: null,
);
}
// If we don't track fine dependencies, any hit is good enough.
// The key already depends on the transitive API signature.
if (!withFineDependencies) {
return _LinkedBundleProbeResult(
libraryManifests: entry.libraryManifests,
linkedBytes: entry.linkedBytes,
);
}
// If anything changed in the API signature, relink the cycle.
// But keep previous manifests to reuse item IDs.
if (entry.nonTransitiveApiSignature != cycle.nonTransitiveApiSignature) {
return _LinkedBundleProbeResult(
libraryManifests: entry.libraryManifests,
linkedBytes: null,
);
}
// Fast-path: if the stored digest matches current manifests, reuse.
var digestSatisfied = performance.run('checkDigest', (performance) {
return entry.requirementsDigest.isSatisfied(elementFactory);
});
if (digestSatisfied) {
return _LinkedBundleProbeResult(
libraryManifests: entry.libraryManifests,
linkedBytes: entry.linkedBytes,
);
}
var failure = performance.run('checkRequirements', (performance) {
return entry.requirements.isSatisfied(
elementFactory: elementFactory,
performance: performance,
);
});
eventsController?.add(
CheckLinkedBundleRequirements(cycle: cycle, failure: failure),
);
if (failure != null) {
return _LinkedBundleProbeResult(
libraryManifests: entry.libraryManifests,
linkedBytes: null,
);
}
return _LinkedBundleProbeResult(
libraryManifests: entry.libraryManifests,
linkedBytes: entry.linkedBytes,
);
});
}
/// The [exception] was caught during the [cycle] linking.
///
/// Throw another exception that wraps the given one, with more information.
Never _throwLibraryCycleLinkException(
LibraryCycle cycle,
Object exception,
StackTrace stackTrace,
) {
var fileContentMap = <String, String>{};
for (var library in cycle.libraries) {
for (var file in library.files) {
fileContentMap[file.path] = file.content;
}
}
throw CaughtExceptionWithFiles(exception, stackTrace, fileContentMap);
}
}
class LibraryContextTestData {
final FileSystemTestData fileSystemTestData;
// TODO(scheglov): Use [libraryCycles] and textual dumps for the driver too.
final List<Set<String>> linkedCycles = [];
/// Keys: the sorted list of library files.
final Map<List<FileTestData>, LibraryCycleTestData> libraryCycles =
LinkedHashMap(
hashCode: Object.hashAll,
equals: const ListEquality<FileTestData>().equals,
);
/// The current instance of [LibraryContext].
LibraryContext? instance;
LibraryContextTestData({required this.fileSystemTestData});
LibraryCycleTestData forCycle(LibraryCycle cycle) {
var files = cycle.libraries.map((library) {
var file = library.file;
return fileSystemTestData.forFile(file.resource, file.uri);
}).toList();
files.sortBy((fileData) => fileData.file.path);
return libraryCycles[files] ??= LibraryCycleTestData();
}
}
class LibraryCycleTestData {
final List<String> getKeys = [];
final List<String> putKeys = [];
}
/// A bundle of linked libraries.
class _LinkedBundleCacheEntry {
/// See [LibraryCycle.nonTransitiveApiSignature].
final String nonTransitiveApiSignature;
/// Digest summarizing the requirements, for a fast validation check.
final RequirementsManifestDigest requirementsDigest;
/// Serialized requirements; parsed lazily if needed.
final Uint8List requirementsBytes;
RequirementsManifest? _requirements;
/// The manifests of libraries in [linkedBytes].
///
/// If we have to relink libraries, we will match new elements against
/// these old manifests, and reuse IDs for not affected elements.
final Map<Uri, LibraryManifestHandle> libraryManifests;
/// The serialized libraries, for [BundleReader].
final Uint8List linkedBytes;
_LinkedBundleCacheEntry({
required this.nonTransitiveApiSignature,
required this.requirementsDigest,
required this.requirementsBytes,
required this.libraryManifests,
required this.linkedBytes,
});
RequirementsManifest get requirements {
return _requirements ??= RequirementsManifest.fromBytes(requirementsBytes);
}
void write({
required ByteStore byteStore,
required String key,
required OperationPerformanceImpl performance,
}) {
var writer = BinaryWriter();
writer.writeStringUtf8(nonTransitiveApiSignature);
requirementsDigest.write(writer);
writer.writeUint8List(requirementsBytes);
writer.writeMap(
libraryManifests,
writeKey: (uri) => writer.writeUri(uri),
writeValue: (manifest) => manifest.write(writer),
);
writer.writeUint8List(linkedBytes);
writer.writeTableTrailer();
var bytes = writer.takeBytes();
byteStore.putGet(key, bytes);
performance.getDataInt('bytes').add(bytes.length);
}
static _LinkedBundleCacheEntry? read({
required ByteStore byteStore,
required String key,
required OperationPerformanceImpl performance,
}) {
var bytes = byteStore.get(key);
if (bytes == null) {
return null;
}
performance.getDataInt('bytesLength').add(bytes.length);
var reader = BinaryReader(bytes);
reader.initFromTableTrailer();
var result = _LinkedBundleCacheEntry(
nonTransitiveApiSignature: reader.readStringUtf8(),
requirementsDigest: RequirementsManifestDigest.read(reader),
requirementsBytes: reader.readUint8List(),
libraryManifests: reader.readMap(
readKey: () => reader.readUri(),
readValue: () => LibraryManifestHandle.read(reader),
),
linkedBytes: reader.readUint8List(),
);
// We have copies of all data.
byteStore.release([key]);
return result;
}
}
class _LinkedBundleProbeResult {
final Map<Uri, LibraryManifestHandle> libraryManifests;
final Uint8List? linkedBytes;
_LinkedBundleProbeResult({
required this.libraryManifests,
required this.linkedBytes,
});
}