1//===--- AnalysisTest.cpp -------------------------------------------------===//
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-include-cleaner/Analysis.h"
10#include "AnalysisInternal.h"
11#include "TypesInternal.h"
12#include "clang-include-cleaner/Record.h"
13#include "clang-include-cleaner/Types.h"
14#include "clang/AST/ASTContext.h"
15#include "clang/AST/DeclBase.h"
16#include "clang/Basic/FileManager.h"
17#include "clang/Basic/IdentifierTable.h"
18#include "clang/Basic/SourceLocation.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Format/Format.h"
21#include "clang/Frontend/FrontendActions.h"
22#include "clang/Testing/TestAST.h"
23#include "clang/Tooling/Inclusions/StandardLibrary.h"
24#include "llvm/ADT/ArrayRef.h"
25#include "llvm/ADT/IntrusiveRefCntPtr.h"
26#include "llvm/ADT/SmallVector.h"
27#include "llvm/ADT/StringRef.h"
28#include "llvm/Support/Error.h"
29#include "llvm/Support/MemoryBuffer.h"
30#include "llvm/Support/ScopedPrinter.h"
31#include "llvm/Support/VirtualFileSystem.h"
32#include "llvm/Testing/Annotations/Annotations.h"
33#include "gmock/gmock.h"
34#include "gtest/gtest.h"
35#include <cstddef>
36#include <map>
37#include <memory>
38#include <string>
39#include <vector>
40
41namespace clang::include_cleaner {
42namespace {
43using testing::_;
44using testing::AllOf;
45using testing::Contains;
46using testing::ElementsAre;
47using testing::Pair;
48using testing::UnorderedElementsAre;
49
50std::string guard(llvm::StringRef Code) {
51 return "#pragma once\n" + Code.str();
52}
53
54class WalkUsedTest : public testing::Test {
55protected:
56 TestInputs Inputs;
57 PragmaIncludes PI;
58 WalkUsedTest() {
59 Inputs.MakeAction = [this] {
60 struct Hook : public SyntaxOnlyAction {
61 public:
62 Hook(PragmaIncludes *Out) : Out(Out) {}
63 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
64 Out->record(CI);
65 return true;
66 }
67
68 PragmaIncludes *Out;
69 };
70 return std::make_unique<Hook>(args: &PI);
71 };
72 }
73
74 std::multimap<size_t, std::vector<Header>>
75 offsetToProviders(TestAST &AST,
76 llvm::ArrayRef<SymbolReference> MacroRefs = {}) {
77 const auto &SM = AST.sourceManager();
78 llvm::SmallVector<Decl *> TopLevelDecls;
79 for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {
80 if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation())))
81 continue;
82 TopLevelDecls.emplace_back(D);
83 }
84 std::multimap<size_t, std::vector<Header>> OffsetToProviders;
85 walkUsed(ASTRoots: TopLevelDecls, MacroRefs, PI: &PI, PP: AST.preprocessor(),
86 CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
87 auto [FID, Offset] = SM.getDecomposedLoc(Loc: Ref.RefLocation);
88 if (FID != SM.getMainFileID())
89 ADD_FAILURE() << "Reference outside of the main file!";
90 OffsetToProviders.emplace(args&: Offset, args: Providers.vec());
91 });
92 return OffsetToProviders;
93 }
94};
95
96TEST_F(WalkUsedTest, Basic) {
97 llvm::Annotations Code(R"cpp(
98 #include "header.h"
99 #include "private.h"
100
101 // No reference reported for the Parameter "p".
102 void $bar^bar($private^Private p) {
103 $foo^foo();
104 std::$vector^vector $vconstructor^$v^v;
105 $builtin^__builtin_popcount(1);
106 std::$move^move(3);
107 }
108 )cpp");
109 Inputs.Code = Code.code();
110 Inputs.ExtraFiles["header.h"] = guard(Code: R"cpp(
111 void foo();
112 namespace std { class vector {}; int&& move(int&&); }
113 )cpp");
114 Inputs.ExtraFiles["private.h"] = guard(Code: R"cpp(
115 // IWYU pragma: private, include "path/public.h"
116 class Private {};
117 )cpp");
118
119 TestAST AST(Inputs);
120 auto &SM = AST.sourceManager();
121 auto HeaderFile = Header(*AST.fileManager().getOptionalFileRef(Filename: "header.h"));
122 auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef(Filename: "private.h"));
123 auto PublicFile = Header("\"path/public.h\"");
124 auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID()));
125 auto VectorSTL = Header(*tooling::stdlib::Header::named(Name: "<vector>"));
126 auto UtilitySTL = Header(*tooling::stdlib::Header::named(Name: "<utility>"));
127 EXPECT_THAT(
128 offsetToProviders(AST),
129 UnorderedElementsAre(
130 Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
131 Pair(Code.point("private"),
132 UnorderedElementsAre(PublicFile, PrivateFile)),
133 Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)),
134 Pair(Code.point("vector"), UnorderedElementsAre(VectorSTL)),
135 Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL)),
136 Pair(Code.point("v"), UnorderedElementsAre(MainFile)),
137 Pair(Code.point("builtin"), testing::IsEmpty()),
138 Pair(Code.point("move"), UnorderedElementsAre(UtilitySTL))));
139}
140
141TEST_F(WalkUsedTest, MultipleProviders) {
142 llvm::Annotations Code(R"cpp(
143 #include "header1.h"
144 #include "header2.h"
145 void foo();
146
147 void bar() {
148 $foo^foo();
149 }
150 )cpp");
151 Inputs.Code = Code.code();
152 Inputs.ExtraFiles["header1.h"] = guard(Code: R"cpp(
153 void foo();
154 )cpp");
155 Inputs.ExtraFiles["header2.h"] = guard(Code: R"cpp(
156 void foo();
157 )cpp");
158
159 TestAST AST(Inputs);
160 auto &SM = AST.sourceManager();
161 auto HeaderFile1 = Header(*AST.fileManager().getOptionalFileRef(Filename: "header1.h"));
162 auto HeaderFile2 = Header(*AST.fileManager().getOptionalFileRef(Filename: "header2.h"));
163 auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID()));
164 EXPECT_THAT(
165 offsetToProviders(AST),
166 Contains(Pair(Code.point("foo"),
167 UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile))));
168}
169
170TEST_F(WalkUsedTest, MacroRefs) {
171 llvm::Annotations Code(R"cpp(
172 #include "hdr.h"
173 int $3^x = $1^ANSWER;
174 int $4^y = $2^ANSWER;
175 )cpp");
176 llvm::Annotations Hdr(guard(Code: "#define ^ANSWER 42"));
177 Inputs.Code = Code.code();
178 Inputs.ExtraFiles["hdr.h"] = Hdr.code();
179 TestAST AST(Inputs);
180 auto &SM = AST.sourceManager();
181 auto &PP = AST.preprocessor();
182 auto HdrFile = *SM.getFileManager().getOptionalFileRef(Filename: "hdr.h");
183 auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID()));
184
185 auto HdrID = SM.translateFile(SourceFile: HdrFile);
186
187 Symbol Answer1 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER"),
188 .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())};
189 Symbol Answer2 = Macro{.Name: PP.getIdentifierInfo(Name: "ANSWER"),
190 .Definition: SM.getComposedLoc(FID: HdrID, Offset: Hdr.point())};
191 EXPECT_THAT(
192 offsetToProviders(
193 AST,
194 {SymbolReference{
195 Answer1, SM.getComposedLoc(SM.getMainFileID(), Code.point("1")),
196 RefType::Explicit},
197 SymbolReference{
198 Answer2, SM.getComposedLoc(SM.getMainFileID(), Code.point("2")),
199 RefType::Explicit}}),
200 UnorderedElementsAre(
201 Pair(Code.point("1"), UnorderedElementsAre(HdrFile)),
202 Pair(Code.point("2"), UnorderedElementsAre(HdrFile)),
203 Pair(Code.point("3"), UnorderedElementsAre(MainFile)),
204 Pair(Code.point("4"), UnorderedElementsAre(MainFile))));
205}
206
207class AnalyzeTest : public testing::Test {
208protected:
209 TestInputs Inputs;
210 PragmaIncludes PI;
211 RecordedPP PP;
212 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS = nullptr;
213
214 AnalyzeTest() {
215 Inputs.MakeAction = [this] {
216 struct Hook : public SyntaxOnlyAction {
217 public:
218 Hook(RecordedPP &PP, PragmaIncludes &PI,
219 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS)
220 : PP(PP), PI(PI), ExtraFS(std::move(ExtraFS)) {}
221 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
222 CI.getPreprocessor().addPPCallbacks(C: PP.record(PP: CI.getPreprocessor()));
223 PI.record(CI);
224 return true;
225 }
226
227 bool BeginInvocation(CompilerInstance &CI) override {
228 if (!ExtraFS)
229 return true;
230 auto OverlayFS =
231 llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(
232 A: CI.getFileManager().getVirtualFileSystemPtr());
233 OverlayFS->pushOverlay(FS: ExtraFS);
234 CI.getFileManager().setVirtualFileSystem(std::move(OverlayFS));
235 return true;
236 }
237
238 RecordedPP &PP;
239 PragmaIncludes &PI;
240 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS;
241 };
242 return std::make_unique<Hook>(args&: PP, args&: PI, args&: ExtraFS);
243 };
244 }
245};
246
247TEST_F(AnalyzeTest, Basic) {
248 Inputs.Code = R"cpp(
249#include "a.h"
250#include "b.h"
251#include "keep.h" // IWYU pragma: keep
252
253int x = a + c;
254)cpp";
255 Inputs.ExtraFiles["a.h"] = guard(Code: "int a;");
256 Inputs.ExtraFiles["b.h"] = guard(Code: R"cpp(
257 #include "c.h"
258 int b;
259 )cpp");
260 Inputs.ExtraFiles["c.h"] = guard(Code: "int c;");
261 Inputs.ExtraFiles["keep.h"] = guard(Code: "");
262 TestAST AST(Inputs);
263 auto Decls = AST.context().getTranslationUnitDecl()->decls();
264 auto Results =
265 analyze(ASTRoots: std::vector<Decl *>{Decls.begin(), Decls.end()},
266 MacroRefs: PP.MacroReferences, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
267 auto CHeader = llvm::cantFail(
268 ValOrErr: AST.context().getSourceManager().getFileManager().getFileRef(Filename: "c.h"));
269
270 const Include *B = PP.Includes.atLine(OneBasedIndex: 3);
271 ASSERT_EQ(B->Spelled, "b.h");
272 EXPECT_THAT(Results.Missing, ElementsAre(Pair("\"c.h\"", Header(CHeader))));
273 EXPECT_THAT(Results.Unused, ElementsAre(B));
274}
275
276TEST_F(AnalyzeTest, PrivateUsedInPublic) {
277 // Check that umbrella header uses private include.
278 Inputs.Code = R"cpp(#include "private.h")cpp";
279 Inputs.ExtraFiles["private.h"] =
280 guard(Code: "// IWYU pragma: private, include \"public.h\"");
281 Inputs.FileName = "public.h";
282 TestAST AST(Inputs);
283 EXPECT_FALSE(PP.Includes.all().empty());
284 auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
285 EXPECT_THAT(Results.Unused, testing::IsEmpty());
286}
287
288TEST_F(AnalyzeTest, NoCrashWhenUnresolved) {
289 // Check that umbrella header uses private include.
290 Inputs.Code = R"cpp(#include "not_found.h")cpp";
291 Inputs.ErrorOK = true;
292 TestAST AST(Inputs);
293 EXPECT_FALSE(PP.Includes.all().empty());
294 auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
295 EXPECT_THAT(Results.Unused, testing::IsEmpty());
296}
297
298TEST_F(AnalyzeTest, ResourceDirIsIgnored) {
299 Inputs.ExtraArgs.push_back(x: "-resource-dir");
300 Inputs.ExtraArgs.push_back(x: "resources");
301 Inputs.ExtraArgs.push_back(x: "-internal-isystem");
302 Inputs.ExtraArgs.push_back(x: "resources/include");
303 Inputs.Code = R"cpp(
304 #include <amintrin.h>
305 #include <imintrin.h>
306 void baz() {
307 bar();
308 }
309 )cpp";
310 Inputs.ExtraFiles["resources/include/amintrin.h"] = guard(Code: "");
311 Inputs.ExtraFiles["resources/include/emintrin.h"] = guard(Code: R"cpp(
312 void bar();
313 )cpp");
314 Inputs.ExtraFiles["resources/include/imintrin.h"] = guard(Code: R"cpp(
315 #include <emintrin.h>
316 )cpp");
317 TestAST AST(Inputs);
318 auto Results = analyze(ASTRoots: {}, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
319 EXPECT_THAT(Results.Unused, testing::IsEmpty());
320 EXPECT_THAT(Results.Missing, testing::IsEmpty());
321}
322
323TEST_F(AnalyzeTest, DifferentHeaderSameSpelling) {
324 Inputs.ExtraArgs.push_back(x: "-Ifoo");
325 Inputs.ExtraArgs.push_back(x: "-Ifoo_inner");
326 // `foo` is declared in foo_inner/foo.h, but there's no way to spell it
327 // directly. Make sure we don't generate unusued/missing include findings in
328 // such cases.
329 Inputs.Code = R"cpp(
330 #include <foo.h>
331 void baz() {
332 foo();
333 }
334 )cpp";
335 Inputs.ExtraFiles["foo/foo.h"] = guard(Code: "#include_next <foo.h>");
336 Inputs.ExtraFiles["foo_inner/foo.h"] = guard(Code: R"cpp(
337 void foo();
338 )cpp");
339 TestAST AST(Inputs);
340 std::vector<Decl *> DeclsInTU;
341 for (auto *D : AST.context().getTranslationUnitDecl()->decls())
342 DeclsInTU.push_back(D);
343 auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
344 EXPECT_THAT(Results.Unused, testing::IsEmpty());
345 EXPECT_THAT(Results.Missing, testing::IsEmpty());
346}
347
348TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
349 llvm::Annotations Code(R"cpp(
350 #include "header.h"
351 void $bar^bar() {
352 $foo^foo();
353 }
354 )cpp");
355 Inputs.Code = Code.code();
356 ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
357 ExtraFS->addFile(Path: "content_for/0", /*ModificationTime=*/{},
358 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp(
359 #include "inner.h"
360 )cpp")));
361 ExtraFS->addSymbolicLink(NewLink: "header.h", Target: "content_for/0",
362 /*ModificationTime=*/{});
363 ExtraFS->addFile(Path: "content_for/1", /*ModificationTime=*/{},
364 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp(
365 void foo();
366 )cpp")));
367 ExtraFS->addSymbolicLink(NewLink: "inner.h", Target: "content_for/1",
368 /*ModificationTime=*/{});
369
370 TestAST AST(Inputs);
371 std::vector<Decl *> DeclsInTU;
372 for (auto *D : AST.context().getTranslationUnitDecl()->decls())
373 DeclsInTU.push_back(D);
374 auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
375 // Check that we're spelling header using the symlink, and not underlying
376 // path.
377 EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
378 // header.h should be unused.
379 EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
380
381 {
382 // Make sure filtering is also applied to symlink, not underlying file.
383 auto HeaderFilter = [](llvm::StringRef Path) { return Path == "inner.h"; };
384 Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor(),
385 HeaderFilter);
386 EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
387 // header.h should be unused.
388 EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
389 }
390 {
391 auto HeaderFilter = [](llvm::StringRef Path) { return Path == "header.h"; };
392 Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor(),
393 HeaderFilter);
394 // header.h should be ignored now.
395 EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
396 EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
397 }
398}
399
400// Make sure that the references to implicit operator new/delete are reported as
401// ambigious.
402TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotMissing) {
403 ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
404 ExtraFS->addFile(Path: "header.h",
405 /*ModificationTime=*/{},
406 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp(
407 void* operator new(decltype(sizeof(int)));
408 )cpp")));
409 ExtraFS->addFile(Path: "wrapper.h",
410 /*ModificationTime=*/{},
411 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp(
412 #include "header.h"
413 )cpp")));
414
415 Inputs.Code = R"cpp(
416 #include "wrapper.h"
417 void bar() {
418 operator new(3);
419 })cpp";
420 TestAST AST(Inputs);
421 std::vector<Decl *> DeclsInTU;
422 for (auto *D : AST.context().getTranslationUnitDecl()->decls())
423 DeclsInTU.push_back(D);
424 auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
425 EXPECT_THAT(Results.Missing, testing::IsEmpty());
426}
427
428TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) {
429 ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
430 ExtraFS->addFile(Path: "header.h",
431 /*ModificationTime=*/{},
432 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: guard(Code: R"cpp(
433 void* operator new(decltype(sizeof(int)));
434 )cpp")));
435
436 Inputs.Code = R"cpp(
437 #include "header.h"
438 void bar() {
439 operator new(3);
440 })cpp";
441 TestAST AST(Inputs);
442 std::vector<Decl *> DeclsInTU;
443 for (auto *D : AST.context().getTranslationUnitDecl()->decls())
444 DeclsInTU.push_back(D);
445 auto Results = analyze(ASTRoots: DeclsInTU, MacroRefs: {}, I: PP.Includes, PI: &PI, PP: AST.preprocessor());
446 EXPECT_THAT(Results.Unused, testing::IsEmpty());
447}
448
449TEST(FixIncludes, Basic) {
450 llvm::StringRef Code = R"cpp(#include "d.h"
451#include "a.h"
452#include "b.h"
453#include <c.h>
454)cpp";
455
456 Includes Inc;
457 Include I;
458 I.Spelled = "a.h";
459 I.Line = 2;
460 Inc.add(I);
461 I.Spelled = "b.h";
462 I.Line = 3;
463 Inc.add(I);
464 I.Spelled = "c.h";
465 I.Line = 4;
466 I.Angled = true;
467 Inc.add(I);
468
469 AnalysisResults Results;
470 Results.Missing.emplace_back(args: "\"aa.h\"", args: Header(""));
471 Results.Missing.emplace_back(args: "\"ab.h\"", args: Header(""));
472 Results.Missing.emplace_back(args: "<e.h>", args: Header(""));
473 Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 3));
474 Results.Unused.push_back(x: Inc.atLine(OneBasedIndex: 4));
475
476 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
477R"cpp(#include "d.h"
478#include "a.h"
479#include "aa.h"
480#include "ab.h"
481#include <e.h>
482)cpp");
483
484 Results = {};
485 Results.Missing.emplace_back(args: "\"d.h\"", args: Header(""));
486 Code = R"cpp(#include "a.h")cpp";
487 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
488R"cpp(#include "d.h"
489#include "a.h")cpp");
490}
491
492MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
493 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
494 return ExpanedFileID == FileID && ExpandedOffset == Offset;
495}
496MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
497 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
498 return SpelledFileID == FileID && SpelledOffset == Offset;
499}
500TEST(WalkUsed, FilterRefsNotSpelledInMainFile) {
501 // Each test is expected to have a single expected ref of `target` symbol
502 // (or have none).
503 // The location in the reported ref is a macro location. $expand points to
504 // the macro location, and $spell points to the spelled location.
505 struct {
506 llvm::StringRef Header;
507 llvm::StringRef Main;
508 } TestCases[] = {
509 // Tests for decl references.
510 {
511 /*Header=*/"int target();",
512 .Main: R"cpp(
513 #define CALL_FUNC $spell^target()
514
515 int b = $expand^CALL_FUNC;
516 )cpp",
517 },
518 {/*Header=*/R"cpp(
519 int target();
520 #define CALL_FUNC target()
521 )cpp",
522 // No ref of `target` being reported, as it is not spelled in main file.
523 .Main: "int a = CALL_FUNC;"},
524 {
525 /*Header=*/R"cpp(
526 int target();
527 #define PLUS_ONE(X) X() + 1
528 )cpp",
529 .Main: R"cpp(
530 int a = $expand^PLUS_ONE($spell^target);
531 )cpp",
532 },
533 {
534 /*Header=*/R"cpp(
535 int target();
536 #define PLUS_ONE(X) X() + 1
537 )cpp",
538 .Main: R"cpp(
539 int a = $expand^PLUS_ONE($spell^target);
540 )cpp",
541 },
542 // Tests for macro references
543 {/*Header=*/"#define target 1",
544 .Main: R"cpp(
545 #define USE_target $spell^target
546 int b = $expand^USE_target;
547 )cpp"},
548 {/*Header=*/R"cpp(
549 #define target 1
550 #define USE_target target
551 )cpp",
552 // No ref of `target` being reported, it is not spelled in main file.
553 .Main: R"cpp(
554 int a = USE_target;
555 )cpp"},
556 };
557
558 for (const auto &T : TestCases) {
559 llvm::Annotations Main(T.Main);
560 TestInputs Inputs(Main.code());
561 Inputs.ExtraFiles["header.h"] = guard(Code: T.Header);
562 RecordedPP Recorded;
563 Inputs.MakeAction = [&]() {
564 struct RecordAction : public SyntaxOnlyAction {
565 RecordedPP &Out;
566 RecordAction(RecordedPP &Out) : Out(Out) {}
567 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
568 auto &PP = CI.getPreprocessor();
569 PP.addPPCallbacks(C: Out.record(PP));
570 return true;
571 }
572 };
573 return std::make_unique<RecordAction>(args&: Recorded);
574 };
575 Inputs.ExtraArgs.push_back(x: "-include");
576 Inputs.ExtraArgs.push_back(x: "header.h");
577 TestAST AST(Inputs);
578 llvm::SmallVector<Decl *> TopLevelDecls;
579 for (Decl *D : AST.context().getTranslationUnitDecl()->decls())
580 TopLevelDecls.emplace_back(D);
581 auto &SM = AST.sourceManager();
582
583 SourceLocation RefLoc;
584 walkUsed(ASTRoots: TopLevelDecls, MacroRefs: Recorded.MacroReferences,
585 /*PragmaIncludes=*/PI: nullptr, PP: AST.preprocessor(),
586 CB: [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {
587 if (!Ref.RefLocation.isMacroID())
588 return;
589 if (llvm::to_string(Value: Ref.Target) == "target") {
590 ASSERT_TRUE(RefLoc.isInvalid())
591 << "Expected only one 'target' ref loc per testcase";
592 RefLoc = Ref.RefLocation;
593 }
594 });
595 FileID MainFID = SM.getMainFileID();
596 if (RefLoc.isValid()) {
597 EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand"), &SM),
598 spelledAt(MainFID, Main.point("spell"), &SM)))
599 << T.Main.str();
600 } else {
601 EXPECT_THAT(Main.points(), testing::IsEmpty());
602 }
603 }
604}
605
606struct Tag {
607 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
608 return OS << "Anon Tag";
609 }
610};
611TEST(Hints, Ordering) {
612 auto Hinted = [](Hints Hints) {
613 return clang::include_cleaner::Hinted<Tag>({}, Hints);
614 };
615 EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol));
616 EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader));
617 EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader));
618 EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader),
619 Hinted(Hints::PublicHeader));
620}
621
622// Test ast traversal & redecl selection end-to-end for templates, as explicit
623// instantiations/specializations are not redecls of the primary template. We
624// need to make sure we're selecting the right ones.
625TEST_F(WalkUsedTest, TemplateDecls) {
626 llvm::Annotations Code(R"cpp(
627 #include "fwd.h"
628 #include "def.h"
629 #include "partial.h"
630 template <> struct $exp_spec^Foo<char> {};
631 template struct $exp^Foo<int>;
632 $full^Foo<int> x;
633 $implicit^Foo<bool> y;
634 $partial^Foo<int*> z;
635 )cpp");
636 Inputs.Code = Code.code();
637 Inputs.ExtraFiles["fwd.h"] = guard(Code: "template<typename> struct Foo;");
638 Inputs.ExtraFiles["def.h"] = guard(Code: "template<typename> struct Foo {};");
639 Inputs.ExtraFiles["partial.h"] =
640 guard(Code: "template<typename T> struct Foo<T*> {};");
641 TestAST AST(Inputs);
642 auto &SM = AST.sourceManager();
643 auto Fwd = *SM.getFileManager().getOptionalFileRef(Filename: "fwd.h");
644 auto Def = *SM.getFileManager().getOptionalFileRef(Filename: "def.h");
645 auto Partial = *SM.getFileManager().getOptionalFileRef(Filename: "partial.h");
646
647 EXPECT_THAT(
648 offsetToProviders(AST),
649 AllOf(Contains(
650 Pair(Code.point("exp_spec"), UnorderedElementsAre(Fwd, Def))),
651 Contains(Pair(Code.point("exp"), UnorderedElementsAre(Fwd, Def))),
652 Contains(Pair(Code.point("full"), UnorderedElementsAre(Fwd, Def))),
653 Contains(
654 Pair(Code.point("implicit"), UnorderedElementsAre(Fwd, Def))),
655 Contains(
656 Pair(Code.point("partial"), UnorderedElementsAre(Partial)))));
657}
658
659TEST_F(WalkUsedTest, IgnoresIdentityMacros) {
660 llvm::Annotations Code(R"cpp(
661 #include "header.h"
662 void $bar^bar() {
663 $stdin^stdin();
664 }
665 )cpp");
666 Inputs.Code = Code.code();
667 Inputs.ExtraFiles["header.h"] = guard(Code: R"cpp(
668 #include "inner.h"
669 void stdin();
670 )cpp");
671 Inputs.ExtraFiles["inner.h"] = guard(Code: R"cpp(
672 #define stdin stdin
673 )cpp");
674
675 TestAST AST(Inputs);
676 auto &SM = AST.sourceManager();
677 auto MainFile = Header(*SM.getFileEntryRefForID(FID: SM.getMainFileID()));
678 EXPECT_THAT(offsetToProviders(AST),
679 UnorderedElementsAre(
680 // FIXME: we should have a reference from stdin to header.h
681 Pair(Code.point("bar"), UnorderedElementsAre(MainFile))));
682}
683} // namespace
684} // namespace clang::include_cleaner
685

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp