clang-tools 24.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"
14#include "index/Serialization.h"
16#include "index/SymbolOrigin.h"
17#include "clang/AST/ASTConsumer.h"
18#include "clang/AST/ASTContext.h"
19#include "clang/Basic/SourceLocation.h"
20#include "clang/Basic/SourceManager.h"
21#include "clang/Frontend/CompilerInstance.h"
22#include "clang/Frontend/FrontendAction.h"
23#include "clang/Index/IndexingAction.h"
24#include "clang/Index/IndexingOptions.h"
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(IndexFileIn)> IndexContentsCallback)
135 : IndexContentsCallback(IndexContentsCallback), Collector(C),
136 PI(std::move(PI)), Opts(Opts) {
137 this->Opts.ShouldTraverseDecl = [this](const Decl *D) {
138 // Many operations performed during indexing is linear in terms of depth
139 // of the decl (USR generation, name lookups, figuring out role of a
140 // reference are some examples). Since we index all the decls nested
141 // inside, it becomes quadratic. So we give up on nested symbols.
142 if (isDeeplyNested(D))
143 return false;
144 // If D is a likely forwarding function we need the body to index indirect
145 // constructor calls (e.g. `make_unique`)
146 if (auto *FT = llvm::dyn_cast<clang::FunctionTemplateDecl>(D);
148 return true;
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 CI.getPreprocessor().addPPCallbacks(
161 std::make_unique<IncludeGraphCollector>(CI.getSourceManager(), IG));
162
163 return index::createIndexingASTConsumer(Collector, Opts,
164 CI.getPreprocessorPtr());
165 }
166
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().CommentOpts.RetainComments = true;
171 CI.getLangOpts().CommentOpts.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 IndexFileIn Result;
185 Result.Symbols = Collector->takeSymbols();
186 Result.Refs = Collector->takeRefs();
187 Result.Relations = Collector->takeRelations();
188#ifndef NDEBUG
189 // This checks if all nodes are initialized.
190 for (const auto &Node : IG)
191 assert(Node.getKeyData() == Node.getValue().URI.data());
192#endif
193 Result.Sources = std::move(IG);
194 IndexContentsCallback(std::move(Result));
195 }
196
197private:
198 std::function<void(IndexFileIn)> IndexContentsCallback;
199 std::shared_ptr<SymbolCollector> Collector;
200 std::unique_ptr<include_cleaner::PragmaIncludes> PI;
201 index::IndexingOptions Opts;
202 IncludeGraph IG;
203};
204
205} // namespace
206
207std::unique_ptr<FrontendAction> createStaticIndexingAction(
209 std::function<void(IndexFileIn)> IndexContentsCallback) {
210 index::IndexingOptions IndexOpts;
211 IndexOpts.SystemSymbolFilter =
212 index::IndexingOptions::SystemSymbolFilterKind::All;
213 // We index function-local classes and its member functions only.
214 IndexOpts.IndexFunctionLocals = true;
215 // We need to delay indexing so instantiations of function bodies become
216 // available, this is so we can find constructor calls through `make_unique`.
217 IndexOpts.DeferIndexingToEndOfTranslationUnit = true;
218 Opts.CollectIncludePath = true;
219 if (Opts.Origin == SymbolOrigin::Unknown)
221 Opts.StoreAllDocumentation = false;
222 Opts.RefFilter = RefKind::All;
223 Opts.RefsInHeaders = true;
224 auto PragmaIncludes = std::make_unique<include_cleaner::PragmaIncludes>();
225 Opts.PragmaIncludes = PragmaIncludes.get();
226 return std::make_unique<IndexAction>(std::make_shared<SymbolCollector>(Opts),
227 std::move(PragmaIncludes), IndexOpts,
228 IndexContentsCallback);
229}
230
231} // namespace clangd
232} // namespace clang
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:44
bool isLikelyForwardingFunction(const FunctionTemplateDecl *FT)
Heuristic that checks if FT is likely to be forwarding a parameter pack to another function (e....
Definition AST.cpp:1052
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(IndexFileIn)> IndexContentsCallback)
bool isDeeplyNested(const Decl *D, unsigned MaxDepth)
Checks whether D is more than MaxDepth away from translation unit scope.
Definition AST.cpp:743
===– 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 ...