Index: pkg/analyzer/lib/src/dart/analysis/driver.dart |
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart |
index 88358a81984fb59eb5c9dd860bfa14e1cf06ca08..d96e63daa15ccad290dd73e509a663c6854690ff 100644 |
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart |
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart |
@@ -146,6 +146,13 @@ class AnalysisDriver { |
final _fileContentHashMap = <String, String>{}; |
/** |
+ * The API signatures corresponding to [_fileContentHashMap]. |
+ * |
+ * It maps file paths to the unlinked API signatures. |
+ */ |
+ final _fileApiSignatureMap = <String, String>{}; |
+ |
+ /** |
* Mapping from library URIs to the dependency signature of the library. |
*/ |
final _dependencySignatureMap = <Uri, String>{}; |
@@ -501,7 +508,7 @@ class AnalysisDriver { |
Source fileSource = _resourceProvider.getFile(path).createSource(); |
Uri uri = _sourceFactory.restoreUri(fileSource); |
Source source = _resourceProvider.getFile(path).createSource(uri); |
- return new _File(this, source); |
+ return new _File.forResolution(this, source); |
} |
/** |
@@ -516,25 +523,28 @@ class AnalysisDriver { |
} |
int numOfFiles = _filesToVerifyUnlinkedSignature.length; |
_logger.run('Verify API signatures of $numOfFiles files', () { |
+ bool hasMismatch = false; |
for (String path in _filesToVerifyUnlinkedSignature) { |
- _File file = _fileForPath(path); |
- // Get the existing old API signature, maybe null. |
- String oldSignature = file.currentUnlinked?.apiSignature; |
- // Clear the content hash cache, so force the file reading. |
- _fileContentHashMap.remove(path); |
+ String oldSignature = _fileApiSignatureMap[path]; |
// Compute the new API signature. |
- String newSignature = file.unlinked.apiSignature; |
- // If the signatures are not the same, then potentially every linked |
- // library is inconsistent and should be recomputed, and every explicit |
- // file has inconsistent analysis results which also should be recomputed. |
- if (oldSignature != newSignature) { |
- _logger.writeln('API signature mismatch found for $file.'); |
- _dependencySignatureMap.clear(); |
- _filesToAnalyze.addAll(_explicitFiles); |
- // Stop the verification, and restart analysis. |
- break; |
+ // _File.forResolution() also updates the content hash in the cache. |
Paul Berry
2016/10/31 16:30:25
Nit: it might be clearer to say "_fileForPath() al
|
+ _File newFile = _fileForPath(path); |
+ String newSignature = newFile.unlinked.apiSignature; |
+ // If the old API signature is not null, then the file was used to |
+ // compute at least one dependency signature. If the new API signature |
+ // is different, then potentially all dependency signatures and |
+ // resolution results are invalid. |
+ if (oldSignature != null && oldSignature != newSignature) { |
+ _logger.writeln('API signature mismatch found for $newFile.'); |
+ hasMismatch = true; |
Paul Berry
2016/10/31 16:30:25
Can you add a comment explaining why it's not ok t
scheglov
2016/10/31 17:49:07
The code was restructured that the "break" does no
|
} |
} |
+ if (hasMismatch) { |
+ _dependencySignatureMap.clear(); |
+ _filesToAnalyze.addAll(_explicitFiles); |
+ } else { |
+ _logger.writeln('All API signatures match.'); |
+ } |
_filesToVerifyUnlinkedSignature.clear(); |
}); |
} |
@@ -665,13 +675,21 @@ class PerformanceLogSection { |
/** |
* Information about a file being analyzed, explicitly or implicitly. |
* |
- * It keeps a consistent view on its [content], [contentHash] and [unit]. |
+ * It provides a stable, consistent view on its [content], [contentHash], |
+ * [unlinked] and [unit]. |
* |
- * Instances of this class may only be used during computing a single analysis |
- * result and should not be cached anywhere. We need this limitation to prevent |
- * references from caches to the resolved [unit], so to element models, etc. |
- * The data structures should be short lived - computed, returned to the client, |
- * processed there and quickly thrown away. |
+ * A new file can be created either for resolution or for linking. |
+ * |
+ * When file is created for linking, it assumes that the file has not been |
+ * changed since the last time its content was read and hashed. So, this |
+ * content hash is also used to look for an existing unlinked bundle in the |
+ * [AnalysisDriver._byteStore]. If any of the caches is empty, the file is |
+ * created without caching, as for resolution. |
+ * |
+ * When file is created for resolution, we always read the content, compute its |
+ * hash and update [AnalysisDriver._fileContentHashMap], parse the content, |
+ * compute the unlinked bundle and update [AnalysisDriver._fileApiSignatureMap]. |
+ * It is important to keep these two maps in sync. |
*/ |
class _File { |
/** |
@@ -684,128 +702,98 @@ class _File { |
*/ |
final Source source; |
- String _content; |
- String _contentHash; |
- CompilationUnit _unit; |
- |
- _File(this.driver, this.source); |
- |
/** |
- * Return the current content of the file. |
- * |
- * If the [_content] field if it is still `null`, get the content from the |
- * content cache or from the [source]. If the content cannot be accessed |
- * because of an exception, it considers to be an empty string. |
- * |
- * When a new content is read, the new [_contentHash] is computed and the |
- * current file state is updated. |
+ * The [source] content, or `null` if this file is for linking. |
*/ |
- String get content { |
- if (_content == null) { |
- _readContentAndComputeHash(); |
- } |
- return _content; |
- } |
+ final String content; |
/** |
- * Ensure that the content hash is set for this [_File] instance, return it. |
- * |
- * If the content hash has already been set for this [_File] instance, it is |
- * not updated here. But the hash value might be updated on [content] access. |
- * |
- * If the content hash is known in the current file state, use it. |
- * |
- * Otherwise, read the [content], compute the hash, put it into the current |
- * file state, and update the [contentHash] field. |
- * |
- * The client should not remember values of this property, because its value |
- * might change when [content] is read and the hash is recomputed. |
+ * The [source] content hash, not `null` even if [content] is `null`. |
*/ |
- String get contentHash { |
- _contentHash ??= currentContentHash; |
- if (_contentHash == null) { |
- _readContentAndComputeHash(); |
- } |
- return _contentHash; |
- } |
+ final String contentHash; |
/** |
- * Return the hash of the file content in the current file state, or `null` |
- * if the current file state does not know the current file content hash. |
+ * The unlinked bundle, not `null`. |
*/ |
- String get currentContentHash { |
- return driver._fileContentHashMap[path]; |
- } |
+ final PackageBundle unlinked; |
/** |
- * Return the unlinked bundle for the current file state, or `null`. |
+ * The unresolved unit, not `null` if this file is for resolution. |
*/ |
- PackageBundle get currentUnlinked { |
- String hash = currentContentHash; |
- if (hash != null) { |
- String key = '$hash.unlinked'; |
+ final CompilationUnit unit; |
+ |
+ factory _File.forLinking(AnalysisDriver driver, Source source) { |
+ // If we have enough cached information, use it. |
+ String contentHash = driver._fileContentHashMap[source.fullName]; |
+ if (contentHash != null) { |
+ String key = '$contentHash.unlinked'; |
List<int> bytes = driver._byteStore.get(key); |
if (bytes != null) { |
- return new PackageBundle.fromBuffer(bytes); |
+ PackageBundle unlinked = new PackageBundle.fromBuffer(bytes); |
+ return new _File._(driver, source, null, contentHash, unlinked, null); |
} |
} |
- return null; |
+ // Otherwise, read the source, parse and build a new unlinked bundle. |
+ return new _File.forResolution(driver, source); |
} |
- String get path => source.fullName; |
- |
- /** |
- * Return the unresolved [CompilationUnit] of the file. |
- * |
- * Performing resolution and computing errors is done in a separate analysis |
- * context. In the future we might push the existing unresolved unit into the |
- * analysis context, so at some point the unit might become resolved. |
- */ |
- CompilationUnit get unit { |
- if (_unit == null) { |
- AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; |
- |
- CharSequenceReader reader = new CharSequenceReader(content); |
- Scanner scanner = new Scanner(source, reader, errorListener); |
- scanner.scanGenericMethodComments = driver._analysisOptions.strongMode; |
- Token token = scanner.tokenize(); |
- LineInfo lineInfo = new LineInfo(scanner.lineStarts); |
- |
- Parser parser = new Parser(source, errorListener); |
- parser.parseGenericMethodComments = driver._analysisOptions.strongMode; |
- _unit = parser.parseCompilationUnit(token); |
- _unit.lineInfo = lineInfo; |
+ factory _File.forResolution(AnalysisDriver driver, Source source) { |
+ String path = source.fullName; |
+ // Read the content. |
+ String content; |
+ try { |
+ content = driver._contentCache.getContents(source); |
+ content ??= source.contents.data; |
+ } catch (_) { |
+ content = ''; |
+ // TODO(scheglov) We fail to report URI_DOES_NOT_EXIST. |
+ // On one hand we need to provide an unlinked bundle to prevent |
+ // analysis context from reading the file (we want it to work |
+ // hermetically and handle one one file at a time). OTOH, |
+ // ResynthesizerResultProvider happily reports that any source in the |
+ // SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1` |
+ // for missing files. Maybe add this feature to SummaryDataStore? |
} |
- return _unit; |
- } |
- |
- /** |
- * Return the unlinked bundle for the current file state. |
- * |
- * If the file [contentHash] is cached, try to load the bundle with this |
- * hash. Otherwise, read the content, compute the new hash and try to find |
- * the existing bundle, or parse the content and compute a new bundle. |
- */ |
- PackageBundle get unlinked { |
- String key = '$contentHash.unlinked'; |
- List<int> bytes = driver._byteStore.get(key); |
- if (bytes == null) { |
- driver._logger.run('Create unlinked for $this', () { |
- UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); |
- PackageBundleAssembler assembler = new PackageBundleAssembler(); |
- assembler.addUnlinkedUnitWithHash( |
- uri.toString(), unlinkedUnit, contentHash); |
- bytes = assembler.assemble().toBuffer(); |
- driver._byteStore.put(key, bytes); |
- }); |
+ // Compute the content hash. |
+ List<int> textBytes = UTF8.encode(content); |
+ List<int> hashBytes = md5.convert(textBytes).bytes; |
+ String contentHash = hex.encode(hashBytes); |
+ driver._fileContentHashMap[path] = contentHash; |
+ // Parse the unit. |
+ CompilationUnit unit = _parse(driver, source, content); |
+ // Prepare the unlinked bundle. |
+ PackageBundle unlinked; |
+ { |
+ String key = '$contentHash.unlinked'; |
+ List<int> bytes = driver._byteStore.get(key); |
+ if (bytes == null) { |
+ driver._logger.run('Create unlinked for $path', () { |
+ UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); |
+ PackageBundleAssembler assembler = new PackageBundleAssembler(); |
+ assembler.addUnlinkedUnitWithHash( |
+ source.uri.toString(), unlinkedUnit, contentHash); |
+ bytes = assembler.assemble().toBuffer(); |
+ driver._byteStore.put(key, bytes); |
+ }); |
+ } |
+ unlinked = new PackageBundle.fromBuffer(bytes); |
+ driver._fileApiSignatureMap[path] = unlinked.apiSignature; |
} |
- return new PackageBundle.fromBuffer(bytes); |
+ // Update the current file state. |
+ return new _File._(driver, source, content, contentHash, unlinked, unit); |
} |
+ _File._(this.driver, this.source, this.content, this.contentHash, |
+ this.unlinked, this.unit); |
+ |
+ String get path => source.fullName; |
+ |
Uri get uri => source.uri; |
/** |
* Return the [_File] for the [uri] referenced in this file. |
+ * |
+ * This [_File] can be used only for linking. |
*/ |
_File resolveUri(String uri) { |
// TODO(scheglov) Consider removing this caching after implementing other |
@@ -813,42 +801,30 @@ class _File { |
Source uriSource = driver._uriResolutionCache |
.putIfAbsent(this.uri, () => <String, Source>{}) |
.putIfAbsent(uri, () => driver._sourceFactory.resolveUri(source, uri)); |
- return new _File(driver, uriSource); |
+ return new _File.forLinking(driver, uriSource); |
} |
@override |
- String toString() => uri.toString(); |
+ String toString() => path; |
/** |
- * Fill the [_content] and [_contentHash] fields. |
- * |
- * If the [_content] field is still `null`, get the content from the |
- * content cache or from the [source]. If the content cannot be accessed |
- * because of an exception, it is considered to be an empty string. |
- * |
- * When a new content is read, the new [_contentHash] should be computed and |
- * the current file state should be updated. |
+ * Return the parsed unresolved [CompilationUnit] for the given [content]. |
*/ |
- void _readContentAndComputeHash() { |
- try { |
- _content = driver._contentCache.getContents(source); |
- _content ??= source.contents.data; |
- } catch (_) { |
- _content = ''; |
- // TODO(scheglov) We fail to report URI_DOES_NOT_EXIST. |
- // On one hand we need to provide an unlinked bundle to prevent |
- // analysis context from reading the file (we want it to work |
- // hermetically and handle one one file at a time). OTOH, |
- // ResynthesizerResultProvider happily reports that any source in the |
- // SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1` |
- // for missing files. Maybe add this feature to SummaryDataStore? |
- } |
- // Compute the content hash. |
- List<int> textBytes = UTF8.encode(_content); |
- List<int> hashBytes = md5.convert(textBytes).bytes; |
- _contentHash = hex.encode(hashBytes); |
- // Update the current file state. |
- driver._fileContentHashMap[path] = _contentHash; |
+ static CompilationUnit _parse( |
+ AnalysisDriver driver, Source source, String content) { |
+ AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; |
+ |
+ CharSequenceReader reader = new CharSequenceReader(content); |
+ Scanner scanner = new Scanner(source, reader, errorListener); |
+ scanner.scanGenericMethodComments = driver._analysisOptions.strongMode; |
+ Token token = scanner.tokenize(); |
+ LineInfo lineInfo = new LineInfo(scanner.lineStarts); |
+ |
+ Parser parser = new Parser(source, errorListener); |
+ parser.parseGenericMethodComments = driver._analysisOptions.strongMode; |
+ CompilationUnit unit = parser.parseCompilationUnit(token); |
+ unit.lineInfo = lineInfo; |
+ return unit; |
} |
} |