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 "index/Relation.h"
13 #include "index/SymbolOrigin.h"
14 #include "clang/AST/ASTConsumer.h"
15 #include "clang/AST/ASTContext.h"
16 #include "clang/Basic/SourceLocation.h"
17 #include "clang/Basic/SourceManager.h"
18 #include "clang/Frontend/CompilerInstance.h"
19 #include "clang/Frontend/FrontendAction.h"
20 #include "clang/Index/IndexingAction.h"
21 #include "clang/Index/IndexingOptions.h"
22 #include <cstddef>
23 #include <functional>
24 #include <memory>
25 #include <utility>
26
27 namespace clang {
28 namespace clangd {
29 namespace {
30
toURI(Optional<FileEntryRef> File)31 llvm::Optional<std::string> toURI(Optional<FileEntryRef> File) {
32 if (!File)
33 return llvm::None;
34 auto AbsolutePath = File->getFileEntry().tryGetRealPathName();
35 if (AbsolutePath.empty())
36 return llvm::None;
37 return URI::create(AbsolutePath).toString();
38 }
39
40 // Collects the nodes and edges of include graph during indexing action.
41 // Important: The graph generated by those callbacks might contain cycles and
42 // self edges.
43 struct IncludeGraphCollector : public PPCallbacks {
44 public:
IncludeGraphCollectorclang::clangd::__anon5be1d7680111::IncludeGraphCollector45 IncludeGraphCollector(const SourceManager &SM, IncludeGraph &IG)
46 : SM(SM), IG(IG) {}
47
48 // Populates everything except direct includes for a node, which represents
49 // edges in the include graph and populated in inclusion directive.
50 // We cannot populate the fields in InclusionDirective because it does not
51 // have access to the contents of the included file.
FileChangedclang::clangd::__anon5be1d7680111::IncludeGraphCollector52 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
53 SrcMgr::CharacteristicKind FileType,
54 FileID PrevFID) override {
55 // We only need to process each file once. So we don't care about anything
56 // but entries.
57 if (Reason != FileChangeReason::EnterFile)
58 return;
59
60 const auto FileID = SM.getFileID(Loc);
61 auto File = SM.getFileEntryRefForID(FileID);
62 auto URI = toURI(File);
63 if (!URI)
64 return;
65 auto I = IG.try_emplace(*URI).first;
66
67 auto &Node = I->getValue();
68 // Node has already been populated.
69 if (Node.URI.data() == I->getKeyData()) {
70 #ifndef NDEBUG
71 auto Digest = digestFile(SM, FileID);
72 assert(Digest && Node.Digest == *Digest &&
73 "Same file, different digest?");
74 #endif
75 return;
76 }
77 if (auto Digest = digestFile(SM, FileID))
78 Node.Digest = std::move(*Digest);
79 if (FileID == SM.getMainFileID())
80 Node.Flags |= IncludeGraphNode::SourceFlag::IsTU;
81 Node.URI = I->getKey();
82 }
83
84 // Add edges from including files to includes.
InclusionDirectiveclang::clangd::__anon5be1d7680111::IncludeGraphCollector85 void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
86 llvm::StringRef FileName, bool IsAngled,
87 CharSourceRange FilenameRange,
88 Optional<FileEntryRef> File,
89 llvm::StringRef SearchPath,
90 llvm::StringRef RelativePath, const Module *Imported,
91 SrcMgr::CharacteristicKind FileType) override {
92 auto IncludeURI = toURI(File);
93 if (!IncludeURI)
94 return;
95
96 auto IncludingURI = toURI(SM.getFileEntryRefForID(SM.getFileID(HashLoc)));
97 if (!IncludingURI)
98 return;
99
100 auto NodeForInclude = IG.try_emplace(*IncludeURI).first->getKey();
101 auto NodeForIncluding = IG.try_emplace(*IncludingURI);
102
103 NodeForIncluding.first->getValue().DirectIncludes.push_back(NodeForInclude);
104 }
105
106 // Sanity check to ensure we have already populated a skipped file.
FileSkippedclang::clangd::__anon5be1d7680111::IncludeGraphCollector107 void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
108 SrcMgr::CharacteristicKind FileType) override {
109 #ifndef NDEBUG
110 auto URI = toURI(SkippedFile);
111 if (!URI)
112 return;
113 auto I = IG.try_emplace(*URI);
114 assert(!I.second && "File inserted for the first time on skip.");
115 assert(I.first->getKeyData() == I.first->getValue().URI.data() &&
116 "Node have not been populated yet");
117 #endif
118 }
119
120 private:
121 const SourceManager &SM;
122 IncludeGraph &IG;
123 };
124
125 // Wraps the index action and reports index data after each translation unit.
126 class IndexAction : public ASTFrontendAction {
127 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)128 IndexAction(std::shared_ptr<SymbolCollector> C,
129 std::unique_ptr<CanonicalIncludes> Includes,
130 const index::IndexingOptions &Opts,
131 std::function<void(SymbolSlab)> SymbolsCallback,
132 std::function<void(RefSlab)> RefsCallback,
133 std::function<void(RelationSlab)> RelationsCallback,
134 std::function<void(IncludeGraph)> IncludeGraphCallback)
135 : SymbolsCallback(SymbolsCallback), RefsCallback(RefsCallback),
136 RelationsCallback(RelationsCallback),
137 IncludeGraphCallback(IncludeGraphCallback), Collector(C),
138 Includes(std::move(Includes)), Opts(Opts),
139 PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {
140 this->Opts.ShouldTraverseDecl = [this](const Decl *D) {
141 // Many operations performed during indexing is linear in terms of depth
142 // of the decl (USR generation, name lookups, figuring out role of a
143 // reference are some examples). Since we index all the decls nested
144 // inside, it becomes quadratic. So we give up on nested symbols.
145 if (isDeeplyNested(D))
146 return false;
147 auto &SM = D->getASTContext().getSourceManager();
148 auto FID = SM.getFileID(SM.getExpansionLoc(D->getLocation()));
149 if (!FID.isValid())
150 return true;
151 return Collector->shouldIndexFile(FID);
152 };
153 }
154
155 std::unique_ptr<ASTConsumer>
CreateASTConsumer(CompilerInstance & CI,llvm::StringRef InFile)156 CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override {
157 CI.getPreprocessor().addCommentHandler(PragmaHandler.get());
158 Includes->addSystemHeadersMapping(CI.getLangOpts());
159 if (IncludeGraphCallback != nullptr)
160 CI.getPreprocessor().addPPCallbacks(
161 std::make_unique<IncludeGraphCollector>(CI.getSourceManager(), IG));
162
163 return index::createIndexingASTConsumer(Collector, Opts,
164 CI.getPreprocessorPtr());
165 }
166
BeginInvocation(CompilerInstance & CI)167 bool BeginInvocation(CompilerInstance &CI) override {
168 // We want all comments, not just the doxygen ones.
169 CI.getLangOpts().CommentOpts.ParseAllComments = true;
170 CI.getLangOpts().RetainCommentsFromSystemHeaders = true;
171 // Index the whole file even if there are warnings and -Werror is set.
172 // Avoids some analyses too. Set in two places as we're late to the party.
173 CI.getDiagnosticOpts().IgnoreWarnings = true;
174 CI.getDiagnostics().setIgnoreAllWarnings(true);
175 // Instruct the parser to ask our ASTConsumer if it should skip function
176 // bodies. The ASTConsumer will take care of skipping only functions inside
177 // the files that we have already processed.
178 CI.getFrontendOpts().SkipFunctionBodies = true;
179 return true;
180 }
181
EndSourceFileAction()182 void EndSourceFileAction() override {
183 SymbolsCallback(Collector->takeSymbols());
184 if (RefsCallback != nullptr)
185 RefsCallback(Collector->takeRefs());
186 if (RelationsCallback != nullptr)
187 RelationsCallback(Collector->takeRelations());
188 if (IncludeGraphCallback != nullptr) {
189 #ifndef NDEBUG
190 // This checks if all nodes are initialized.
191 for (const auto &Node : IG)
192 assert(Node.getKeyData() == Node.getValue().URI.data());
193 #endif
194 IncludeGraphCallback(std::move(IG));
195 }
196 }
197
198 private:
199 std::function<void(SymbolSlab)> SymbolsCallback;
200 std::function<void(RefSlab)> RefsCallback;
201 std::function<void(RelationSlab)> RelationsCallback;
202 std::function<void(IncludeGraph)> IncludeGraphCallback;
203 std::shared_ptr<SymbolCollector> Collector;
204 std::unique_ptr<CanonicalIncludes> Includes;
205 index::IndexingOptions Opts;
206 std::unique_ptr<CommentHandler> PragmaHandler;
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 Includes = std::make_unique<CanonicalIncludes>();
232 Opts.Includes = Includes.get();
233 return std::make_unique<IndexAction>(
234 std::make_shared<SymbolCollector>(std::move(Opts)), std::move(Includes),
235 IndexOpts, SymbolsCallback, RefsCallback, RelationsCallback,
236 IncludeGraphCallback);
237 }
238
239 } // namespace clangd
240 } // namespace clang
241