clang-tools 22.0.0git
IndexAction.cpp
Go to the documentation of this file.
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"
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 and
45// 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 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(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())
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 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
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 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>
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 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
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} // namespace
211
212std::unique_ptr<FrontendAction> createStaticIndexingAction(
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)
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
An efficient structure of storing large set of symbol references in memory.
Definition Ref.h:111
An immutable symbol container that stores a set of symbols.
Definition Symbol.h:201
static llvm::Expected< URI > create(llvm::StringRef AbsolutePath, llvm::StringRef Scheme)
Creates a URI for a file in the given scheme.
Definition URI.cpp:208
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
std::optional< FileDigest > digestFile(const SourceManager &SM, FileID FID)
llvm::StringMap< IncludeGraphNode > IncludeGraph
Definition Headers.h:103
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)
bool isDeeplyNested(const Decl *D, unsigned MaxDepth)
Checks whether D is more than MaxDepth away from translation unit scope.
Definition AST.cpp:742
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
RefKind RefFilter
The symbol ref kinds that will be collected.
bool StoreAllDocumentation
If set to true, SymbolCollector will collect doc for all symbols.
bool RefsInHeaders
If set to true, SymbolCollector will collect all refs (from main file and included headers); otherwis...
const include_cleaner::PragmaIncludes * PragmaIncludes
If set, this is used to map symbol include path to a potentially different include path specified by ...