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 | |
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: |
48 | 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. |
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 anything |
59 | // but entries. |
60 | if (Reason != FileChangeReason::EnterFile) |
61 | return; |
62 | |
63 | const auto FileID = SM.getFileID(SpellingLoc: Loc); |
64 | auto File = SM.getFileEntryRefForID(FID: FileID); |
65 | auto URI = toURI(File); |
66 | if (!URI) |
67 | return; |
68 | auto I = IG.try_emplace(Key: *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, FID: FileID); |
75 | assert(Digest && Node.Digest == *Digest && |
76 | "Same file, different digest?" ); |
77 | #endif |
78 | return; |
79 | } |
80 | if (auto Digest = digestFile(SM, FID: 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(File: SM.getFileEntryRefForID(FID: SM.getFileID(SpellingLoc: HashLoc))); |
100 | if (!IncludingURI) |
101 | return; |
102 | |
103 | auto NodeForInclude = IG.try_emplace(Key: *IncludeURI).first->getKey(); |
104 | auto NodeForIncluding = IG.try_emplace(Key: *IncludingURI); |
105 | |
106 | NodeForIncluding.first->getValue().DirectIncludes.push_back(x: 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 NDEBUG |
113 | auto URI = toURI(File: SkippedFile); |
114 | if (!URI) |
115 | return; |
116 | auto I = IG.try_emplace(Key: *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: |
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(SpellingLoc: SM.getExpansionLoc(Loc: 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(P&: CI.getPreprocessor()); |
160 | if (IncludeGraphCallback != nullptr) |
161 | CI.getPreprocessor().addPPCallbacks( |
162 | C: std::make_unique<IncludeGraphCollector>(args&: CI.getSourceManager(), args&: IG)); |
163 | |
164 | return index::createIndexingASTConsumer(DataConsumer: Collector, Opts, |
165 | PP: 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 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 | |
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 | |
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>(args: std::make_shared<SymbolCollector>(args&: Opts), |
234 | args: std::move(PragmaIncludes), args&: IndexOpts, |
235 | args&: SymbolsCallback, args&: RefsCallback, |
236 | args&: RelationsCallback, args&: IncludeGraphCallback); |
237 | } |
238 | |
239 | } // namespace clangd |
240 | } // namespace clang |
241 | |