@@ -27,6 +27,7 @@ import 'package:analyzer/src/dart/analysis/file_tracker.dart';
2727import 'package:analyzer/src/dart/analysis/index.dart' ;
2828import 'package:analyzer/src/dart/analysis/library_analyzer.dart' ;
2929import 'package:analyzer/src/dart/analysis/library_context.dart' ;
30+ import 'package:analyzer/src/dart/analysis/library_diagnostics.dart' ;
3031import 'package:analyzer/src/dart/analysis/performance_logger.dart' ;
3132import 'package:analyzer/src/dart/analysis/results.dart' ;
3233import 'package:analyzer/src/dart/analysis/search.dart' ;
@@ -51,8 +52,6 @@ import 'package:analyzer/src/summary/idl.dart';
5152import 'package:analyzer/src/summary/package_bundle_reader.dart' ;
5253import 'package:analyzer/src/summary2/ast_binary_flags.dart' ;
5354import 'package:analyzer/src/summary2/bundle_writer.dart' ;
54- import 'package:analyzer/src/summary2/data_reader.dart' ;
55- import 'package:analyzer/src/summary2/data_writer.dart' ;
5655import 'package:analyzer/src/summary2/package_bundle_format.dart' ;
5756import 'package:analyzer/src/util/file_paths.dart' as file_paths;
5857import 'package:analyzer/src/util/performance/operation_performance.dart' ;
@@ -108,7 +107,7 @@ testFineAfterLibraryAnalyzerHook;
108107// TODO(scheglov): Clean up the list of implicitly analyzed files.
109108class AnalysisDriver {
110109 /// The version of data format, should be incremented on every format change.
111- static const int DATA_VERSION = 525 ;
110+ static const int DATA_VERSION = 526 ;
112111
113112 /// The number of exception contexts allowed to write. Once this field is
114113 /// zero, we stop writing any new exception contexts in this process.
@@ -282,6 +281,22 @@ class AnalysisDriver {
282281 /// Whether timing data should be gathered during lint rule execution.
283282 final bool _enableLintRuleTiming;
284283
284+ /// The last known diagnostics bundle for a library.
285+ ///
286+ /// It might be still valid, but might be not.
287+ /// We check its requirements to decide.
288+ ///
289+ /// We keep it in memory for performance. There are cases when we edit a
290+ /// file with many transitive clients, and we don't want to deserialize
291+ /// requirements every time. They are almost always satisfied, so we don't
292+ /// analyze libraries, and so deserialization cost would dominate.
293+ ///
294+ /// There might be a way in the future to collapse these requirements to
295+ /// reduce heap usage.
296+ ///
297+ /// Key: [_getLibraryDiagnosticsKey]
298+ final Map <String , LibraryDiagnosticsBundle > _libraryDiagnosticsCache = {};
299+
285300 /// Create a new instance of [AnalysisDriver] .
286301 ///
287302 /// The given [SourceFactory] is cloned to ensure that it does not contain a
@@ -1375,7 +1390,7 @@ class AnalysisDriver {
13751390 }
13761391
13771392 var isLibraryWithPriorityFile = _isLibraryWithPriorityFile (library);
1378- var fileResultBytesMap = < Uri , Uint8List > {};
1393+ var serializedFileResults = < Uri , Uint8List > {};
13791394
13801395 var resolvedUnits = < ResolvedUnitResultImpl > [];
13811396 for (var unitResult in results) {
@@ -1420,7 +1435,7 @@ class AnalysisDriver {
14201435 index: index,
14211436 ).toBuffer ();
14221437 _byteStore.putGet (unitKey, unitBytes);
1423- fileResultBytesMap [unitFile.uri] = unitBytes;
1438+ serializedFileResults [unitFile.uri] = unitBytes;
14241439 }
14251440
14261441 _fileTracker.fileWasAnalyzed (unitFile.path);
@@ -1437,28 +1452,19 @@ class AnalysisDriver {
14371452 if (withFineDependencies && requirements != null ) {
14381453 requirements.removeReqForLibs ({library.file.uri});
14391454
1440- performance.run ('writeResolvedLibrary' , (performance) {
1441- var mapSink = BufferedSink ();
1442- mapSink.writeMap (
1443- fileResultBytesMap,
1444- writeKey: (uri) => mapSink.writeUri (uri),
1445- writeValue: (bytes) => mapSink.writeUint8List (bytes),
1446- );
1447- var mapBytes = mapSink.takeBytes ();
1448-
1449- library.lastResolutionResult = LibraryResolutionResult (
1455+ performance.run ('writeLibraryDiagnostics' , (performance) {
1456+ var bundle = LibraryDiagnosticsBundle (
14501457 requirements: requirements! ,
1451- bytes : mapBytes ,
1458+ serializedFileResults : serializedFileResults ,
14521459 );
1460+ bundle.addValidated (library.libraryCycle.apiSignature);
14531461
1454- var sink = BufferedSink ();
1455- requirements.write (sink);
1456- sink.writeUint8List (mapBytes);
1457- var allBytes = sink.takeBytes ();
1458- performance.getDataInt ('bytes' ).add (allBytes.length);
1462+ var key = _getLibraryDiagnosticsKey (library);
1463+ _libraryDiagnosticsCache[key] = bundle;
14591464
1460- var key = _getResolvedLibraryKey (library);
1461- _byteStore.putGet (key, allBytes);
1465+ var bytes = bundle.toBytes ();
1466+ performance.getDataInt ('bytes' ).add (bytes.length);
1467+ _byteStore.putGet (key, bytes);
14621468 });
14631469
14641470 _scheduler.eventsController.add (
@@ -1761,49 +1767,13 @@ class AnalysisDriver {
17611767 });
17621768
17631769 if (withFineDependencies) {
1764- var fileResultBytesMap = performance.run ('errors(isSatisfied)' , (
1765- performance,
1766- ) {
1767- var reqAndUnitBytes = performance.run ('getBytes' , (_) {
1768- var key = _getResolvedLibraryKey (library);
1769- return _byteStore.get (key);
1770- });
1771-
1772- if (reqAndUnitBytes != null ) {
1773- var elementFactory = libraryContext.elementFactory;
1774-
1775- var reader = SummaryDataReader (reqAndUnitBytes);
1776- var requirements = performance.run ('readRequirements' , (_) {
1777- return RequirementsManifest .read (reader);
1778- });
1779-
1780- var failure = requirements.isSatisfied (
1781- elementFactory: elementFactory,
1782- libraryManifests: elementFactory.libraryManifests,
1783- );
1784- if (failure == null ) {
1785- var mapBytes = reader.readUint8List ();
1786- library.lastResolutionResult = LibraryResolutionResult (
1787- requirements: requirements,
1788- bytes: mapBytes,
1789- );
1790-
1791- var reader2 = SummaryDataReader (mapBytes);
1792- return reader2.readMap (
1793- readKey: () => reader2.readUri (),
1794- readValue: () => reader2.readUint8List (),
1795- );
1796- } else {
1797- scheduler.eventsController.add (
1798- events.GetErrorsCannotReuse (library: library, failure: failure),
1799- );
1800- }
1801- }
1802- return null ;
1803- });
1770+ var bundle = _getLibraryDiagnosticsBundle (
1771+ library: library,
1772+ performance: performance,
1773+ );
18041774
1805- if (fileResultBytesMap != null ) {
1806- var bytes = fileResultBytesMap [file.uri]! ;
1775+ if (bundle != null ) {
1776+ var bytes = bundle.serializedFileResults [file.uri]! ;
18071777 var result = _createErrorsResultFromBytes (file, library, bytes);
18081778 _errorsRequestedFiles.completeAll (path, result);
18091779 return ;
@@ -1876,35 +1846,77 @@ class AnalysisDriver {
18761846 _analyzeFile (path);
18771847 }
18781848
1879- /// Completes the [getResolvedLibrary] request.
1880- void _getResolvedLibrary (String path) {
1881- var file = _fsState.getFileForPath (path);
1882- var kind = file.kind;
1883- switch (kind) {
1884- case LibraryFileKind ():
1885- break ;
1886- case PartFileKind ():
1887- _requestedLibraries.completeAll (path, NotLibraryButPartResult ());
1888- return ;
1889- default :
1890- throw UnimplementedError ('(${kind .runtimeType }) $kind ' );
1891- }
1849+ /// Returns the [LibraryDiagnosticsBundle] for the given [library] .
1850+ ///
1851+ /// The bundle is returned if it is present in the cache, and its
1852+ /// requirements are satisfied. The cache here is in-memory, or the
1853+ /// byte store.
1854+ ///
1855+ /// Returns `null` if the bundle is not available, or its requirements
1856+ /// are not satisfied. In this case the client should re-analyze the
1857+ /// library to produce a new bundle.
1858+ LibraryDiagnosticsBundle ? _getLibraryDiagnosticsBundle ({
1859+ required LibraryFileKind library,
1860+ required OperationPerformanceImpl performance,
1861+ }) {
1862+ return performance.run ('getLibraryDiagnosticsBundle' , (performance) {
1863+ var key = _getLibraryDiagnosticsKey (library);
1864+ var bundle = _libraryDiagnosticsCache[key];
1865+
1866+ var apiSignature = library.libraryCycle.apiSignature;
1867+ if (bundle != null && bundle.isValidated (apiSignature)) {
1868+ performance.getDataInt ('apiSignature' ).increment ();
1869+ return bundle;
1870+ }
18921871
1893- if (_resolvedLibraryCache[path] case var cached? ) {
1894- _requestedLibraries.completeAll (path, cached);
1895- return ;
1896- }
1872+ if (bundle == null ) {
1873+ bundle = performance.run ('readFromBytes' , (performance) {
1874+ var bytes = _byteStore.get (key);
1875+ if (bytes != null ) {
1876+ performance.getDataInt ('hasBytes' ).increment ();
1877+ performance.getDataInt ('bytes' ).add (bytes.length);
1878+ return LibraryDiagnosticsBundle .fromBytes (bytes);
1879+ } else {
1880+ performance.getDataInt ('noBytes' ).increment ();
1881+ }
1882+ return null ;
1883+ });
1884+ if (bundle == null ) {
1885+ return null ;
1886+ }
1887+ _libraryDiagnosticsCache[key] = bundle;
1888+ }
18971889
1898- _analyzeFile (path);
1890+ var elementFactory = libraryContext.elementFactory;
1891+ var failure = bundle.requirements.isSatisfied (
1892+ elementFactory: elementFactory,
1893+ libraryManifests: elementFactory.libraryManifests,
1894+ );
1895+ scheduler.eventsController.add (
1896+ events.CheckLibraryDiagnosticsRequirements (
1897+ library: library,
1898+ failure: failure,
1899+ ),
1900+ );
1901+ if (failure == null ) {
1902+ bundle.addValidated (apiSignature);
1903+ return bundle;
1904+ }
1905+ return null ;
1906+ });
18991907 }
19001908
1901- /// The key to store a resolved library result.
1902- ///
1903- /// The key is based on the path, URI, and content of the library files.
1909+ /// Returns the key for the [LibraryDiagnosticsBundle] in the byte store.
19041910 ///
1905- /// The result contains the fine-grained requirements, and map with
1906- /// diagnostics for each file.
1907- String _getResolvedLibraryKey (LibraryFileKind library) {
1911+ /// The key is a signature that incorporates the analysis options, the content
1912+ /// of the `pubspec.yaml` file, and the paths, URIs, and content hashes of all
1913+ /// files that are part of the [library] . This ensures that the cached bundle
1914+ /// is invalidated if any of these inputs change.
1915+ String _getLibraryDiagnosticsKey (LibraryFileKind library) {
1916+ if (library.diagnosticsBundleKey case var key? ) {
1917+ return key;
1918+ }
1919+
19081920 var keyBuilder = ApiSignature ();
19091921 keyBuilder.addInt (AnalysisDriver .DATA_VERSION );
19101922 keyBuilder.addUint32List (_saltForResolution);
@@ -1921,7 +1933,30 @@ class AnalysisDriver {
19211933 keyBuilder.addString (file.contentHash);
19221934 }
19231935
1924- return '${keyBuilder .toHex ()}.resolved2' ;
1936+ var key = '${keyBuilder .toHex ()}.resolved2' ;
1937+ return library.diagnosticsBundleKey = key;
1938+ }
1939+
1940+ /// Completes the [getResolvedLibrary] request.
1941+ void _getResolvedLibrary (String path) {
1942+ var file = _fsState.getFileForPath (path);
1943+ var kind = file.kind;
1944+ switch (kind) {
1945+ case LibraryFileKind ():
1946+ break ;
1947+ case PartFileKind ():
1948+ _requestedLibraries.completeAll (path, NotLibraryButPartResult ());
1949+ return ;
1950+ default :
1951+ throw UnimplementedError ('(${kind .runtimeType }) $kind ' );
1952+ }
1953+
1954+ if (_resolvedLibraryCache[path] case var cached? ) {
1955+ _requestedLibraries.completeAll (path, cached);
1956+ return ;
1957+ }
1958+
1959+ _analyzeFile (path);
19251960 }
19261961
19271962 /// Return the key to store fully resolved results for the [signature] .
@@ -2036,66 +2071,13 @@ class AnalysisDriver {
20362071 });
20372072
20382073 if (withFineDependencies) {
2039- var fileResultBytesMap = performance.run ('errors(isSatisfied)' , (
2040- performance,
2041- ) {
2042- var elementFactory = libraryContext.elementFactory;
2043-
2044- if (library.lastResolutionResult case var lastResult? ) {
2045- var failure = lastResult.requirements.isSatisfied (
2046- elementFactory: elementFactory,
2047- libraryManifests: elementFactory.libraryManifests,
2048- );
2049- if (failure == null ) {
2050- var reader = SummaryDataReader (lastResult.bytes);
2051- return reader.readMap (
2052- readKey: () => reader.readUri (),
2053- readValue: () => reader.readUint8List (),
2054- );
2055- }
2056- }
2057-
2058- var reqAndUnitBytes = performance.run ('getBytes' , (_) {
2059- var key = _getResolvedLibraryKey (library);
2060- return _byteStore.get (key);
2061- });
2062-
2063- if (reqAndUnitBytes != null ) {
2064- var reader = SummaryDataReader (reqAndUnitBytes);
2065- var requirements = performance.run ('readRequirements' , (_) {
2066- return RequirementsManifest .read (reader);
2067- });
2068-
2069- var failure = requirements.isSatisfied (
2070- elementFactory: elementFactory,
2071- libraryManifests: elementFactory.libraryManifests,
2072- );
2073- if (failure == null ) {
2074- var mapBytes = reader.readUint8List ();
2075- library.lastResolutionResult = LibraryResolutionResult (
2076- requirements: requirements,
2077- bytes: mapBytes,
2078- );
2079-
2080- var reader2 = SummaryDataReader (mapBytes);
2081- return reader2.readMap (
2082- readKey: () => reader2.readUri (),
2083- readValue: () => reader2.readUint8List (),
2084- );
2085- } else {
2086- scheduler.eventsController.add (
2087- events.ProduceErrorsCannotReuse (
2088- library: library,
2089- failure: failure,
2090- ),
2091- );
2092- }
2093- }
2094- return null ;
2095- });
2074+ var bundle = _getLibraryDiagnosticsBundle (
2075+ library: library,
2076+ performance: performance,
2077+ );
20962078
2097- if (fileResultBytesMap != null ) {
2098- for (var fileEntry in fileResultBytesMap .entries) {
2079+ if (bundle != null ) {
2080+ for (var fileEntry in bundle.serializedFileResults .entries) {
20992081 var file = library.files
21002082 .where ((file) => file.uri == fileEntry.key)
21012083 .single;
@@ -2181,6 +2163,11 @@ class AnalysisDriver {
21812163 accumulatedAffected.add (file.path);
21822164 }
21832165
2166+ if (_fsState.getExistingFromPath (path) case var changedFile? ) {
2167+ var changedLibrary = changedFile.kind.library;
2168+ changedLibrary? .invalidateDiagnosticsBundleKey ();
2169+ }
2170+
21842171 _libraryContext? .elementFactory.replaceAnalysisSession (
21852172 AnalysisSessionImpl (this ),
21862173 );
0 commit comments