xref: /llvm-project/clang-tools-extra/clangd/index/IndexAction.cpp (revision da95d926f6fce4ed9707c77908ad96624268f134)
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-exception
6 //
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 
30 namespace clang {
31 namespace clangd {
32 namespace {
33 
toURI(OptionalFileEntryRef File)34 std::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 and
45 // self edges.
46 struct IncludeGraphCollector : public PPCallbacks {
47 public:
IncludeGraphCollectorclang::clangd::__anon056224cb0111::IncludeGraphCollector48   IncludeGraphCollector(const SourceManager &SM, IncludeGraph &IG)
49       : SM(SM), IG(IG) {}
50 
51   // Populates everything except direct includes for a node, which represents
52   // edges in the include graph and populated in inclusion directive.
53   // We cannot populate the fields in InclusionDirective because it does not
54   // have access to the contents of the included file.
FileChangedclang::clangd::__anon056224cb0111::IncludeGraphCollector55   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 anything
59     // 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 NDEBUG
74       auto Digest = digestFile(SM, FileID);
75       assert(Digest && Node.Digest == *Digest &&
76              "Same file, different digest?");
77 #endif
78       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.
InclusionDirectiveclang::clangd::__anon056224cb0111::IncludeGraphCollector88   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.
FileSkippedclang::clangd::__anon056224cb0111::IncludeGraphCollector110   void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
111                    SrcMgr::CharacteristicKind FileType) override {
112 #ifndef NDEBUG
113     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 #endif
121   }
122 
123 private:
124   const SourceManager &SM;
125   IncludeGraph &IG;
126 };
127 
128 // Wraps the index action and reports index data after each translation unit.
129 class IndexAction : public ASTFrontendAction {
130 public:
IndexAction(std::shared_ptr<SymbolCollector> C,std::unique_ptr<include_cleaner::PragmaIncludes> PI,const index::IndexingOptions & Opts,std::function<void (SymbolSlab)> SymbolsCallback,std::function<void (RefSlab)> RefsCallback,std::function<void (RelationSlab)> RelationsCallback,std::function<void (IncludeGraph)> IncludeGraphCallback)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 depth
144       // of the decl (USR generation, name lookups, figuring out role of a
145       // reference are some examples). Since we index all the decls nested
146       // 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>
CreateASTConsumer(CompilerInstance & CI,llvm::StringRef InFile)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 
BeginInvocation(CompilerInstance & CI)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 function
177     // bodies. The ASTConsumer will take care of skipping only functions inside
178     // the files that we have already processed.
179     CI.getFrontendOpts().SkipFunctionBodies = true;
180     return true;
181   }
182 
EndSourceFileAction()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 NDEBUG
191       // This checks if all nodes are initialized.
192       for (const auto &Node : IG)
193         assert(Node.getKeyData() == Node.getValue().URI.data());
194 #endif
195       IncludeGraphCallback(std::move(IG));
196     }
197   }
198 
199 private:
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 } // namespace
211 
createStaticIndexingAction(SymbolCollector::Options Opts,std::function<void (SymbolSlab)> SymbolsCallback,std::function<void (RefSlab)> RefsCallback,std::function<void (RelationSlab)> RelationsCallback,std::function<void (IncludeGraph)> IncludeGraphCallback)212 std::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 clangd
240 } // namespace clang
241