1//===--- IndexTests.cpp - Test indexing actions -----------------*- 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 "clang/AST/ASTConsumer.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/Decl.h"
12#include "clang/Basic/SourceLocation.h"
13#include "clang/Basic/SourceManager.h"
14#include "clang/Frontend/CompilerInstance.h"
15#include "clang/Frontend/FrontendAction.h"
16#include "clang/Index/IndexDataConsumer.h"
17#include "clang/Index/IndexSymbol.h"
18#include "clang/Index/IndexingAction.h"
19#include "clang/Lex/Preprocessor.h"
20#include "clang/Tooling/Tooling.h"
21#include "llvm/ADT/StringRef.h"
22#include "llvm/Support/VirtualFileSystem.h"
23#include "gmock/gmock.h"
24#include "gtest/gtest.h"
25#include <memory>
26
27namespace clang {
28namespace index {
29namespace {
30struct Position {
31 size_t Line = 0;
32 size_t Column = 0;
33
34 Position(size_t Line = 0, size_t Column = 0) : Line(Line), Column(Column) {}
35
36 static Position fromSourceLocation(SourceLocation Loc,
37 const SourceManager &SM) {
38 auto [FID, Offset] = SM.getDecomposedSpellingLoc(Loc);
39 Position P;
40 P.Line = SM.getLineNumber(FID, FilePos: Offset);
41 P.Column = SM.getColumnNumber(FID, FilePos: Offset);
42 return P;
43 }
44};
45
46bool operator==(const Position &LHS, const Position &RHS) {
47 return std::tie(args: LHS.Line, args: LHS.Column) == std::tie(args: RHS.Line, args: RHS.Column);
48}
49
50llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Position &Pos) {
51 return OS << Pos.Line << ':' << Pos.Column;
52}
53
54struct TestSymbol {
55 std::string QName;
56 Position WrittenPos;
57 Position DeclPos;
58 SymbolInfo SymInfo;
59 SymbolRoleSet Roles;
60 // FIXME: add more information.
61};
62
63llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const TestSymbol &S) {
64 return OS << S.QName << '[' << S.WrittenPos << ']' << '@' << S.DeclPos << '('
65 << static_cast<unsigned>(S.SymInfo.Kind) << ')';
66}
67
68class Indexer : public IndexDataConsumer {
69public:
70 void initialize(ASTContext &Ctx) override {
71 AST = &Ctx;
72 IndexDataConsumer::initialize(Ctx);
73 }
74
75 bool handleDeclOccurrence(const Decl *D, SymbolRoleSet Roles,
76 ArrayRef<SymbolRelation>, SourceLocation Loc,
77 ASTNodeInfo) override {
78 const auto *ND = llvm::dyn_cast<NamedDecl>(Val: D);
79 if (!ND)
80 return true;
81 TestSymbol S;
82 S.SymInfo = getSymbolInfo(D);
83 S.QName = ND->getQualifiedNameAsString();
84 S.WrittenPos = Position::fromSourceLocation(Loc, SM: AST->getSourceManager());
85 S.DeclPos =
86 Position::fromSourceLocation(Loc: D->getLocation(), SM: AST->getSourceManager());
87 S.Roles = Roles;
88 Symbols.push_back(x: std::move(S));
89 return true;
90 }
91
92 bool handleMacroOccurrence(const IdentifierInfo *Name, const MacroInfo *MI,
93 SymbolRoleSet Roles, SourceLocation Loc) override {
94 TestSymbol S;
95 S.SymInfo = getSymbolInfoForMacro(MI: *MI);
96 S.QName = std::string(Name->getName());
97 S.WrittenPos = Position::fromSourceLocation(Loc, SM: AST->getSourceManager());
98 S.DeclPos = Position::fromSourceLocation(Loc: MI->getDefinitionLoc(),
99 SM: AST->getSourceManager());
100 S.Roles = Roles;
101 Symbols.push_back(x: std::move(S));
102 return true;
103 }
104
105 std::vector<TestSymbol> Symbols;
106 const ASTContext *AST = nullptr;
107};
108
109class IndexAction : public ASTFrontendAction {
110public:
111 IndexAction(std::shared_ptr<Indexer> Index,
112 IndexingOptions Opts = IndexingOptions())
113 : Index(std::move(Index)), Opts(Opts) {}
114
115protected:
116 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
117 StringRef InFile) override {
118 class Consumer : public ASTConsumer {
119 std::shared_ptr<Indexer> Index;
120 std::shared_ptr<Preprocessor> PP;
121 IndexingOptions Opts;
122
123 public:
124 Consumer(std::shared_ptr<Indexer> Index, std::shared_ptr<Preprocessor> PP,
125 IndexingOptions Opts)
126 : Index(std::move(Index)), PP(std::move(PP)), Opts(Opts) {}
127
128 void HandleTranslationUnit(ASTContext &Ctx) override {
129 std::vector<Decl *> DeclsToIndex(
130 Ctx.getTranslationUnitDecl()->decls().begin(),
131 Ctx.getTranslationUnitDecl()->decls().end());
132 indexTopLevelDecls(Ctx, PP&: *PP, Decls: DeclsToIndex, DataConsumer&: *Index, Opts);
133 }
134 };
135 return std::make_unique<Consumer>(args&: Index, args: CI.getPreprocessorPtr(), args&: Opts);
136 }
137
138private:
139 std::shared_ptr<Indexer> Index;
140 IndexingOptions Opts;
141};
142
143using testing::AllOf;
144using testing::Contains;
145using testing::Not;
146using testing::UnorderedElementsAre;
147
148MATCHER_P(QName, Name, "") { return arg.QName == Name; }
149MATCHER_P(WrittenAt, Pos, "") { return arg.WrittenPos == Pos; }
150MATCHER_P(DeclAt, Pos, "") { return arg.DeclPos == Pos; }
151MATCHER_P(Kind, SymKind, "") { return arg.SymInfo.Kind == SymKind; }
152MATCHER_P(HasRole, Role, "") { return arg.Roles & static_cast<unsigned>(Role); }
153
154TEST(IndexTest, Simple) {
155 auto Index = std::make_shared<Indexer>();
156 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index),
157 Code: "class X {}; void f() {}");
158 EXPECT_THAT(Index->Symbols, UnorderedElementsAre(QName("X"), QName("f")));
159}
160
161TEST(IndexTest, IndexPreprocessorMacros) {
162 std::string Code = R"cpp(
163 #define INDEX_MAC 1
164 #define INDEX_MAC_UNDEF 1
165 #undef INDEX_MAC_UNDEF
166 #define INDEX_MAC_REDEF 1
167 #undef INDEX_MAC_REDEF
168 #define INDEX_MAC_REDEF 2
169 )cpp";
170 auto Index = std::make_shared<Indexer>();
171 IndexingOptions Opts;
172 Opts.IndexMacrosInPreprocessor = true;
173 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
174 EXPECT_THAT(Index->Symbols,
175 Contains(AllOf(QName("INDEX_MAC"), WrittenAt(Position(2, 13)),
176 DeclAt(Position(2, 13)),
177 HasRole(SymbolRole::Definition))));
178 EXPECT_THAT(
179 Index->Symbols,
180 AllOf(Contains(AllOf(QName("INDEX_MAC_UNDEF"), WrittenAt(Position(3, 13)),
181 DeclAt(Position(3, 13)),
182 HasRole(SymbolRole::Definition))),
183 Contains(AllOf(QName("INDEX_MAC_UNDEF"), WrittenAt(Position(4, 12)),
184 DeclAt(Position(3, 13)),
185 HasRole(SymbolRole::Undefinition)))));
186 EXPECT_THAT(
187 Index->Symbols,
188 AllOf(Contains(AllOf(QName("INDEX_MAC_REDEF"), WrittenAt(Position(5, 13)),
189 DeclAt(Position(5, 13)),
190 HasRole(SymbolRole::Definition))),
191 Contains(AllOf(QName("INDEX_MAC_REDEF"), WrittenAt(Position(6, 12)),
192 DeclAt(Position(5, 13)),
193 HasRole(SymbolRole::Undefinition))),
194 Contains(AllOf(QName("INDEX_MAC_REDEF"), WrittenAt(Position(7, 13)),
195 DeclAt(Position(7, 13)),
196 HasRole(SymbolRole::Definition)))));
197
198 Opts.IndexMacrosInPreprocessor = false;
199 Index->Symbols.clear();
200 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
201 EXPECT_THAT(Index->Symbols, UnorderedElementsAre());
202}
203
204TEST(IndexTest, IndexParametersInDecls) {
205 std::string Code = "void foo(int bar);";
206 auto Index = std::make_shared<Indexer>();
207 IndexingOptions Opts;
208 Opts.IndexFunctionLocals = true;
209 Opts.IndexParametersInDeclarations = true;
210 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
211 EXPECT_THAT(Index->Symbols, Contains(QName("bar")));
212
213 Opts.IndexParametersInDeclarations = false;
214 Index->Symbols.clear();
215 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
216 EXPECT_THAT(Index->Symbols, Not(Contains(QName("bar"))));
217}
218
219TEST(IndexTest, IndexLabels) {
220 std::string Code = R"cpp(
221 int main() {
222 goto theLabel;
223 theLabel:
224 return 1;
225 }
226 )cpp";
227 auto Index = std::make_shared<Indexer>();
228 IndexingOptions Opts;
229 Opts.IndexFunctionLocals = true;
230 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
231 EXPECT_THAT(Index->Symbols,
232 Contains(AllOf(QName("theLabel"), WrittenAt(Position(3, 16)),
233 DeclAt(Position(4, 11)))));
234
235 Opts.IndexFunctionLocals = false;
236 Index->Symbols.clear();
237 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
238 EXPECT_THAT(Index->Symbols, Not(Contains(QName("theLabel"))));
239}
240
241TEST(IndexTest, IndexExplicitTemplateInstantiation) {
242 std::string Code = R"cpp(
243 template <typename T>
244 struct Foo { void bar() {} };
245 template <>
246 struct Foo<int> { void bar() {} };
247 void foo() {
248 Foo<char> abc;
249 Foo<int> b;
250 }
251 )cpp";
252 auto Index = std::make_shared<Indexer>();
253 IndexingOptions Opts;
254 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
255 EXPECT_THAT(Index->Symbols,
256 AllOf(Contains(AllOf(QName("Foo"), WrittenAt(Position(8, 7)),
257 DeclAt(Position(5, 12)))),
258 Contains(AllOf(QName("Foo"), WrittenAt(Position(7, 7)),
259 DeclAt(Position(3, 12))))));
260}
261
262TEST(IndexTest, IndexTemplateInstantiationPartial) {
263 std::string Code = R"cpp(
264 template <typename T1, typename T2>
265 struct Foo { void bar() {} };
266 template <typename T>
267 struct Foo<T, int> { void bar() {} };
268 void foo() {
269 Foo<char, char> abc;
270 Foo<int, int> b;
271 }
272 )cpp";
273 auto Index = std::make_shared<Indexer>();
274 IndexingOptions Opts;
275 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
276 EXPECT_THAT(Index->Symbols,
277 Contains(AllOf(QName("Foo"), WrittenAt(Position(8, 7)),
278 DeclAt(Position(5, 12)))));
279}
280
281TEST(IndexTest, IndexTypeParmDecls) {
282 std::string Code = R"cpp(
283 template <typename T, int I, template<typename> class C, typename NoRef>
284 struct Foo {
285 T t = I;
286 C<int> x;
287 };
288 )cpp";
289 auto Index = std::make_shared<Indexer>();
290 IndexingOptions Opts;
291 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
292 EXPECT_THAT(Index->Symbols, AllOf(Not(Contains(QName("Foo::T"))),
293 Not(Contains(QName("Foo::I"))),
294 Not(Contains(QName("Foo::C"))),
295 Not(Contains(QName("Foo::NoRef")))));
296
297 Opts.IndexTemplateParameters = true;
298 Index->Symbols.clear();
299 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
300 EXPECT_THAT(Index->Symbols,
301 AllOf(Contains(AllOf(QName("Foo::T"),
302 Kind(SymbolKind::TemplateTypeParm))),
303 Contains(AllOf(QName("Foo::I"),
304 Kind(SymbolKind::NonTypeTemplateParm))),
305 Contains(AllOf(QName("Foo::C"),
306 Kind(SymbolKind::TemplateTemplateParm))),
307 Contains(QName("Foo::NoRef"))));
308}
309
310TEST(IndexTest, UsingDecls) {
311 std::string Code = R"cpp(
312 void foo(int bar);
313 namespace std {
314 using ::foo;
315 }
316 )cpp";
317 auto Index = std::make_shared<Indexer>();
318 IndexingOptions Opts;
319 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
320 EXPECT_THAT(Index->Symbols,
321 Contains(AllOf(QName("std::foo"), Kind(SymbolKind::Using))));
322}
323
324TEST(IndexTest, Constructors) {
325 std::string Code = R"cpp(
326 struct Foo {
327 Foo(int);
328 ~Foo();
329 };
330 )cpp";
331 auto Index = std::make_shared<Indexer>();
332 IndexingOptions Opts;
333 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
334 EXPECT_THAT(
335 Index->Symbols,
336 UnorderedElementsAre(
337 AllOf(QName("Foo"), Kind(SymbolKind::Struct),
338 WrittenAt(Position(2, 12))),
339 AllOf(QName("Foo::Foo"), Kind(SymbolKind::Constructor),
340 WrittenAt(Position(3, 7))),
341 AllOf(QName("Foo"), Kind(SymbolKind::Struct),
342 HasRole(SymbolRole::NameReference), WrittenAt(Position(3, 7))),
343 AllOf(QName("Foo::~Foo"), Kind(SymbolKind::Destructor),
344 WrittenAt(Position(4, 7))),
345 AllOf(QName("Foo"), Kind(SymbolKind::Struct),
346 HasRole(SymbolRole::NameReference),
347 WrittenAt(Position(4, 8)))));
348}
349
350TEST(IndexTest, InjecatedNameClass) {
351 std::string Code = R"cpp(
352 template <typename T>
353 class Foo {
354 void f(Foo x);
355 };
356 )cpp";
357 auto Index = std::make_shared<Indexer>();
358 IndexingOptions Opts;
359 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
360 EXPECT_THAT(Index->Symbols,
361 UnorderedElementsAre(AllOf(QName("Foo"), Kind(SymbolKind::Class),
362 WrittenAt(Position(3, 11))),
363 AllOf(QName("Foo::f"),
364 Kind(SymbolKind::InstanceMethod),
365 WrittenAt(Position(4, 12))),
366 AllOf(QName("Foo"), Kind(SymbolKind::Class),
367 HasRole(SymbolRole::Reference),
368 WrittenAt(Position(4, 14)))));
369}
370
371TEST(IndexTest, VisitDefaultArgs) {
372 std::string Code = R"cpp(
373 int var = 0;
374 void f(int s = var) {}
375 )cpp";
376 auto Index = std::make_shared<Indexer>();
377 IndexingOptions Opts;
378 Opts.IndexFunctionLocals = true;
379 Opts.IndexParametersInDeclarations = true;
380 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
381 EXPECT_THAT(Index->Symbols,
382 Contains(AllOf(QName("var"), HasRole(SymbolRole::Reference),
383 WrittenAt(Position(3, 20)))));
384}
385
386TEST(IndexTest, RelationBaseOf) {
387 std::string Code = R"cpp(
388 class A {};
389 template <typename> class B {};
390 class C : B<A> {};
391 )cpp";
392 auto Index = std::make_shared<Indexer>();
393 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index), Code);
394 // A should not be the base of anything.
395 EXPECT_THAT(Index->Symbols,
396 Contains(AllOf(QName("A"), HasRole(SymbolRole::Reference),
397 Not(HasRole(SymbolRole::RelationBaseOf)))));
398}
399
400TEST(IndexTest, EnumBase) {
401 std::string Code = R"cpp(
402 typedef int MyTypedef;
403 enum Foo : MyTypedef;
404 enum Foo : MyTypedef {};
405 )cpp";
406 auto Index = std::make_shared<Indexer>();
407 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index), Code);
408 EXPECT_THAT(
409 Index->Symbols,
410 AllOf(Contains(AllOf(QName("MyTypedef"), HasRole(SymbolRole::Reference),
411 WrittenAt(Position(3, 16)))),
412 Contains(AllOf(QName("MyTypedef"), HasRole(SymbolRole::Reference),
413 WrittenAt(Position(4, 16))))));
414}
415
416TEST(IndexTest, NonTypeTemplateParameter) {
417 std::string Code = R"cpp(
418 enum class Foobar { foo };
419 template <Foobar f>
420 constexpr void func() {}
421 )cpp";
422 auto Index = std::make_shared<Indexer>();
423 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index), Code);
424 EXPECT_THAT(Index->Symbols,
425 Contains(AllOf(QName("Foobar"), HasRole(SymbolRole::Reference),
426 WrittenAt(Position(3, 15)))));
427}
428
429TEST(IndexTest, ReadWriteRoles) {
430 std::string Code = R"cpp(
431 int main() {
432 int foo = 0;
433 foo = 2;
434 foo += 1;
435 int bar = foo;
436 }
437 )cpp";
438 auto Index = std::make_shared<Indexer>();
439 IndexingOptions Opts;
440 Opts.IndexFunctionLocals = true;
441 tooling::runToolOnCode(ToolAction: std::make_unique<IndexAction>(args&: Index, args&: Opts), Code);
442 EXPECT_THAT(
443 Index->Symbols,
444 AllOf(Contains(AllOf(QName("foo"), HasRole(SymbolRole::Write),
445 WrittenAt(Position(4, 7)))),
446 Contains(AllOf(QName("foo"),
447 HasRole(static_cast<unsigned>(SymbolRole::Read) |
448 static_cast<unsigned>(SymbolRole::Write)),
449 WrittenAt(Position(5, 7)))),
450 Contains(AllOf(QName("foo"), HasRole(SymbolRole::Read),
451 WrittenAt(Position(6, 17))))));
452}
453
454} // namespace
455} // namespace index
456} // namespace clang
457

source code of clang/unittests/Index/IndexTests.cpp