[MemProf] Templatize CallStackRadixTreeBuilder (NFC) (#117014)
[llvm-project.git] / clang-tools-extra / include-cleaner / unittests / AnalysisTest.cpp
blobd2d137a0dfb42acc5e78e538d7db525fedde0f3e
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 //===----------------------------------------------------------------------===//
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>
41 namespace clang::include_cleaner {
42 namespace {
43 using testing::_;
44 using testing::AllOf;
45 using testing::Contains;
46 using testing::ElementsAre;
47 using testing::Pair;
48 using testing::UnorderedElementsAre;
50 std::string guard(llvm::StringRef Code) {
51 return "#pragma once\n" + Code.str();
54 class WalkUsedTest : public testing::Test {
55 protected:
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;
68 PragmaIncludes *Out;
70 return std::make_unique<Hook>(&PI);
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);
84 std::multimap<size_t, std::vector<Header>> OffsetToProviders;
85 walkUsed(TopLevelDecls, MacroRefs, &PI, AST.preprocessor(),
86 [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Providers) {
87 auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation);
88 if (FID != SM.getMainFileID())
89 ADD_FAILURE() << "Reference outside of the main file!";
90 OffsetToProviders.emplace(Offset, Providers.vec());
91 });
92 return OffsetToProviders;
96 TEST_F(WalkUsedTest, Basic) {
97 llvm::Annotations Code(R"cpp(
98 #include "header.h"
99 #include "private.h"
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);
108 )cpp");
109 Inputs.Code = Code.code();
110 Inputs.ExtraFiles["header.h"] = guard(R"cpp(
111 void foo();
112 namespace std { class vector {}; int&& move(int&&); }
113 )cpp");
114 Inputs.ExtraFiles["private.h"] = guard(R"cpp(
115 // IWYU pragma: private, include "path/public.h"
116 class Private {};
117 )cpp");
119 TestAST AST(Inputs);
120 auto &SM = AST.sourceManager();
121 auto HeaderFile = Header(*AST.fileManager().getOptionalFileRef("header.h"));
122 auto PrivateFile = Header(*AST.fileManager().getOptionalFileRef("private.h"));
123 auto PublicFile = Header("\"path/public.h\"");
124 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
125 auto VectorSTL = Header(*tooling::stdlib::Header::named("<vector>"));
126 auto UtilitySTL = Header(*tooling::stdlib::Header::named("<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))));
141 TEST_F(WalkUsedTest, MultipleProviders) {
142 llvm::Annotations Code(R"cpp(
143 #include "header1.h"
144 #include "header2.h"
145 void foo();
147 void bar() {
148 $foo^foo();
150 )cpp");
151 Inputs.Code = Code.code();
152 Inputs.ExtraFiles["header1.h"] = guard(R"cpp(
153 void foo();
154 )cpp");
155 Inputs.ExtraFiles["header2.h"] = guard(R"cpp(
156 void foo();
157 )cpp");
159 TestAST AST(Inputs);
160 auto &SM = AST.sourceManager();
161 auto HeaderFile1 = Header(*AST.fileManager().getOptionalFileRef("header1.h"));
162 auto HeaderFile2 = Header(*AST.fileManager().getOptionalFileRef("header2.h"));
163 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
164 EXPECT_THAT(
165 offsetToProviders(AST),
166 Contains(Pair(Code.point("foo"),
167 UnorderedElementsAre(HeaderFile1, HeaderFile2, MainFile))));
170 TEST_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("#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("hdr.h");
183 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
185 auto HdrID = SM.translateFile(HdrFile);
187 Symbol Answer1 = Macro{PP.getIdentifierInfo("ANSWER"),
188 SM.getComposedLoc(HdrID, Hdr.point())};
189 Symbol Answer2 = Macro{PP.getIdentifierInfo("ANSWER"),
190 SM.getComposedLoc(HdrID, 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))));
207 class AnalyzeTest : public testing::Test {
208 protected:
209 TestInputs Inputs;
210 PragmaIncludes PI;
211 RecordedPP PP;
212 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS = nullptr;
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(PP.record(CI.getPreprocessor()));
223 PI.record(CI);
224 return true;
227 bool BeginInvocation(CompilerInstance &CI) override {
228 if (!ExtraFS)
229 return true;
230 auto OverlayFS =
231 llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(
232 CI.getFileManager().getVirtualFileSystemPtr());
233 OverlayFS->pushOverlay(ExtraFS);
234 CI.getFileManager().setVirtualFileSystem(std::move(OverlayFS));
235 return true;
238 RecordedPP &PP;
239 PragmaIncludes &PI;
240 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> ExtraFS;
242 return std::make_unique<Hook>(PP, PI, ExtraFS);
247 TEST_F(AnalyzeTest, Basic) {
248 Inputs.Code = R"cpp(
249 #include "a.h"
250 #include "b.h"
251 #include "keep.h" // IWYU pragma: keep
253 int x = a + c;
254 )cpp";
255 Inputs.ExtraFiles["a.h"] = guard("int a;");
256 Inputs.ExtraFiles["b.h"] = guard(R"cpp(
257 #include "c.h"
258 int b;
259 )cpp");
260 Inputs.ExtraFiles["c.h"] = guard("int c;");
261 Inputs.ExtraFiles["keep.h"] = guard("");
262 TestAST AST(Inputs);
263 auto Decls = AST.context().getTranslationUnitDecl()->decls();
264 auto Results =
265 analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
266 PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
267 auto CHeader = llvm::cantFail(
268 AST.context().getSourceManager().getFileManager().getFileRef("c.h"));
270 const Include *B = PP.Includes.atLine(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));
276 TEST_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("// 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({}, {}, PP.Includes, &PI, AST.preprocessor());
285 EXPECT_THAT(Results.Unused, testing::IsEmpty());
288 TEST_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({}, {}, PP.Includes, &PI, AST.preprocessor());
295 EXPECT_THAT(Results.Unused, testing::IsEmpty());
298 TEST_F(AnalyzeTest, ResourceDirIsIgnored) {
299 Inputs.ExtraArgs.push_back("-resource-dir");
300 Inputs.ExtraArgs.push_back("resources");
301 Inputs.ExtraArgs.push_back("-internal-isystem");
302 Inputs.ExtraArgs.push_back("resources/include");
303 Inputs.Code = R"cpp(
304 #include <amintrin.h>
305 #include <imintrin.h>
306 void baz() {
307 bar();
309 )cpp";
310 Inputs.ExtraFiles["resources/include/amintrin.h"] = guard("");
311 Inputs.ExtraFiles["resources/include/emintrin.h"] = guard(R"cpp(
312 void bar();
313 )cpp");
314 Inputs.ExtraFiles["resources/include/imintrin.h"] = guard(R"cpp(
315 #include <emintrin.h>
316 )cpp");
317 TestAST AST(Inputs);
318 auto Results = analyze({}, {}, PP.Includes, &PI, AST.preprocessor());
319 EXPECT_THAT(Results.Unused, testing::IsEmpty());
320 EXPECT_THAT(Results.Missing, testing::IsEmpty());
323 TEST_F(AnalyzeTest, DifferentHeaderSameSpelling) {
324 Inputs.ExtraArgs.push_back("-Ifoo");
325 Inputs.ExtraArgs.push_back("-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();
334 )cpp";
335 Inputs.ExtraFiles["foo/foo.h"] = guard("#include_next <foo.h>");
336 Inputs.ExtraFiles["foo_inner/foo.h"] = guard(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(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());
344 EXPECT_THAT(Results.Unused, testing::IsEmpty());
345 EXPECT_THAT(Results.Missing, testing::IsEmpty());
348 TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
349 llvm::Annotations Code(R"cpp(
350 #include "header.h"
351 void $bar^bar() {
352 $foo^foo();
354 )cpp");
355 Inputs.Code = Code.code();
356 ExtraFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
357 ExtraFS->addFile("content_for/0", /*ModificationTime=*/{},
358 llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(
359 #include "inner.h"
360 )cpp")));
361 ExtraFS->addSymbolicLink("header.h", "content_for/0",
362 /*ModificationTime=*/{});
363 ExtraFS->addFile("content_for/1", /*ModificationTime=*/{},
364 llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp(
365 void foo();
366 )cpp")));
367 ExtraFS->addSymbolicLink("inner.h", "content_for/1",
368 /*ModificationTime=*/{});
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(DeclsInTU, {}, PP.Includes, &PI, 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()));
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(DeclsInTU, {}, PP.Includes, &PI, 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()));
391 auto HeaderFilter = [](llvm::StringRef Path) { return Path == "header.h"; };
392 Results = analyze(DeclsInTU, {}, PP.Includes, &PI, 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\"", _)));
400 TEST(FixIncludes, Basic) {
401 llvm::StringRef Code = R"cpp(#include "d.h"
402 #include "a.h"
403 #include "b.h"
404 #include <c.h>
405 )cpp";
407 Includes Inc;
408 Include I;
409 I.Spelled = "a.h";
410 I.Line = 2;
411 Inc.add(I);
412 I.Spelled = "b.h";
413 I.Line = 3;
414 Inc.add(I);
415 I.Spelled = "c.h";
416 I.Line = 4;
417 I.Angled = true;
418 Inc.add(I);
420 AnalysisResults Results;
421 Results.Missing.emplace_back("\"aa.h\"", Header(""));
422 Results.Missing.emplace_back("\"ab.h\"", Header(""));
423 Results.Missing.emplace_back("<e.h>", Header(""));
424 Results.Unused.push_back(Inc.atLine(3));
425 Results.Unused.push_back(Inc.atLine(4));
427 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
428 R"cpp(#include "d.h"
429 #include "a.h"
430 #include "aa.h"
431 #include "ab.h"
432 #include <e.h>
433 )cpp");
435 Results = {};
436 Results.Missing.emplace_back("\"d.h\"", Header(""));
437 Code = R"cpp(#include "a.h")cpp";
438 EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
439 R"cpp(#include "d.h"
440 #include "a.h")cpp");
443 MATCHER_P3(expandedAt, FileID, Offset, SM, "") {
444 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg);
445 return ExpanedFileID == FileID && ExpandedOffset == Offset;
447 MATCHER_P3(spelledAt, FileID, Offset, SM, "") {
448 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg);
449 return SpelledFileID == FileID && SpelledOffset == Offset;
451 TEST(WalkUsed, FilterRefsNotSpelledInMainFile) {
452 // Each test is expected to have a single expected ref of `target` symbol
453 // (or have none).
454 // The location in the reported ref is a macro location. $expand points to
455 // the macro location, and $spell points to the spelled location.
456 struct {
457 llvm::StringRef Header;
458 llvm::StringRef Main;
459 } TestCases[] = {
460 // Tests for decl references.
462 /*Header=*/"int target();",
463 R"cpp(
464 #define CALL_FUNC $spell^target()
466 int b = $expand^CALL_FUNC;
467 )cpp",
469 {/*Header=*/R"cpp(
470 int target();
471 #define CALL_FUNC target()
472 )cpp",
473 // No ref of `target` being reported, as it is not spelled in main file.
474 "int a = CALL_FUNC;"},
476 /*Header=*/R"cpp(
477 int target();
478 #define PLUS_ONE(X) X() + 1
479 )cpp",
480 R"cpp(
481 int a = $expand^PLUS_ONE($spell^target);
482 )cpp",
485 /*Header=*/R"cpp(
486 int target();
487 #define PLUS_ONE(X) X() + 1
488 )cpp",
489 R"cpp(
490 int a = $expand^PLUS_ONE($spell^target);
491 )cpp",
493 // Tests for macro references
494 {/*Header=*/"#define target 1",
495 R"cpp(
496 #define USE_target $spell^target
497 int b = $expand^USE_target;
498 )cpp"},
499 {/*Header=*/R"cpp(
500 #define target 1
501 #define USE_target target
502 )cpp",
503 // No ref of `target` being reported, it is not spelled in main file.
504 R"cpp(
505 int a = USE_target;
506 )cpp"},
509 for (const auto &T : TestCases) {
510 llvm::Annotations Main(T.Main);
511 TestInputs Inputs(Main.code());
512 Inputs.ExtraFiles["header.h"] = guard(T.Header);
513 RecordedPP Recorded;
514 Inputs.MakeAction = [&]() {
515 struct RecordAction : public SyntaxOnlyAction {
516 RecordedPP &Out;
517 RecordAction(RecordedPP &Out) : Out(Out) {}
518 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
519 auto &PP = CI.getPreprocessor();
520 PP.addPPCallbacks(Out.record(PP));
521 return true;
524 return std::make_unique<RecordAction>(Recorded);
526 Inputs.ExtraArgs.push_back("-include");
527 Inputs.ExtraArgs.push_back("header.h");
528 TestAST AST(Inputs);
529 llvm::SmallVector<Decl *> TopLevelDecls;
530 for (Decl *D : AST.context().getTranslationUnitDecl()->decls())
531 TopLevelDecls.emplace_back(D);
532 auto &SM = AST.sourceManager();
534 SourceLocation RefLoc;
535 walkUsed(TopLevelDecls, Recorded.MacroReferences,
536 /*PragmaIncludes=*/nullptr, AST.preprocessor(),
537 [&](const SymbolReference &Ref, llvm::ArrayRef<Header>) {
538 if (!Ref.RefLocation.isMacroID())
539 return;
540 if (llvm::to_string(Ref.Target) == "target") {
541 ASSERT_TRUE(RefLoc.isInvalid())
542 << "Expected only one 'target' ref loc per testcase";
543 RefLoc = Ref.RefLocation;
546 FileID MainFID = SM.getMainFileID();
547 if (RefLoc.isValid()) {
548 EXPECT_THAT(RefLoc, AllOf(expandedAt(MainFID, Main.point("expand"), &SM),
549 spelledAt(MainFID, Main.point("spell"), &SM)))
550 << T.Main.str();
551 } else {
552 EXPECT_THAT(Main.points(), testing::IsEmpty());
557 struct Tag {
558 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) {
559 return OS << "Anon Tag";
562 TEST(Hints, Ordering) {
563 auto Hinted = [](Hints Hints) {
564 return clang::include_cleaner::Hinted<Tag>({}, Hints);
566 EXPECT_LT(Hinted(Hints::None), Hinted(Hints::CompleteSymbol));
567 EXPECT_LT(Hinted(Hints::CompleteSymbol), Hinted(Hints::PublicHeader));
568 EXPECT_LT(Hinted(Hints::PreferredHeader), Hinted(Hints::PublicHeader));
569 EXPECT_LT(Hinted(Hints::CompleteSymbol | Hints::PreferredHeader),
570 Hinted(Hints::PublicHeader));
573 // Test ast traversal & redecl selection end-to-end for templates, as explicit
574 // instantiations/specializations are not redecls of the primary template. We
575 // need to make sure we're selecting the right ones.
576 TEST_F(WalkUsedTest, TemplateDecls) {
577 llvm::Annotations Code(R"cpp(
578 #include "fwd.h"
579 #include "def.h"
580 #include "partial.h"
581 template <> struct $exp_spec^Foo<char> {};
582 template struct $exp^Foo<int>;
583 $full^Foo<int> x;
584 $implicit^Foo<bool> y;
585 $partial^Foo<int*> z;
586 )cpp");
587 Inputs.Code = Code.code();
588 Inputs.ExtraFiles["fwd.h"] = guard("template<typename> struct Foo;");
589 Inputs.ExtraFiles["def.h"] = guard("template<typename> struct Foo {};");
590 Inputs.ExtraFiles["partial.h"] =
591 guard("template<typename T> struct Foo<T*> {};");
592 TestAST AST(Inputs);
593 auto &SM = AST.sourceManager();
594 auto Fwd = *SM.getFileManager().getOptionalFileRef("fwd.h");
595 auto Def = *SM.getFileManager().getOptionalFileRef("def.h");
596 auto Partial = *SM.getFileManager().getOptionalFileRef("partial.h");
598 EXPECT_THAT(
599 offsetToProviders(AST),
600 AllOf(Contains(
601 Pair(Code.point("exp_spec"), UnorderedElementsAre(Fwd, Def))),
602 Contains(Pair(Code.point("exp"), UnorderedElementsAre(Fwd, Def))),
603 Contains(Pair(Code.point("full"), UnorderedElementsAre(Fwd, Def))),
604 Contains(
605 Pair(Code.point("implicit"), UnorderedElementsAre(Fwd, Def))),
606 Contains(
607 Pair(Code.point("partial"), UnorderedElementsAre(Partial)))));
610 TEST_F(WalkUsedTest, IgnoresIdentityMacros) {
611 llvm::Annotations Code(R"cpp(
612 #include "header.h"
613 void $bar^bar() {
614 $stdin^stdin();
616 )cpp");
617 Inputs.Code = Code.code();
618 Inputs.ExtraFiles["header.h"] = guard(R"cpp(
619 #include "inner.h"
620 void stdin();
621 )cpp");
622 Inputs.ExtraFiles["inner.h"] = guard(R"cpp(
623 #define stdin stdin
624 )cpp");
626 TestAST AST(Inputs);
627 auto &SM = AST.sourceManager();
628 auto MainFile = Header(*SM.getFileEntryRefForID(SM.getMainFileID()));
629 EXPECT_THAT(offsetToProviders(AST),
630 UnorderedElementsAre(
631 // FIXME: we should have a reference from stdin to header.h
632 Pair(Code.point("bar"), UnorderedElementsAre(MainFile))));
634 } // namespace
635 } // namespace clang::include_cleaner