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