Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1340)

Side by Side Diff: pkg/analyzer/lib/src/dart/analysis/driver.dart

Issue 2451973002: Fix and simplify _File creation. (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'dart:async'; 5 import 'dart:async';
6 import 'dart:collection'; 6 import 'dart:collection';
7 import 'dart:convert'; 7 import 'dart:convert';
8 8
9 import 'package:analyzer/dart/ast/ast.dart'; 9 import 'package:analyzer/dart/ast/ast.dart';
10 import 'package:analyzer/dart/ast/token.dart'; 10 import 'package:analyzer/dart/ast/token.dart';
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after
139 final _uriResolutionCache = <Uri, Map<String, Source>>{}; 139 final _uriResolutionCache = <Uri, Map<String, Source>>{};
140 140
141 /** 141 /**
142 * The current file state. 142 * The current file state.
143 * 143 *
144 * It maps file paths to the MD5 hash of the file content. 144 * It maps file paths to the MD5 hash of the file content.
145 */ 145 */
146 final _fileContentHashMap = <String, String>{}; 146 final _fileContentHashMap = <String, String>{};
147 147
148 /** 148 /**
149 * The API signatures corresponding to [_fileContentHashMap].
150 *
151 * It maps file paths to the unlinked API signatures.
152 */
153 final _fileApiSignatureMap = <String, String>{};
154
155 /**
149 * Mapping from library URIs to the dependency signature of the library. 156 * Mapping from library URIs to the dependency signature of the library.
150 */ 157 */
151 final _dependencySignatureMap = <Uri, String>{}; 158 final _dependencySignatureMap = <Uri, String>{};
152 159
153 /** 160 /**
154 * TODO(scheglov) document and improve 161 * TODO(scheglov) document and improve
155 */ 162 */
156 final _hasWorkStreamController = new StreamController<String>(); 163 final _hasWorkStreamController = new StreamController<String>();
157 164
158 AnalysisDriver(this._logger, this._resourceProvider, this._byteStore, 165 AnalysisDriver(this._logger, this._resourceProvider, this._byteStore,
(...skipping 335 matching lines...) Expand 10 before | Expand all | Expand 10 after
494 }); 501 });
495 } 502 }
496 503
497 /** 504 /**
498 * Return the [_File] for the given [path] in [_sourceFactory]. 505 * Return the [_File] for the given [path] in [_sourceFactory].
499 */ 506 */
500 _File _fileForPath(String path) { 507 _File _fileForPath(String path) {
501 Source fileSource = _resourceProvider.getFile(path).createSource(); 508 Source fileSource = _resourceProvider.getFile(path).createSource();
502 Uri uri = _sourceFactory.restoreUri(fileSource); 509 Uri uri = _sourceFactory.restoreUri(fileSource);
503 Source source = _resourceProvider.getFile(path).createSource(uri); 510 Source source = _resourceProvider.getFile(path).createSource(uri);
504 return new _File(this, source); 511 return new _File.forResolution(this, source);
505 } 512 }
506 513
507 /** 514 /**
508 * Verify the API signatures for the changed files, and decide which linked 515 * Verify the API signatures for the changed files, and decide which linked
509 * libraries should be invalidated, and files reanalyzed. 516 * libraries should be invalidated, and files reanalyzed.
510 * 517 *
511 * TODO(scheglov) I see that adding a local var changes (full) API signature. 518 * TODO(scheglov) I see that adding a local var changes (full) API signature.
512 */ 519 */
513 void _verifyUnlinkedSignatureOfChangedFiles() { 520 void _verifyUnlinkedSignatureOfChangedFiles() {
514 if (_filesToVerifyUnlinkedSignature.isEmpty) { 521 if (_filesToVerifyUnlinkedSignature.isEmpty) {
515 return; 522 return;
516 } 523 }
517 int numOfFiles = _filesToVerifyUnlinkedSignature.length; 524 int numOfFiles = _filesToVerifyUnlinkedSignature.length;
518 _logger.run('Verify API signatures of $numOfFiles files', () { 525 _logger.run('Verify API signatures of $numOfFiles files', () {
526 bool hasMismatch = false;
519 for (String path in _filesToVerifyUnlinkedSignature) { 527 for (String path in _filesToVerifyUnlinkedSignature) {
520 _File file = _fileForPath(path); 528 String oldSignature = _fileApiSignatureMap[path];
521 // Get the existing old API signature, maybe null.
522 String oldSignature = file.currentUnlinked?.apiSignature;
523 // Clear the content hash cache, so force the file reading.
524 _fileContentHashMap.remove(path);
525 // Compute the new API signature. 529 // Compute the new API signature.
526 String newSignature = file.unlinked.apiSignature; 530 // _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
527 // If the signatures are not the same, then potentially every linked 531 _File newFile = _fileForPath(path);
528 // library is inconsistent and should be recomputed, and every explicit 532 String newSignature = newFile.unlinked.apiSignature;
529 // file has inconsistent analysis results which also should be recompute d. 533 // If the old API signature is not null, then the file was used to
530 if (oldSignature != newSignature) { 534 // compute at least one dependency signature. If the new API signature
531 _logger.writeln('API signature mismatch found for $file.'); 535 // is different, then potentially all dependency signatures and
532 _dependencySignatureMap.clear(); 536 // resolution results are invalid.
533 _filesToAnalyze.addAll(_explicitFiles); 537 if (oldSignature != null && oldSignature != newSignature) {
534 // Stop the verification, and restart analysis. 538 _logger.writeln('API signature mismatch found for $newFile.');
535 break; 539 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
536 } 540 }
537 } 541 }
542 if (hasMismatch) {
543 _dependencySignatureMap.clear();
544 _filesToAnalyze.addAll(_explicitFiles);
545 } else {
546 _logger.writeln('All API signatures match.');
547 }
538 _filesToVerifyUnlinkedSignature.clear(); 548 _filesToVerifyUnlinkedSignature.clear();
539 }); 549 });
540 } 550 }
541 } 551 }
542 552
543 /** 553 /**
544 * The result of analyzing of a single file. 554 * The result of analyzing of a single file.
545 * 555 *
546 * These results are self-consistent, i.e. [content], [contentHash], the 556 * These results are self-consistent, i.e. [content], [contentHash], the
547 * resolved [unit] correspond to each other. All referenced elements, even 557 * resolved [unit] correspond to each other. All referenced elements, even
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
658 _timer.stop(); 668 _timer.stop();
659 _logger._level--; 669 _logger._level--;
660 int ms = _timer.elapsedMilliseconds; 670 int ms = _timer.elapsedMilliseconds;
661 _logger.writeln('--- $_msg in $ms ms.'); 671 _logger.writeln('--- $_msg in $ms ms.');
662 } 672 }
663 } 673 }
664 674
665 /** 675 /**
666 * Information about a file being analyzed, explicitly or implicitly. 676 * Information about a file being analyzed, explicitly or implicitly.
667 * 677 *
668 * It keeps a consistent view on its [content], [contentHash] and [unit]. 678 * It provides a stable, consistent view on its [content], [contentHash],
679 * [unlinked] and [unit].
669 * 680 *
670 * Instances of this class may only be used during computing a single analysis 681 * A new file can be created either for resolution or for linking.
671 * result and should not be cached anywhere. We need this limitation to prevent 682 *
672 * references from caches to the resolved [unit], so to element models, etc. 683 * When file is created for linking, it assumes that the file has not been
673 * The data structures should be short lived - computed, returned to the client, 684 * changed since the last time its content was read and hashed. So, this
674 * processed there and quickly thrown away. 685 * content hash is also used to look for an existing unlinked bundle in the
686 * [AnalysisDriver._byteStore]. If any of the caches is empty, the file is
687 * created without caching, as for resolution.
688 *
689 * When file is created for resolution, we always read the content, compute its
690 * hash and update [AnalysisDriver._fileContentHashMap], parse the content,
691 * compute the unlinked bundle and update [AnalysisDriver._fileApiSignatureMap].
692 * It is important to keep these two maps in sync.
675 */ 693 */
676 class _File { 694 class _File {
677 /** 695 /**
678 * The driver instance that is used to access [SourceFactory] and caches. 696 * The driver instance that is used to access [SourceFactory] and caches.
679 */ 697 */
680 final AnalysisDriver driver; 698 final AnalysisDriver driver;
681 699
682 /** 700 /**
683 * The [Source] this [_File] instance represent. 701 * The [Source] this [_File] instance represent.
684 */ 702 */
685 final Source source; 703 final Source source;
686 704
687 String _content; 705 /**
688 String _contentHash; 706 * The [source] content, or `null` if this file is for linking.
689 CompilationUnit _unit; 707 */
690 708 final String content;
691 _File(this.driver, this.source);
692 709
693 /** 710 /**
694 * Return the current content of the file. 711 * The [source] content hash, not `null` even if [content] is `null`.
695 *
696 * If the [_content] field if it is still `null`, get the content from the
697 * content cache or from the [source]. If the content cannot be accessed
698 * because of an exception, it considers to be an empty string.
699 *
700 * When a new content is read, the new [_contentHash] is computed and the
701 * current file state is updated.
702 */ 712 */
703 String get content { 713 final String contentHash;
704 if (_content == null) { 714
705 _readContentAndComputeHash(); 715 /**
716 * The unlinked bundle, not `null`.
717 */
718 final PackageBundle unlinked;
719
720 /**
721 * The unresolved unit, not `null` if this file is for resolution.
722 */
723 final CompilationUnit unit;
724
725 factory _File.forLinking(AnalysisDriver driver, Source source) {
726 // If we have enough cached information, use it.
727 String contentHash = driver._fileContentHashMap[source.fullName];
728 if (contentHash != null) {
729 String key = '$contentHash.unlinked';
730 List<int> bytes = driver._byteStore.get(key);
731 if (bytes != null) {
732 PackageBundle unlinked = new PackageBundle.fromBuffer(bytes);
733 return new _File._(driver, source, null, contentHash, unlinked, null);
734 }
706 } 735 }
707 return _content; 736 // Otherwise, read the source, parse and build a new unlinked bundle.
737 return new _File.forResolution(driver, source);
708 } 738 }
709 739
710 /** 740 factory _File.forResolution(AnalysisDriver driver, Source source) {
711 * Ensure that the content hash is set for this [_File] instance, return it. 741 String path = source.fullName;
712 * 742 // Read the content.
713 * If the content hash has already been set for this [_File] instance, it is 743 String content;
714 * not updated here. But the hash value might be updated on [content] access. 744 try {
715 * 745 content = driver._contentCache.getContents(source);
716 * If the content hash is known in the current file state, use it. 746 content ??= source.contents.data;
717 * 747 } catch (_) {
718 * Otherwise, read the [content], compute the hash, put it into the current 748 content = '';
719 * file state, and update the [contentHash] field. 749 // TODO(scheglov) We fail to report URI_DOES_NOT_EXIST.
720 * 750 // On one hand we need to provide an unlinked bundle to prevent
721 * The client should not remember values of this property, because its value 751 // analysis context from reading the file (we want it to work
722 * might change when [content] is read and the hash is recomputed. 752 // hermetically and handle one one file at a time). OTOH,
723 */ 753 // ResynthesizerResultProvider happily reports that any source in the
724 String get contentHash { 754 // SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1`
725 _contentHash ??= currentContentHash; 755 // for missing files. Maybe add this feature to SummaryDataStore?
726 if (_contentHash == null) {
727 _readContentAndComputeHash();
728 } 756 }
729 return _contentHash; 757 // Compute the content hash.
758 List<int> textBytes = UTF8.encode(content);
759 List<int> hashBytes = md5.convert(textBytes).bytes;
760 String contentHash = hex.encode(hashBytes);
761 driver._fileContentHashMap[path] = contentHash;
762 // Parse the unit.
763 CompilationUnit unit = _parse(driver, source, content);
764 // Prepare the unlinked bundle.
765 PackageBundle unlinked;
766 {
767 String key = '$contentHash.unlinked';
768 List<int> bytes = driver._byteStore.get(key);
769 if (bytes == null) {
770 driver._logger.run('Create unlinked for $path', () {
771 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit);
772 PackageBundleAssembler assembler = new PackageBundleAssembler();
773 assembler.addUnlinkedUnitWithHash(
774 source.uri.toString(), unlinkedUnit, contentHash);
775 bytes = assembler.assemble().toBuffer();
776 driver._byteStore.put(key, bytes);
777 });
778 }
779 unlinked = new PackageBundle.fromBuffer(bytes);
780 driver._fileApiSignatureMap[path] = unlinked.apiSignature;
781 }
782 // Update the current file state.
783 return new _File._(driver, source, content, contentHash, unlinked, unit);
730 } 784 }
731 785
732 /** 786 _File._(this.driver, this.source, this.content, this.contentHash,
733 * Return the hash of the file content in the current file state, or `null` 787 this.unlinked, this.unit);
734 * if the current file state does not know the current file content hash.
735 */
736 String get currentContentHash {
737 return driver._fileContentHashMap[path];
738 }
739
740 /**
741 * Return the unlinked bundle for the current file state, or `null`.
742 */
743 PackageBundle get currentUnlinked {
744 String hash = currentContentHash;
745 if (hash != null) {
746 String key = '$hash.unlinked';
747 List<int> bytes = driver._byteStore.get(key);
748 if (bytes != null) {
749 return new PackageBundle.fromBuffer(bytes);
750 }
751 }
752 return null;
753 }
754 788
755 String get path => source.fullName; 789 String get path => source.fullName;
756 790
757 /**
758 * Return the unresolved [CompilationUnit] of the file.
759 *
760 * Performing resolution and computing errors is done in a separate analysis
761 * context. In the future we might push the existing unresolved unit into the
762 * analysis context, so at some point the unit might become resolved.
763 */
764 CompilationUnit get unit {
765 if (_unit == null) {
766 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
767
768 CharSequenceReader reader = new CharSequenceReader(content);
769 Scanner scanner = new Scanner(source, reader, errorListener);
770 scanner.scanGenericMethodComments = driver._analysisOptions.strongMode;
771 Token token = scanner.tokenize();
772 LineInfo lineInfo = new LineInfo(scanner.lineStarts);
773
774 Parser parser = new Parser(source, errorListener);
775 parser.parseGenericMethodComments = driver._analysisOptions.strongMode;
776 _unit = parser.parseCompilationUnit(token);
777 _unit.lineInfo = lineInfo;
778 }
779 return _unit;
780 }
781
782 /**
783 * Return the unlinked bundle for the current file state.
784 *
785 * If the file [contentHash] is cached, try to load the bundle with this
786 * hash. Otherwise, read the content, compute the new hash and try to find
787 * the existing bundle, or parse the content and compute a new bundle.
788 */
789 PackageBundle get unlinked {
790 String key = '$contentHash.unlinked';
791 List<int> bytes = driver._byteStore.get(key);
792 if (bytes == null) {
793 driver._logger.run('Create unlinked for $this', () {
794 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit);
795 PackageBundleAssembler assembler = new PackageBundleAssembler();
796 assembler.addUnlinkedUnitWithHash(
797 uri.toString(), unlinkedUnit, contentHash);
798 bytes = assembler.assemble().toBuffer();
799 driver._byteStore.put(key, bytes);
800 });
801 }
802 return new PackageBundle.fromBuffer(bytes);
803 }
804
805 Uri get uri => source.uri; 791 Uri get uri => source.uri;
806 792
807 /** 793 /**
808 * Return the [_File] for the [uri] referenced in this file. 794 * Return the [_File] for the [uri] referenced in this file.
795 *
796 * This [_File] can be used only for linking.
809 */ 797 */
810 _File resolveUri(String uri) { 798 _File resolveUri(String uri) {
811 // TODO(scheglov) Consider removing this caching after implementing other 799 // TODO(scheglov) Consider removing this caching after implementing other
812 // optimizations, e.g. changeFile() optimization. 800 // optimizations, e.g. changeFile() optimization.
813 Source uriSource = driver._uriResolutionCache 801 Source uriSource = driver._uriResolutionCache
814 .putIfAbsent(this.uri, () => <String, Source>{}) 802 .putIfAbsent(this.uri, () => <String, Source>{})
815 .putIfAbsent(uri, () => driver._sourceFactory.resolveUri(source, uri)); 803 .putIfAbsent(uri, () => driver._sourceFactory.resolveUri(source, uri));
816 return new _File(driver, uriSource); 804 return new _File.forLinking(driver, uriSource);
817 } 805 }
818 806
819 @override 807 @override
820 String toString() => uri.toString(); 808 String toString() => path;
821 809
822 /** 810 /**
823 * Fill the [_content] and [_contentHash] fields. 811 * Return the parsed unresolved [CompilationUnit] for the given [content].
824 *
825 * If the [_content] field is still `null`, get the content from the
826 * content cache or from the [source]. If the content cannot be accessed
827 * because of an exception, it is considered to be an empty string.
828 *
829 * When a new content is read, the new [_contentHash] should be computed and
830 * the current file state should be updated.
831 */ 812 */
832 void _readContentAndComputeHash() { 813 static CompilationUnit _parse(
833 try { 814 AnalysisDriver driver, Source source, String content) {
834 _content = driver._contentCache.getContents(source); 815 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
835 _content ??= source.contents.data; 816
836 } catch (_) { 817 CharSequenceReader reader = new CharSequenceReader(content);
837 _content = ''; 818 Scanner scanner = new Scanner(source, reader, errorListener);
838 // TODO(scheglov) We fail to report URI_DOES_NOT_EXIST. 819 scanner.scanGenericMethodComments = driver._analysisOptions.strongMode;
839 // On one hand we need to provide an unlinked bundle to prevent 820 Token token = scanner.tokenize();
840 // analysis context from reading the file (we want it to work 821 LineInfo lineInfo = new LineInfo(scanner.lineStarts);
841 // hermetically and handle one one file at a time). OTOH, 822
842 // ResynthesizerResultProvider happily reports that any source in the 823 Parser parser = new Parser(source, errorListener);
843 // SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1` 824 parser.parseGenericMethodComments = driver._analysisOptions.strongMode;
844 // for missing files. Maybe add this feature to SummaryDataStore? 825 CompilationUnit unit = parser.parseCompilationUnit(token);
845 } 826 unit.lineInfo = lineInfo;
846 // Compute the content hash. 827 return unit;
847 List<int> textBytes = UTF8.encode(_content);
848 List<int> hashBytes = md5.convert(textBytes).bytes;
849 _contentHash = hex.encode(hashBytes);
850 // Update the current file state.
851 driver._fileContentHashMap[path] = _contentHash;
852 } 828 }
853 } 829 }
854 830
855 /** 831 /**
856 * TODO(scheglov) document 832 * TODO(scheglov) document
857 */ 833 */
858 class _LibraryContext { 834 class _LibraryContext {
859 final _File file; 835 final _File file;
860 final _LibraryNode node; 836 final _LibraryNode node;
861 final SummaryDataStore store; 837 final SummaryDataStore store;
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
983 } 959 }
984 } 960 }
985 for (UnlinkedExportPublic export in unit.publicNamespace.exports) { 961 for (UnlinkedExportPublic export in unit.publicNamespace.exports) {
986 referenced.exported.add(export.uri); 962 referenced.exported.add(export.uri);
987 } 963 }
988 return referenced; 964 return referenced;
989 } 965 }
990 966
991 _ReferencedUris._(); 967 _ReferencedUris._();
992 } 968 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698