clang-tools  9.0.0
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 "Headers.h"
11 #include "Logger.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/MultiplexConsumer.h"
20 #include "clang/Index/IndexingAction.h"
21 #include "clang/Tooling/Tooling.h"
22 #include "llvm/ADT/STLExtras.h"
23 #include <functional>
24 #include <memory>
25 #include <utility>
26 
27 namespace clang {
28 namespace clangd {
29 namespace {
30 
31 llvm::Optional<std::string> toURI(const FileEntry *File) {
32  if (!File)
33  return llvm::None;
34  auto AbsolutePath = File->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:
45  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.
52  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  const auto File = SM.getFileEntryForID(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())
81  Node.URI = I->getKey();
82  }
83 
84  // Add edges from including files to includes.
85  void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
86  llvm::StringRef FileName, bool IsAngled,
87  CharSourceRange FilenameRange, const FileEntry *File,
88  llvm::StringRef SearchPath,
89  llvm::StringRef RelativePath, const Module *Imported,
90  SrcMgr::CharacteristicKind FileType) override {
91  auto IncludeURI = toURI(File);
92  if (!IncludeURI)
93  return;
94 
95  auto IncludingURI = toURI(SM.getFileEntryForID(SM.getFileID(HashLoc)));
96  if (!IncludingURI)
97  return;
98 
99  auto NodeForInclude = IG.try_emplace(*IncludeURI).first->getKey();
100  auto NodeForIncluding = IG.try_emplace(*IncludingURI);
101 
102  NodeForIncluding.first->getValue().DirectIncludes.push_back(NodeForInclude);
103  }
104 
105  // Sanity check to ensure we have already populated a skipped file.
106  void FileSkipped(const FileEntry &SkippedFile, const Token &FilenameTok,
107  SrcMgr::CharacteristicKind FileType) override {
108 #ifndef NDEBUG
109  auto URI = toURI(&SkippedFile);
110  if (!URI)
111  return;
112  auto I = IG.try_emplace(*URI);
113  assert(!I.second && "File inserted for the first time on skip.");
114  assert(I.first->getKeyData() == I.first->getValue().URI.data() &&
115  "Node have not been populated yet");
116 #endif
117  }
118 
119 private:
120  const SourceManager &SM;
121  IncludeGraph &IG;
122 };
123 
124 /// Returns an ASTConsumer that wraps \p Inner and additionally instructs the
125 /// parser to skip bodies of functions in the files that should not be
126 /// processed.
127 static std::unique_ptr<ASTConsumer>
128 skipProcessedFunctions(std::unique_ptr<ASTConsumer> Inner,
129  std::function<bool(FileID)> ShouldIndexFile) {
130  class SkipProcessedFunctions : public ASTConsumer {
131  public:
132  SkipProcessedFunctions(std::function<bool(FileID)> FileFilter)
133  : ShouldIndexFile(std::move(FileFilter)), Context(nullptr) {
134  assert(this->ShouldIndexFile);
135  }
136 
137  void Initialize(ASTContext &Context) override { this->Context = &Context; }
138  bool shouldSkipFunctionBody(Decl *D) override {
139  assert(Context && "Initialize() was never called.");
140  auto &SM = Context->getSourceManager();
141  auto FID = SM.getFileID(SM.getExpansionLoc(D->getLocation()));
142  if (!FID.isValid())
143  return false;
144  return !ShouldIndexFile(FID);
145  }
146 
147  private:
148  std::function<bool(FileID)> ShouldIndexFile;
149  const ASTContext *Context;
150  };
151  std::vector<std::unique_ptr<ASTConsumer>> Consumers;
152  Consumers.push_back(
153  llvm::make_unique<SkipProcessedFunctions>(ShouldIndexFile));
154  Consumers.push_back(std::move(Inner));
155  return llvm::make_unique<MultiplexConsumer>(std::move(Consumers));
156 }
157 
158 // Wraps the index action and reports index data after each translation unit.
159 class IndexAction : public WrapperFrontendAction {
160 public:
161  IndexAction(std::shared_ptr<SymbolCollector> C,
162  std::unique_ptr<CanonicalIncludes> Includes,
163  const index::IndexingOptions &Opts,
164  std::function<void(SymbolSlab)> SymbolsCallback,
165  std::function<void(RefSlab)> RefsCallback,
166  std::function<void(RelationSlab)> RelationsCallback,
167  std::function<void(IncludeGraph)> IncludeGraphCallback)
168  : WrapperFrontendAction(index::createIndexingAction(C, Opts, nullptr)),
169  SymbolsCallback(SymbolsCallback), RefsCallback(RefsCallback),
170  RelationsCallback(RelationsCallback),
171  IncludeGraphCallback(IncludeGraphCallback), Collector(C),
172  Includes(std::move(Includes)),
173  PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {}
174 
175  std::unique_ptr<ASTConsumer>
176  CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override {
177  CI.getPreprocessor().addCommentHandler(PragmaHandler.get());
178  addSystemHeadersMapping(Includes.get(), CI.getLangOpts());
179  if (IncludeGraphCallback != nullptr)
180  CI.getPreprocessor().addPPCallbacks(
181  llvm::make_unique<IncludeGraphCollector>(CI.getSourceManager(), IG));
182  return skipProcessedFunctions(
183  WrapperFrontendAction::CreateASTConsumer(CI, InFile),
184  [this](FileID FID) { return Collector->shouldIndexFile(FID); });
185  }
186 
187  bool BeginInvocation(CompilerInstance &CI) override {
188  // We want all comments, not just the doxygen ones.
189  CI.getLangOpts().CommentOpts.ParseAllComments = true;
190  // Index the whole file even if there are warnings and -Werror is set.
191  // Avoids some analyses too. Set in two places as we're late to the party.
192  CI.getDiagnosticOpts().IgnoreWarnings = true;
193  CI.getDiagnostics().setIgnoreAllWarnings(true);
194  // Instruct the parser to ask our ASTConsumer if it should skip function
195  // bodies. The ASTConsumer will take care of skipping only functions inside
196  // the files that we have already processed.
197  CI.getFrontendOpts().SkipFunctionBodies = true;
198 
199  return WrapperFrontendAction::BeginInvocation(CI);
200  }
201 
202  void EndSourceFileAction() override {
203  WrapperFrontendAction::EndSourceFileAction();
204 
205  SymbolsCallback(Collector->takeSymbols());
206  if (RefsCallback != nullptr)
207  RefsCallback(Collector->takeRefs());
208  if (RelationsCallback != nullptr)
209  RelationsCallback(Collector->takeRelations());
210  if (IncludeGraphCallback != nullptr) {
211 #ifndef NDEBUG
212  // This checks if all nodes are initialized.
213  for (const auto &Node : IG)
214  assert(Node.getKeyData() == Node.getValue().URI.data());
215 #endif
216  IncludeGraphCallback(std::move(IG));
217  }
218  }
219 
220 private:
221  std::function<void(SymbolSlab)> SymbolsCallback;
222  std::function<void(RefSlab)> RefsCallback;
223  std::function<void(RelationSlab)> RelationsCallback;
224  std::function<void(IncludeGraph)> IncludeGraphCallback;
225  std::shared_ptr<SymbolCollector> Collector;
226  std::unique_ptr<CanonicalIncludes> Includes;
227  std::unique_ptr<CommentHandler> PragmaHandler;
228  IncludeGraph IG;
229 };
230 
231 } // namespace
232 
233 std::unique_ptr<FrontendAction> createStaticIndexingAction(
235  std::function<void(SymbolSlab)> SymbolsCallback,
236  std::function<void(RefSlab)> RefsCallback,
237  std::function<void(RelationSlab)> RelationsCallback,
238  std::function<void(IncludeGraph)> IncludeGraphCallback) {
239  index::IndexingOptions IndexOpts;
240  IndexOpts.SystemSymbolFilter =
241  index::IndexingOptions::SystemSymbolFilterKind::All;
242  Opts.CollectIncludePath = true;
243  if (Opts.Origin == SymbolOrigin::Unknown)
245  Opts.StoreAllDocumentation = false;
246  if (RefsCallback != nullptr) {
247  Opts.RefFilter = RefKind::All;
248  Opts.RefsInHeaders = true;
249  }
250  auto Includes = llvm::make_unique<CanonicalIncludes>();
251  Opts.Includes = Includes.get();
252  return llvm::make_unique<IndexAction>(
253  std::make_shared<SymbolCollector>(std::move(Opts)), std::move(Includes),
254  IndexOpts, SymbolsCallback, RefsCallback, RelationsCallback,
255  IncludeGraphCallback);
256 }
257 
258 } // namespace clangd
259 } // namespace clang
std::unique_ptr< CommentHandler > collectIWYUHeaderMaps(CanonicalIncludes *Includes)
Returns a CommentHandler that parses pragma comment on include files to determine when we should incl...
SourceLocation Loc
&#39;#&#39; location in the include directive
An immutable symbol container that stores a set of symbols.
Definition: Symbol.h:177
An efficient structure of storing large set of symbol references in memory.
Definition: Ref.h:69
void addSystemHeadersMapping(CanonicalIncludes *Includes, const LangOptions &Language)
Adds mapping for system headers and some special symbols (e.g.
Documents should not be synced at all.
llvm::StringMap< IncludeGraphNode > IncludeGraph
Definition: Headers.h:82
bool StoreAllDocumentation
If set to true, SymbolCollector will collect doc for all symbols.
bool IsAngled
true if this was an include with angle brackets
std::shared_ptr< SymbolCollector > Collector
const Decl * D
Definition: XRefs.cpp:868
PathRef FileName
llvm::Optional< FileDigest > digestFile(const SourceManager &SM, FileID FID)
Definition: SourceCode.cpp:522
static llvm::Expected< URI > create(llvm::StringRef AbsolutePath, llvm::StringRef Scheme)
Creates a URI for a file in the given scheme.
Definition: URI.cpp:186
bool RefsInHeaders
If set to true, SymbolCollector will collect all refs (from main file and included headers); otherwis...
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
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)
const CanonicalIncludes * Includes
If set, this is used to map symbol #include path to a potentially different #include path...
RefKind RefFilter
The symbol ref kinds that will be collected.
CommentHandler * PragmaHandler