| //===--- IndexAction.cpp -----------------------------------------*- C++-*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "IndexAction.h" |
| #include "AST.h" |
| #include "Headers.h" |
| #include "index/Relation.h" |
| #include "index/SymbolOrigin.h" |
| #include "support/Logger.h" |
| #include "clang/AST/ASTConsumer.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/MultiplexConsumer.h" |
| #include "clang/Index/IndexingAction.h" |
| #include "clang/Index/IndexingOptions.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include <cstddef> |
| #include <functional> |
| #include <memory> |
| #include <utility> |
| |
| namespace clang { |
| namespace clangd { |
| namespace { |
| |
| llvm::Optional<std::string> toURI(const FileEntry *File) { |
| if (!File) |
| return llvm::None; |
| auto AbsolutePath = File->tryGetRealPathName(); |
| if (AbsolutePath.empty()) |
| return llvm::None; |
| return URI::create(AbsolutePath).toString(); |
| } |
| |
| // Collects the nodes and edges of include graph during indexing action. |
| // Important: The graph generated by those callbacks might contain cycles and |
| // self edges. |
| struct IncludeGraphCollector : public PPCallbacks { |
| public: |
| IncludeGraphCollector(const SourceManager &SM, IncludeGraph &IG) |
| : SM(SM), IG(IG) {} |
| |
| // Populates everything except direct includes for a node, which represents |
| // edges in the include graph and populated in inclusion directive. |
| // We cannot populate the fields in InclusionDirective because it does not |
| // have access to the contents of the included file. |
| void FileChanged(SourceLocation Loc, FileChangeReason Reason, |
| SrcMgr::CharacteristicKind FileType, |
| FileID PrevFID) override { |
| // We only need to process each file once. So we don't care about anything |
| // but entries. |
| if (Reason != FileChangeReason::EnterFile) |
| return; |
| |
| const auto FileID = SM.getFileID(Loc); |
| const auto File = SM.getFileEntryForID(FileID); |
| auto URI = toURI(File); |
| if (!URI) |
| return; |
| auto I = IG.try_emplace(*URI).first; |
| |
| auto &Node = I->getValue(); |
| // Node has already been populated. |
| if (Node.URI.data() == I->getKeyData()) { |
| #ifndef NDEBUG |
| auto Digest = digestFile(SM, FileID); |
| assert(Digest && Node.Digest == *Digest && |
| "Same file, different digest?"); |
| #endif |
| return; |
| } |
| if (auto Digest = digestFile(SM, FileID)) |
| Node.Digest = std::move(*Digest); |
| if (FileID == SM.getMainFileID()) |
| Node.Flags |= IncludeGraphNode::SourceFlag::IsTU; |
| Node.URI = I->getKey(); |
| } |
| |
| // Add edges from including files to includes. |
| void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
| llvm::StringRef FileName, bool IsAngled, |
| CharSourceRange FilenameRange, const FileEntry *File, |
| llvm::StringRef SearchPath, |
| llvm::StringRef RelativePath, const Module *Imported, |
| SrcMgr::CharacteristicKind FileType) override { |
| auto IncludeURI = toURI(File); |
| if (!IncludeURI) |
| return; |
| |
| auto IncludingURI = toURI(SM.getFileEntryForID(SM.getFileID(HashLoc))); |
| if (!IncludingURI) |
| return; |
| |
| auto NodeForInclude = IG.try_emplace(*IncludeURI).first->getKey(); |
| auto NodeForIncluding = IG.try_emplace(*IncludingURI); |
| |
| NodeForIncluding.first->getValue().DirectIncludes.push_back(NodeForInclude); |
| } |
| |
| // Sanity check to ensure we have already populated a skipped file. |
| void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok, |
| SrcMgr::CharacteristicKind FileType) override { |
| #ifndef NDEBUG |
| auto URI = toURI(&SkippedFile.getFileEntry()); |
| if (!URI) |
| return; |
| auto I = IG.try_emplace(*URI); |
| assert(!I.second && "File inserted for the first time on skip."); |
| assert(I.first->getKeyData() == I.first->getValue().URI.data() && |
| "Node have not been populated yet"); |
| #endif |
| } |
| |
| private: |
| const SourceManager &SM; |
| IncludeGraph &IG; |
| }; |
| |
| // Wraps the index action and reports index data after each translation unit. |
| class IndexAction : public ASTFrontendAction { |
| public: |
| IndexAction(std::shared_ptr<SymbolCollector> C, |
| std::unique_ptr<CanonicalIncludes> Includes, |
| const index::IndexingOptions &Opts, |
| std::function<void(SymbolSlab)> SymbolsCallback, |
| std::function<void(RefSlab)> RefsCallback, |
| std::function<void(RelationSlab)> RelationsCallback, |
| std::function<void(IncludeGraph)> IncludeGraphCallback) |
| : SymbolsCallback(SymbolsCallback), RefsCallback(RefsCallback), |
| RelationsCallback(RelationsCallback), |
| IncludeGraphCallback(IncludeGraphCallback), Collector(C), |
| Includes(std::move(Includes)), Opts(Opts), |
| PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) { |
| this->Opts.ShouldTraverseDecl = [this](const Decl *D) { |
| // Many operations performed during indexing is linear in terms of depth |
| // of the decl (USR generation, name lookups, figuring out role of a |
| // reference are some examples). Since we index all the decls nested |
| // inside, it becomes quadratic. So we give up on nested symbols. |
| if (isDeeplyNested(D)) |
| return false; |
| auto &SM = D->getASTContext().getSourceManager(); |
| auto FID = SM.getFileID(SM.getExpansionLoc(D->getLocation())); |
| if (!FID.isValid()) |
| return true; |
| return Collector->shouldIndexFile(FID); |
| }; |
| } |
| |
| std::unique_ptr<ASTConsumer> |
| CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { |
| CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); |
| Includes->addSystemHeadersMapping(CI.getLangOpts()); |
| if (IncludeGraphCallback != nullptr) |
| CI.getPreprocessor().addPPCallbacks( |
| std::make_unique<IncludeGraphCollector>(CI.getSourceManager(), IG)); |
| |
| return index::createIndexingASTConsumer(Collector, Opts, |
| CI.getPreprocessorPtr()); |
| } |
| |
| bool BeginInvocation(CompilerInstance &CI) override { |
| // We want all comments, not just the doxygen ones. |
| CI.getLangOpts().CommentOpts.ParseAllComments = true; |
| CI.getLangOpts().RetainCommentsFromSystemHeaders = true; |
| // Index the whole file even if there are warnings and -Werror is set. |
| // Avoids some analyses too. Set in two places as we're late to the party. |
| CI.getDiagnosticOpts().IgnoreWarnings = true; |
| CI.getDiagnostics().setIgnoreAllWarnings(true); |
| // Instruct the parser to ask our ASTConsumer if it should skip function |
| // bodies. The ASTConsumer will take care of skipping only functions inside |
| // the files that we have already processed. |
| CI.getFrontendOpts().SkipFunctionBodies = true; |
| return true; |
| } |
| |
| void EndSourceFileAction() override { |
| SymbolsCallback(Collector->takeSymbols()); |
| if (RefsCallback != nullptr) |
| RefsCallback(Collector->takeRefs()); |
| if (RelationsCallback != nullptr) |
| RelationsCallback(Collector->takeRelations()); |
| if (IncludeGraphCallback != nullptr) { |
| #ifndef NDEBUG |
| // This checks if all nodes are initialized. |
| for (const auto &Node : IG) |
| assert(Node.getKeyData() == Node.getValue().URI.data()); |
| #endif |
| IncludeGraphCallback(std::move(IG)); |
| } |
| } |
| |
| private: |
| std::function<void(SymbolSlab)> SymbolsCallback; |
| std::function<void(RefSlab)> RefsCallback; |
| std::function<void(RelationSlab)> RelationsCallback; |
| std::function<void(IncludeGraph)> IncludeGraphCallback; |
| std::shared_ptr<SymbolCollector> Collector; |
| std::unique_ptr<CanonicalIncludes> Includes; |
| index::IndexingOptions Opts; |
| std::unique_ptr<CommentHandler> PragmaHandler; |
| IncludeGraph IG; |
| }; |
| |
| } // namespace |
| |
| std::unique_ptr<FrontendAction> createStaticIndexingAction( |
| SymbolCollector::Options Opts, |
| std::function<void(SymbolSlab)> SymbolsCallback, |
| std::function<void(RefSlab)> RefsCallback, |
| std::function<void(RelationSlab)> RelationsCallback, |
| std::function<void(IncludeGraph)> IncludeGraphCallback) { |
| index::IndexingOptions IndexOpts; |
| IndexOpts.SystemSymbolFilter = |
| index::IndexingOptions::SystemSymbolFilterKind::All; |
| // We index function-local classes and its member functions only. |
| IndexOpts.IndexFunctionLocals = true; |
| Opts.CollectIncludePath = true; |
| if (Opts.Origin == SymbolOrigin::Unknown) |
| Opts.Origin = SymbolOrigin::Static; |
| Opts.StoreAllDocumentation = false; |
| if (RefsCallback != nullptr) { |
| Opts.RefFilter = RefKind::All; |
| Opts.RefsInHeaders = true; |
| } |
| auto Includes = std::make_unique<CanonicalIncludes>(); |
| Opts.Includes = Includes.get(); |
| return std::make_unique<IndexAction>( |
| std::make_shared<SymbolCollector>(std::move(Opts)), std::move(Includes), |
| IndexOpts, SymbolsCallback, RefsCallback, RelationsCallback, |
| IncludeGraphCallback); |
| } |
| |
| } // namespace clangd |
| } // namespace clang |