blob: 72092ad35b1aca24930cfd4fe861870164efc9a2 [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.
import 'dart:typed_data';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/element/element.dart'
show CompilationUnitElement, LibraryElement;
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.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/exception/exception.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisContext, AnalysisOptionsImpl;
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' as link2;
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/reference.dart';
var counterLinkedLibraries = 0;
var counterLoadedLibraries = 0;
var timerBundleToBytes = Stopwatch(); // TODO(scheglov) use
var timerInputLibraries = Stopwatch();
var timerLinking = Stopwatch();
var timerLoad2 = Stopwatch();
/// Context information necessary to analyze one or more libraries within an
/// [AnalysisDriver].
///
/// Currently this is implemented as a wrapper around [AnalysisContext].
class LibraryContext {
static const _maxLinkedDataInBytes = 64 * 1024 * 1024;
final LibraryContextTestView testView;
final PerformanceLog logger;
final ByteStore byteStore;
final AnalysisSessionImpl analysisSession;
final SummaryDataStore? externalSummaries;
final SummaryDataStore store = SummaryDataStore([]);
/// The size of the linked data that is loaded by this context.
/// When it reaches [_maxLinkedDataInBytes] the whole context is thrown away.
/// We use it as an approximation for the heap size of elements.
final int _linkedDataInBytes = 0;
late final AnalysisContextImpl analysisContext;
late LinkedElementFactory elementFactory;
LibraryContext({
required this.testView,
required AnalysisSessionImpl session,
required PerformanceLog logger,
required ByteStore byteStore,
required AnalysisOptionsImpl analysisOptions,
required DeclaredVariables declaredVariables,
required SourceFactory sourceFactory,
required this.externalSummaries,
}) : logger = logger,
byteStore = byteStore,
analysisSession = session {
var synchronousSession =
SynchronousSession(analysisOptions, declaredVariables);
analysisContext = AnalysisContextImpl(synchronousSession, sourceFactory);
_createElementFactory();
}
/// Computes a [CompilationUnitElement] for the given library/unit pair.
CompilationUnitElement computeUnitElement(FileState library, FileState unit) {
var reference = elementFactory.rootReference
.getChild(library.uriStr)
.getChild('@unit')
.getChild(unit.uriStr);
var element = elementFactory.elementOfReference(reference);
return element as CompilationUnitElement;
}
/// Get the [LibraryElement] for the given library.
LibraryElement getLibraryElement(Uri uri) {
_createElementFactoryTypeProvider();
return elementFactory.libraryOfUri2('$uri');
}
/// We are about to discard this context, mark all libraries invalid.
void invalidAllLibraries() {
elementFactory.invalidateAllLibraries();
}
/// Load data required to access elements of the given [targetLibrary].
void load2(FileState targetLibrary) {
timerLoad2.start();
var librariesTotal = 0;
var librariesLoaded = 0;
var librariesLinked = 0;
var librariesLinkedTimer = Stopwatch();
var inputsTimer = Stopwatch();
var bytesGet = 0;
var bytesPut = 0;
void loadBundle(LibraryCycle cycle) {
if (cycle.libraries.isEmpty ||
elementFactory.hasLibrary(cycle.libraries.first.uriStr)) {
return;
}
librariesTotal += cycle.libraries.length;
cycle.directDependencies.forEach(loadBundle);
var unitsInformativeBytes = <Uri, Uint8List>{};
for (var library in cycle.libraries) {
for (var file in library.libraryFiles) {
unitsInformativeBytes[file.uri] = file.unlinked2.informativeBytes;
}
}
var resolutionKey = cycle.transitiveSignature + '.linked_bundle';
var resolutionBytes = byteStore.get(resolutionKey);
if (resolutionBytes == null) {
librariesLinkedTimer.start();
testView.linkedCycles.add(
cycle.libraries.map((e) => e.path).toSet(),
);
timerInputLibraries.start();
inputsTimer.start();
var inputLibraries = <link2.LinkInputLibrary>[];
for (var libraryFile in cycle.libraries) {
var librarySource = libraryFile.source;
var inputUnits = <link2.LinkInputUnit>[];
var partIndex = -1;
for (var file in libraryFile.libraryFiles) {
var isSynthetic = !file.exists;
var unit = file.parse();
String? partUriStr;
if (partIndex >= 0) {
partUriStr = libraryFile.unlinked2.parts[partIndex];
}
partIndex++;
inputUnits.add(
link2.LinkInputUnit(
// TODO(scheglov) bad, group part data
partDirectiveIndex: partIndex - 1,
partUriStr: partUriStr,
source: file.source,
sourceContent: file.content,
isSynthetic: isSynthetic,
unit: unit,
),
);
}
inputLibraries.add(
link2.LinkInputLibrary(
source: librarySource,
units: inputUnits,
),
);
}
inputsTimer.stop();
timerInputLibraries.stop();
link2.LinkResult linkResult;
try {
timerLinking.start();
linkResult = link2.link(elementFactory, inputLibraries, true);
librariesLinked += cycle.libraries.length;
counterLinkedLibraries += inputLibraries.length;
timerLinking.stop();
} catch (exception, stackTrace) {
_throwLibraryCycleLinkException(cycle, exception, stackTrace);
}
resolutionBytes = linkResult.resolutionBytes;
byteStore.put(resolutionKey, resolutionBytes);
bytesPut += resolutionBytes.length;
counterUnlinkedLinkedBytes += resolutionBytes.length;
librariesLinkedTimer.stop();
} else {
// TODO(scheglov) Take / clear parsed units in files.
bytesGet += resolutionBytes.length;
librariesLoaded += cycle.libraries.length;
elementFactory.addBundle(
BundleReader(
elementFactory: elementFactory,
unitsInformativeBytes: unitsInformativeBytes,
resolutionBytes: resolutionBytes,
),
);
}
}
logger.run('Prepare linked bundles', () {
var libraryCycle = targetLibrary.libraryCycle;
loadBundle(libraryCycle);
logger.writeln(
'[librariesTotal: $librariesTotal]'
'[librariesLoaded: $librariesLoaded]'
'[inputsTimer: ${inputsTimer.elapsedMilliseconds} ms]'
'[librariesLinked: $librariesLinked]'
'[librariesLinkedTimer: ${librariesLinkedTimer.elapsedMilliseconds} ms]'
'[bytesGet: $bytesGet][bytesPut: $bytesPut]',
);
});
// 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();
timerLoad2.stop();
}
/// Return `true` if this context grew too large, and should be recreated.
///
/// It might have been used to analyze libraries that we don't need anymore,
/// and because loading libraries is not very expensive (but not free), the
/// simplest way to get rid of the garbage is to throw away everything.
bool pack() {
return _linkedDataInBytes > _maxLinkedDataInBytes;
}
void _createElementFactory() {
elementFactory = LinkedElementFactory(
analysisContext,
analysisSession,
Reference.root(),
);
if (externalSummaries != null) {
for (var bundle in externalSummaries!.bundles) {
elementFactory.addBundle(
BundleReader(
elementFactory: elementFactory,
resolutionBytes: bundle.resolutionBytes,
unitsInformativeBytes: {},
),
);
}
}
}
/// Ensure that type provider is created.
void _createElementFactoryTypeProvider() {
if (!analysisContext.hasTypeProvider) {
var dartCore = elementFactory.libraryOfUri2('dart:core');
var dartAsync = elementFactory.libraryOfUri2('dart:async');
elementFactory.createTypeProviders(dartCore, dartAsync);
}
}
/// 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 libraryFile in cycle.libraries) {
for (var file in libraryFile.libraryFiles) {
fileContentMap[file.path] = file.content;
}
}
throw CaughtExceptionWithFiles(exception, stackTrace, fileContentMap);
}
}
class LibraryContextTestView {
final List<Set<String>> linkedCycles = [];
}