241 lines · cpp
1//===--- IndexAction.cpp -----------------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "IndexAction.h"10#include "AST.h"11#include "Headers.h"12#include "clang-include-cleaner/Record.h"13#include "index/Relation.h"14#include "index/SymbolCollector.h"15#include "index/SymbolOrigin.h"16#include "clang/AST/ASTConsumer.h"17#include "clang/AST/ASTContext.h"18#include "clang/Basic/SourceLocation.h"19#include "clang/Basic/SourceManager.h"20#include "clang/Frontend/CompilerInstance.h"21#include "clang/Frontend/FrontendAction.h"22#include "clang/Index/IndexingAction.h"23#include "clang/Index/IndexingOptions.h"24#include <cstddef>25#include <functional>26#include <memory>27#include <optional>28#include <utility>29 30namespace clang {31namespace clangd {32namespace {33 34std::optional<std::string> toURI(OptionalFileEntryRef File) {35 if (!File)36 return std::nullopt;37 auto AbsolutePath = File->getFileEntry().tryGetRealPathName();38 if (AbsolutePath.empty())39 return std::nullopt;40 return URI::create(AbsolutePath).toString();41}42 43// Collects the nodes and edges of include graph during indexing action.44// Important: The graph generated by those callbacks might contain cycles and45// self edges.46struct IncludeGraphCollector : public PPCallbacks {47public:48 IncludeGraphCollector(const SourceManager &SM, IncludeGraph &IG)49 : SM(SM), IG(IG) {}50 51 // Populates everything except direct includes for a node, which represents52 // edges in the include graph and populated in inclusion directive.53 // We cannot populate the fields in InclusionDirective because it does not54 // have access to the contents of the included file.55 void FileChanged(SourceLocation Loc, FileChangeReason Reason,56 SrcMgr::CharacteristicKind FileType,57 FileID PrevFID) override {58 // We only need to process each file once. So we don't care about anything59 // but entries.60 if (Reason != FileChangeReason::EnterFile)61 return;62 63 const auto FileID = SM.getFileID(Loc);64 auto File = SM.getFileEntryRefForID(FileID);65 auto URI = toURI(File);66 if (!URI)67 return;68 auto I = IG.try_emplace(*URI).first;69 70 auto &Node = I->getValue();71 // Node has already been populated.72 if (Node.URI.data() == I->getKeyData()) {73#ifndef NDEBUG74 auto Digest = digestFile(SM, FileID);75 assert(Digest && Node.Digest == *Digest &&76 "Same file, different digest?");77#endif78 return;79 }80 if (auto Digest = digestFile(SM, FileID))81 Node.Digest = std::move(*Digest);82 if (FileID == SM.getMainFileID())83 Node.Flags |= IncludeGraphNode::SourceFlag::IsTU;84 Node.URI = I->getKey();85 }86 87 // Add edges from including files to includes.88 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,89 llvm::StringRef FileName, bool IsAngled,90 CharSourceRange FilenameRange,91 OptionalFileEntryRef File, llvm::StringRef SearchPath,92 llvm::StringRef RelativePath,93 const Module *SuggestedModule, bool ModuleImported,94 SrcMgr::CharacteristicKind FileType) override {95 auto IncludeURI = toURI(File);96 if (!IncludeURI)97 return;98 99 auto IncludingURI = toURI(SM.getFileEntryRefForID(SM.getFileID(HashLoc)));100 if (!IncludingURI)101 return;102 103 auto NodeForInclude = IG.try_emplace(*IncludeURI).first->getKey();104 auto NodeForIncluding = IG.try_emplace(*IncludingURI);105 106 NodeForIncluding.first->getValue().DirectIncludes.push_back(NodeForInclude);107 }108 109 // Sanity check to ensure we have already populated a skipped file.110 void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,111 SrcMgr::CharacteristicKind FileType) override {112#ifndef NDEBUG113 auto URI = toURI(SkippedFile);114 if (!URI)115 return;116 auto I = IG.try_emplace(*URI);117 assert(!I.second && "File inserted for the first time on skip.");118 assert(I.first->getKeyData() == I.first->getValue().URI.data() &&119 "Node have not been populated yet");120#endif121 }122 123private:124 const SourceManager &SM;125 IncludeGraph &IG;126};127 128// Wraps the index action and reports index data after each translation unit.129class IndexAction : public ASTFrontendAction {130public:131 IndexAction(std::shared_ptr<SymbolCollector> C,132 std::unique_ptr<include_cleaner::PragmaIncludes> PI,133 const index::IndexingOptions &Opts,134 std::function<void(SymbolSlab)> SymbolsCallback,135 std::function<void(RefSlab)> RefsCallback,136 std::function<void(RelationSlab)> RelationsCallback,137 std::function<void(IncludeGraph)> IncludeGraphCallback)138 : SymbolsCallback(SymbolsCallback), RefsCallback(RefsCallback),139 RelationsCallback(RelationsCallback),140 IncludeGraphCallback(IncludeGraphCallback), Collector(C),141 PI(std::move(PI)), Opts(Opts) {142 this->Opts.ShouldTraverseDecl = [this](const Decl *D) {143 // Many operations performed during indexing is linear in terms of depth144 // of the decl (USR generation, name lookups, figuring out role of a145 // reference are some examples). Since we index all the decls nested146 // inside, it becomes quadratic. So we give up on nested symbols.147 if (isDeeplyNested(D))148 return false;149 auto &SM = D->getASTContext().getSourceManager();150 auto FID = SM.getFileID(SM.getExpansionLoc(D->getLocation()));151 if (!FID.isValid())152 return true;153 return Collector->shouldIndexFile(FID);154 };155 }156 157 std::unique_ptr<ASTConsumer>158 CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override {159 PI->record(CI.getPreprocessor());160 if (IncludeGraphCallback != nullptr)161 CI.getPreprocessor().addPPCallbacks(162 std::make_unique<IncludeGraphCollector>(CI.getSourceManager(), IG));163 164 return index::createIndexingASTConsumer(Collector, Opts,165 CI.getPreprocessorPtr());166 }167 168 bool BeginInvocation(CompilerInstance &CI) override {169 // We want all comments, not just the doxygen ones.170 CI.getLangOpts().CommentOpts.ParseAllComments = true;171 CI.getLangOpts().RetainCommentsFromSystemHeaders = true;172 // Index the whole file even if there are warnings and -Werror is set.173 // Avoids some analyses too. Set in two places as we're late to the party.174 CI.getDiagnosticOpts().IgnoreWarnings = true;175 CI.getDiagnostics().setIgnoreAllWarnings(true);176 // Instruct the parser to ask our ASTConsumer if it should skip function177 // bodies. The ASTConsumer will take care of skipping only functions inside178 // the files that we have already processed.179 CI.getFrontendOpts().SkipFunctionBodies = true;180 return true;181 }182 183 void EndSourceFileAction() override {184 SymbolsCallback(Collector->takeSymbols());185 if (RefsCallback != nullptr)186 RefsCallback(Collector->takeRefs());187 if (RelationsCallback != nullptr)188 RelationsCallback(Collector->takeRelations());189 if (IncludeGraphCallback != nullptr) {190#ifndef NDEBUG191 // This checks if all nodes are initialized.192 for (const auto &Node : IG)193 assert(Node.getKeyData() == Node.getValue().URI.data());194#endif195 IncludeGraphCallback(std::move(IG));196 }197 }198 199private:200 std::function<void(SymbolSlab)> SymbolsCallback;201 std::function<void(RefSlab)> RefsCallback;202 std::function<void(RelationSlab)> RelationsCallback;203 std::function<void(IncludeGraph)> IncludeGraphCallback;204 std::shared_ptr<SymbolCollector> Collector;205 std::unique_ptr<include_cleaner::PragmaIncludes> PI;206 index::IndexingOptions Opts;207 IncludeGraph IG;208};209 210} // namespace211 212std::unique_ptr<FrontendAction> createStaticIndexingAction(213 SymbolCollector::Options Opts,214 std::function<void(SymbolSlab)> SymbolsCallback,215 std::function<void(RefSlab)> RefsCallback,216 std::function<void(RelationSlab)> RelationsCallback,217 std::function<void(IncludeGraph)> IncludeGraphCallback) {218 index::IndexingOptions IndexOpts;219 IndexOpts.SystemSymbolFilter =220 index::IndexingOptions::SystemSymbolFilterKind::All;221 // We index function-local classes and its member functions only.222 IndexOpts.IndexFunctionLocals = true;223 Opts.CollectIncludePath = true;224 if (Opts.Origin == SymbolOrigin::Unknown)225 Opts.Origin = SymbolOrigin::Static;226 Opts.StoreAllDocumentation = false;227 if (RefsCallback != nullptr) {228 Opts.RefFilter = RefKind::All;229 Opts.RefsInHeaders = true;230 }231 auto PragmaIncludes = std::make_unique<include_cleaner::PragmaIncludes>();232 Opts.PragmaIncludes = PragmaIncludes.get();233 return std::make_unique<IndexAction>(std::make_shared<SymbolCollector>(Opts),234 std::move(PragmaIncludes), IndexOpts,235 SymbolsCallback, RefsCallback,236 RelationsCallback, IncludeGraphCallback);237}238 239} // namespace clangd240} // namespace clang241