OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |