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 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; 49 50 std::string guard(llvm::StringRef Code) { 51 return "#pragma once\n" + Code.str(); 52 } 53 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; 66 } 67 68 PragmaIncludes *Out; 69 }; 70 return std::make_unique<Hook>(&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(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; 93 } 94 }; 95 96 TEST_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(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"); 118 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)))); 139 } 140 141 TEST_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(R"cpp( 153 void foo(); 154 )cpp"); 155 Inputs.ExtraFiles["header2.h"] = guard(R"cpp( 156 void foo(); 157 )cpp"); 158 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)))); 168 } 169 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())); 184 185 auto HdrID = SM.translateFile(HdrFile); 186 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)))); 205 } 206 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; 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(PP.record(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 CI.getFileManager().getVirtualFileSystemPtr()); 233 OverlayFS->pushOverlay(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>(PP, PI, ExtraFS); 243 }; 244 } 245 }; 246 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 252 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")); 269 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)); 274 } 275 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()); 286 } 287 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()); 296 } 297 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(); 308 } 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()); 321 } 322 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(); 333 } 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()); 346 } 347 348 TEST_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("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=*/{}); 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(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())); 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(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())); 389 } 390 { 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\"", _))); 397 } 398 } 399 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"; 406 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); 419 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)); 426 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"); 434 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"); 441 } 442 443 MATCHER_P3(expandedAt, FileID, Offset, SM, "") { 444 auto [ExpanedFileID, ExpandedOffset] = SM->getDecomposedExpansionLoc(arg); 445 return ExpanedFileID == FileID && ExpandedOffset == Offset; 446 } 447 MATCHER_P3(spelledAt, FileID, Offset, SM, "") { 448 auto [SpelledFileID, SpelledOffset] = SM->getDecomposedSpellingLoc(arg); 449 return SpelledFileID == FileID && SpelledOffset == Offset; 450 } 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. 461 { 462 /*Header=*/"int target();", 463 R"cpp( 464 #define CALL_FUNC $spell^target() 465 466 int b = $expand^CALL_FUNC; 467 )cpp", 468 }, 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;"}, 475 { 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", 483 }, 484 { 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", 492 }, 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"}, 507 }; 508 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; 522 } 523 }; 524 return std::make_unique<RecordAction>(Recorded); 525 }; 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(); 533 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; 544 } 545 }); 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()); 553 } 554 } 555 } 556 557 struct Tag { 558 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Tag &T) { 559 return OS << "Anon Tag"; 560 } 561 }; 562 TEST(Hints, Ordering) { 563 auto Hinted = [](Hints Hints) { 564 return clang::include_cleaner::Hinted<Tag>({}, Hints); 565 }; 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)); 571 } 572 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"); 597 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))))); 608 } 609 610 TEST_F(WalkUsedTest, IgnoresIdentityMacros) { 611 llvm::Annotations Code(R"cpp( 612 #include "header.h" 613 void $bar^bar() { 614 $stdin^stdin(); 615 } 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"); 625 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)))); 633 } 634 } // namespace 635 } // namespace clang::include_cleaner 636