1 //===-- RecordTest.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/Record.h" 10 #include "clang-include-cleaner/Types.h" 11 #include "clang/AST/Decl.h" 12 #include "clang/Basic/Diagnostic.h" 13 #include "clang/Basic/FileEntry.h" 14 #include "clang/Basic/LLVM.h" 15 #include "clang/Basic/SourceLocation.h" 16 #include "clang/Frontend/CompilerInvocation.h" 17 #include "clang/Frontend/FrontendAction.h" 18 #include "clang/Frontend/FrontendActions.h" 19 #include "clang/Frontend/FrontendOptions.h" 20 #include "clang/Serialization/PCHContainerOperations.h" 21 #include "clang/Testing/TestAST.h" 22 #include "clang/Tooling/Inclusions/StandardLibrary.h" 23 #include "llvm/ADT/ArrayRef.h" 24 #include "llvm/ADT/IntrusiveRefCntPtr.h" 25 #include "llvm/ADT/StringRef.h" 26 #include "llvm/Support/Error.h" 27 #include "llvm/Support/MemoryBuffer.h" 28 #include "llvm/Support/Path.h" 29 #include "llvm/Support/VirtualFileSystem.h" 30 #include "llvm/Support/raw_ostream.h" 31 #include "llvm/Testing/Annotations/Annotations.h" 32 #include "gmock/gmock.h" 33 #include "gtest/gtest.h" 34 #include <cassert> 35 #include <memory> 36 #include <optional> 37 #include <utility> 38 39 namespace clang::include_cleaner { 40 namespace { 41 using testing::ElementsAreArray; 42 using testing::IsEmpty; 43 44 // Matches a Decl* if it is a NamedDecl with the given name. 45 MATCHER_P(named, N, "") { 46 if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(arg)) { 47 if (N == ND->getNameAsString()) 48 return true; 49 } 50 std::string S; 51 llvm::raw_string_ostream OS(S); 52 arg->dump(OS); 53 *result_listener << S; 54 return false; 55 } 56 57 MATCHER_P(FileNamed, N, "") { 58 llvm::StringRef ActualName = 59 llvm::sys::path::remove_leading_dotslash(arg.getName()); 60 if (ActualName == N) 61 return true; 62 *result_listener << ActualName.str(); 63 return false; 64 } 65 66 class RecordASTTest : public ::testing::Test { 67 protected: 68 TestInputs Inputs; 69 RecordedAST Recorded; 70 71 RecordASTTest() { 72 struct RecordAction : public ASTFrontendAction { 73 RecordedAST &Out; 74 RecordAction(RecordedAST &Out) : Out(Out) {} 75 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 76 StringRef) override { 77 return Out.record(); 78 } 79 }; 80 Inputs.MakeAction = [this] { 81 return std::make_unique<RecordAction>(Recorded); 82 }; 83 } 84 85 TestAST build() { return TestAST(Inputs); } 86 }; 87 88 // Top-level decl from the main file is a root, nested ones aren't. 89 TEST_F(RecordASTTest, Namespace) { 90 Inputs.Code = 91 R"cpp( 92 namespace ns { 93 int x; 94 namespace { 95 int y; 96 } 97 } 98 )cpp"; 99 auto AST = build(); 100 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("ns"))); 101 } 102 103 // Decl in included file is not a root. 104 TEST_F(RecordASTTest, Inclusion) { 105 Inputs.ExtraFiles["header.h"] = "void headerFunc();"; 106 Inputs.Code = R"cpp( 107 #include "header.h" 108 void mainFunc(); 109 )cpp"; 110 auto AST = build(); 111 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("mainFunc"))); 112 } 113 114 // Decl from macro expanded into the main file is a root. 115 TEST_F(RecordASTTest, Macros) { 116 Inputs.ExtraFiles["header.h"] = "#define X void x();"; 117 Inputs.Code = R"cpp( 118 #include "header.h" 119 X 120 )cpp"; 121 auto AST = build(); 122 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("x"))); 123 } 124 125 // Decl from template instantiation is filtered out from roots. 126 TEST_F(RecordASTTest, ImplicitTemplates) { 127 Inputs.ExtraFiles["dispatch.h"] = R"cpp( 128 struct A { 129 static constexpr int value = 1; 130 }; 131 template <class Getter> 132 int dispatch() { 133 return Getter::template get<A>(); 134 } 135 )cpp"; 136 Inputs.Code = R"cpp( 137 #include "dispatch.h" 138 struct MyGetter { 139 template <class T> static int get() { return T::value; } 140 }; 141 int v = dispatch<MyGetter>(); 142 )cpp"; 143 auto AST = build(); 144 EXPECT_THAT(Recorded.Roots, 145 testing::ElementsAre(named("MyGetter"), named("v"))); 146 } 147 148 class RecordPPTest : public ::testing::Test { 149 protected: 150 TestInputs Inputs; 151 RecordedPP Recorded; 152 153 RecordPPTest() { 154 struct RecordAction : public PreprocessOnlyAction { 155 RecordedPP &Out; 156 RecordAction(RecordedPP &Out) : Out(Out) {} 157 158 void ExecuteAction() override { 159 auto &PP = getCompilerInstance().getPreprocessor(); 160 PP.addPPCallbacks(Out.record(PP)); 161 PreprocessOnlyAction::ExecuteAction(); 162 } 163 }; 164 Inputs.MakeAction = [this] { 165 return std::make_unique<RecordAction>(Recorded); 166 }; 167 } 168 169 TestAST build() { return TestAST(Inputs); } 170 }; 171 172 // Matches an Include with a particular spelling. 173 MATCHER_P(spelled, S, "") { return arg.Spelled == S; } 174 175 TEST_F(RecordPPTest, CapturesIncludes) { 176 llvm::Annotations MainFile(R"cpp( 177 $H^#include "./header.h" 178 $M^#include <missing.h> 179 )cpp"); 180 Inputs.Code = MainFile.code(); 181 Inputs.ExtraFiles["header.h"] = ""; 182 Inputs.ErrorOK = true; // missing header 183 auto AST = build(); 184 185 ASSERT_THAT( 186 Recorded.Includes.all(), 187 testing::ElementsAre(spelled("./header.h"), spelled("missing.h"))); 188 189 auto &H = Recorded.Includes.all().front(); 190 EXPECT_EQ(H.Line, 2u); 191 EXPECT_EQ(H.HashLocation, 192 AST.sourceManager().getComposedLoc( 193 AST.sourceManager().getMainFileID(), MainFile.point("H"))); 194 EXPECT_EQ(H.Resolved, *AST.fileManager().getOptionalFileRef("header.h")); 195 EXPECT_FALSE(H.Angled); 196 197 auto &M = Recorded.Includes.all().back(); 198 EXPECT_EQ(M.Line, 3u); 199 EXPECT_EQ(M.HashLocation, 200 AST.sourceManager().getComposedLoc( 201 AST.sourceManager().getMainFileID(), MainFile.point("M"))); 202 EXPECT_EQ(M.Resolved, std::nullopt); 203 EXPECT_TRUE(M.Angled); 204 } 205 206 TEST_F(RecordPPTest, CapturesMacroRefs) { 207 llvm::Annotations Header(R"cpp( 208 #define $def^X 1 209 210 // Refs, but not in main file. 211 #define Y X 212 int one = X; 213 )cpp"); 214 llvm::Annotations MainFile(R"cpp( 215 #define EARLY X // not a ref, no definition 216 #include "header.h" 217 #define LATE ^X 218 #define LATE2 ^X // a ref even if not expanded 219 220 int uno = ^X; 221 int jeden = $exp^LATE; // a ref in LATE's expansion 222 223 #define IDENT(X) X // not a ref, shadowed 224 int eins = IDENT(^X); 225 226 #undef ^X 227 // Not refs, rather a new macro with the same name. 228 #define X 2 229 int two = X; 230 )cpp"); 231 Inputs.Code = MainFile.code(); 232 Inputs.ExtraFiles["header.h"] = Header.code(); 233 auto AST = build(); 234 const auto &SM = AST.sourceManager(); 235 236 SourceLocation Def = SM.getComposedLoc( 237 SM.translateFile(*AST.fileManager().getOptionalFileRef("header.h")), 238 Header.point("def")); 239 ASSERT_THAT(Recorded.MacroReferences, Not(IsEmpty())); 240 Symbol OrigX = Recorded.MacroReferences.front().Target; 241 EXPECT_EQ("X", OrigX.macro().Name->getName()); 242 EXPECT_EQ(Def, OrigX.macro().Definition); 243 244 std::vector<unsigned> RefOffsets; 245 std::vector<unsigned> ExpOffsets; // Expansion locs of refs in macro locs. 246 for (const auto &Ref : Recorded.MacroReferences) { 247 if (Ref.Target == OrigX) { 248 auto [FID, Off] = SM.getDecomposedLoc(Ref.RefLocation); 249 if (FID == SM.getMainFileID()) { 250 RefOffsets.push_back(Off); 251 } else if (Ref.RefLocation.isMacroID() && 252 SM.isWrittenInMainFile(SM.getExpansionLoc(Ref.RefLocation))) { 253 ExpOffsets.push_back( 254 SM.getDecomposedExpansionLoc(Ref.RefLocation).second); 255 } else { 256 ADD_FAILURE() << Ref.RefLocation.printToString(SM); 257 } 258 } 259 } 260 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points())); 261 EXPECT_THAT(ExpOffsets, ElementsAreArray(MainFile.points("exp"))); 262 } 263 264 TEST_F(RecordPPTest, CapturesConditionalMacroRefs) { 265 llvm::Annotations MainFile(R"cpp( 266 #define X 1 267 268 #ifdef ^X 269 #endif 270 271 #if defined(^X) 272 #endif 273 274 #ifndef ^X 275 #endif 276 277 #ifdef Y 278 #elifdef ^X 279 #endif 280 281 #ifndef ^X 282 #elifndef ^X 283 #endif 284 )cpp"); 285 286 Inputs.Code = MainFile.code(); 287 Inputs.ExtraArgs.push_back("-std=c++2b"); 288 auto AST = build(); 289 290 std::vector<unsigned> RefOffsets; 291 SourceManager &SM = AST.sourceManager(); 292 for (const auto &Ref : Recorded.MacroReferences) { 293 auto [FID, Off] = SM.getDecomposedLoc(Ref.RefLocation); 294 ASSERT_EQ(FID, SM.getMainFileID()); 295 EXPECT_EQ(Ref.RT, RefType::Ambiguous); 296 EXPECT_EQ("X", Ref.Target.macro().Name->getName()); 297 RefOffsets.push_back(Off); 298 } 299 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points())); 300 } 301 302 class PragmaIncludeTest : public ::testing::Test { 303 protected: 304 // We don't build an AST, we just run a preprocessor action! 305 TestInputs Inputs; 306 PragmaIncludes PI; 307 308 PragmaIncludeTest() { 309 Inputs.MakeAction = [this] { 310 struct Hook : public PreprocessOnlyAction { 311 public: 312 Hook(PragmaIncludes *Out) : Out(Out) {} 313 bool BeginSourceFileAction(clang::CompilerInstance &CI) override { 314 Out->record(CI); 315 return true; 316 } 317 PragmaIncludes *Out; 318 }; 319 return std::make_unique<Hook>(&PI); 320 }; 321 } 322 323 TestAST build(bool ResetPragmaIncludes = true) { 324 if (ResetPragmaIncludes) 325 PI = PragmaIncludes(); 326 return TestAST(Inputs); 327 } 328 329 void createEmptyFiles(llvm::ArrayRef<StringRef> FileNames) { 330 for (llvm::StringRef File : FileNames) 331 Inputs.ExtraFiles[File] = "#pragma once"; 332 } 333 }; 334 335 TEST_F(PragmaIncludeTest, IWYUKeep) { 336 Inputs.Code = R"cpp( 337 #include "keep1.h" // IWYU pragma: keep 338 #include "keep2.h" /* IWYU pragma: keep */ 339 340 #include "export1.h" // IWYU pragma: export 341 // IWYU pragma: begin_exports 342 #include "export2.h" 343 #include "export3.h" 344 // IWYU pragma: end_exports 345 346 #include "normal.h" 347 348 // IWYU pragma: begin_keep 349 #include "keep3.h" 350 // IWYU pragma: end_keep 351 352 // IWYU pragma: begin_keep 353 #include "keep4.h" 354 // IWYU pragma: begin_keep 355 #include "keep5.h" 356 // IWYU pragma: end_keep 357 #include "keep6.h" 358 // IWYU pragma: end_keep 359 #include <vector> 360 #include <map> // IWYU pragma: keep 361 #include <set> // IWYU pragma: export 362 )cpp"; 363 createEmptyFiles({"keep1.h", "keep2.h", "keep3.h", "keep4.h", "keep5.h", 364 "keep6.h", "export1.h", "export2.h", "export3.h", 365 "normal.h", "std/vector", "std/map", "std/set"}); 366 367 Inputs.ExtraArgs.push_back("-isystemstd"); 368 TestAST Processed = build(); 369 auto &FM = Processed.fileManager(); 370 371 EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("normal.h"))); 372 EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("std/vector"))); 373 374 // Keep 375 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep1.h"))); 376 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep2.h"))); 377 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep3.h"))); 378 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep4.h"))); 379 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep5.h"))); 380 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep6.h"))); 381 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("std/map"))); 382 383 // Exports 384 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export1.h"))); 385 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export2.h"))); 386 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export3.h"))); 387 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("std/set"))); 388 } 389 390 TEST_F(PragmaIncludeTest, AssociatedHeader) { 391 createEmptyFiles({"foo/main.h", "bar/main.h", "bar/other.h", "std/vector"}); 392 auto IsKeep = [&](llvm::StringRef Name, TestAST &AST) { 393 return PI.shouldKeep(*AST.fileManager().getOptionalFileRef(Name)); 394 }; 395 396 Inputs.FileName = "main.cc"; 397 Inputs.ExtraArgs.push_back("-isystemstd"); 398 { 399 Inputs.Code = R"cpp( 400 #include "foo/main.h" 401 #include "bar/main.h" 402 )cpp"; 403 auto AST = build(); 404 EXPECT_TRUE(IsKeep("foo/main.h", AST)); 405 EXPECT_FALSE(IsKeep("bar/main.h", AST)) << "not first include"; 406 } 407 408 { 409 Inputs.Code = R"cpp( 410 #include "bar/other.h" 411 #include "bar/main.h" 412 )cpp"; 413 auto AST = build(); 414 EXPECT_FALSE(IsKeep("bar/other.h", AST)); 415 EXPECT_FALSE(IsKeep("bar/main.h", AST)) << "not first include"; 416 } 417 418 { 419 Inputs.Code = R"cpp( 420 #include "foo/main.h" 421 #include "bar/other.h" // IWYU pragma: associated 422 #include <vector> // IWYU pragma: associated 423 )cpp"; 424 auto AST = build(); 425 EXPECT_TRUE(IsKeep("foo/main.h", AST)); 426 EXPECT_TRUE(IsKeep("bar/other.h", AST)); 427 EXPECT_TRUE(IsKeep("std/vector", AST)); 428 } 429 430 Inputs.FileName = "vector.cc"; 431 { 432 Inputs.Code = R"cpp( 433 #include <vector> 434 )cpp"; 435 auto AST = build(); 436 EXPECT_FALSE(IsKeep("std/vector", AST)) << "stdlib is not associated"; 437 } 438 } 439 440 TEST_F(PragmaIncludeTest, IWYUPrivate) { 441 Inputs.Code = R"cpp( 442 #include "public.h" 443 )cpp"; 444 Inputs.ExtraFiles["public.h"] = R"cpp( 445 #include "private.h" 446 #include "private2.h" 447 )cpp"; 448 Inputs.ExtraFiles["private.h"] = R"cpp( 449 // IWYU pragma: private, include "public2.h" 450 )cpp"; 451 Inputs.ExtraFiles["private2.h"] = R"cpp( 452 // IWYU pragma: private 453 )cpp"; 454 TestAST Processed = build(); 455 auto PrivateFE = Processed.fileManager().getOptionalFileRef("private.h"); 456 assert(PrivateFE); 457 EXPECT_TRUE(PI.isPrivate(*PrivateFE)); 458 EXPECT_EQ(PI.getPublic(*PrivateFE), "\"public2.h\""); 459 460 auto PublicFE = Processed.fileManager().getOptionalFileRef("public.h"); 461 assert(PublicFE); 462 EXPECT_EQ(PI.getPublic(*PublicFE), ""); // no mapping. 463 EXPECT_FALSE(PI.isPrivate(*PublicFE)); 464 465 auto Private2FE = Processed.fileManager().getOptionalFileRef("private2.h"); 466 assert(Private2FE); 467 EXPECT_TRUE(PI.isPrivate(*Private2FE)); 468 } 469 470 TEST_F(PragmaIncludeTest, IWYUExport) { 471 Inputs.Code = R"cpp(// Line 1 472 #include "export1.h" 473 #include "export2.h" 474 )cpp"; 475 Inputs.ExtraFiles["export1.h"] = R"cpp( 476 #include "private.h" // IWYU pragma: export 477 )cpp"; 478 Inputs.ExtraFiles["export2.h"] = R"cpp( 479 #include "export3.h" 480 )cpp"; 481 Inputs.ExtraFiles["export3.h"] = R"cpp( 482 #include "private.h" // IWYU pragma: export 483 )cpp"; 484 Inputs.ExtraFiles["private.h"] = ""; 485 TestAST Processed = build(); 486 const auto &SM = Processed.sourceManager(); 487 auto &FM = Processed.fileManager(); 488 489 EXPECT_THAT(PI.getExporters(*FM.getOptionalFileRef("private.h"), FM), 490 testing::UnorderedElementsAre(FileNamed("export1.h"), 491 FileNamed("export3.h"))); 492 493 EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export1.h"), FM).empty()); 494 EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export2.h"), FM).empty()); 495 EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export3.h"), FM).empty()); 496 EXPECT_TRUE( 497 PI.getExporters(SM.getFileEntryForID(SM.getMainFileID()), FM).empty()); 498 } 499 500 TEST_F(PragmaIncludeTest, IWYUExportForStandardHeaders) { 501 Inputs.Code = R"cpp( 502 #include "export.h" 503 )cpp"; 504 Inputs.ExtraFiles["export.h"] = R"cpp( 505 #include <string> // IWYU pragma: export 506 )cpp"; 507 Inputs.ExtraFiles["string"] = ""; 508 Inputs.ExtraArgs = {"-isystem."}; 509 TestAST Processed = build(); 510 auto &FM = Processed.fileManager(); 511 EXPECT_THAT(PI.getExporters(*tooling::stdlib::Header::named("<string>"), FM), 512 testing::UnorderedElementsAre(FileNamed("export.h"))); 513 EXPECT_THAT(PI.getExporters(llvm::cantFail(FM.getFileRef("string")), FM), 514 testing::UnorderedElementsAre(FileNamed("export.h"))); 515 } 516 517 TEST_F(PragmaIncludeTest, IWYUExportBlock) { 518 Inputs.Code = R"cpp(// Line 1 519 #include "normal.h" 520 )cpp"; 521 Inputs.ExtraFiles["normal.h"] = R"cpp( 522 #include "foo.h" 523 524 // IWYU pragma: begin_exports 525 #include "export1.h" 526 #include "private1.h" 527 // IWYU pragma: end_exports 528 )cpp"; 529 Inputs.ExtraFiles["export1.h"] = R"cpp( 530 // IWYU pragma: begin_exports 531 #include "private1.h" 532 #include "private2.h" 533 // IWYU pragma: end_exports 534 535 #include "bar.h" 536 #include "private3.h" // IWYU pragma: export 537 )cpp"; 538 createEmptyFiles( 539 {"private1.h", "private2.h", "private3.h", "foo.h", "bar.h"}); 540 TestAST Processed = build(); 541 auto &FM = Processed.fileManager(); 542 543 auto GetNames = [](llvm::ArrayRef<FileEntryRef> FEs) { 544 std::string Result; 545 llvm::raw_string_ostream OS(Result); 546 for (auto &FE : FEs) { 547 OS << FE.getName() << " "; 548 } 549 return Result; 550 }; 551 auto Exporters = PI.getExporters(*FM.getOptionalFileRef("private1.h"), FM); 552 EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h"), 553 FileNamed("normal.h"))) 554 << GetNames(Exporters); 555 556 Exporters = PI.getExporters(*FM.getOptionalFileRef("private2.h"), FM); 557 EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h"))) 558 << GetNames(Exporters); 559 560 Exporters = PI.getExporters(*FM.getOptionalFileRef("private3.h"), FM); 561 EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h"))) 562 << GetNames(Exporters); 563 564 Exporters = PI.getExporters(*FM.getOptionalFileRef("foo.h"), FM); 565 EXPECT_TRUE(Exporters.empty()) << GetNames(Exporters); 566 567 Exporters = PI.getExporters(*FM.getOptionalFileRef("bar.h"), FM); 568 EXPECT_TRUE(Exporters.empty()) << GetNames(Exporters); 569 } 570 571 TEST_F(PragmaIncludeTest, SelfContained) { 572 Inputs.Code = R"cpp( 573 #include "guarded.h" 574 575 #include "unguarded.h" 576 )cpp"; 577 Inputs.ExtraFiles["guarded.h"] = R"cpp( 578 #pragma once 579 )cpp"; 580 Inputs.ExtraFiles["unguarded.h"] = ""; 581 TestAST Processed = build(); 582 auto &FM = Processed.fileManager(); 583 EXPECT_TRUE(PI.isSelfContained(*FM.getOptionalFileRef("guarded.h"))); 584 EXPECT_FALSE(PI.isSelfContained(*FM.getOptionalFileRef("unguarded.h"))); 585 } 586 587 TEST_F(PragmaIncludeTest, AlwaysKeep) { 588 Inputs.Code = R"cpp( 589 #include "always_keep.h" 590 #include "usual.h" 591 )cpp"; 592 Inputs.ExtraFiles["always_keep.h"] = R"cpp( 593 #pragma once 594 // IWYU pragma: always_keep 595 )cpp"; 596 Inputs.ExtraFiles["usual.h"] = "#pragma once"; 597 TestAST Processed = build(); 598 auto &FM = Processed.fileManager(); 599 EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("always_keep.h"))); 600 EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("usual.h"))); 601 } 602 603 TEST_F(PragmaIncludeTest, ExportInUnnamedBuffer) { 604 llvm::StringLiteral Filename = "test.cpp"; 605 auto Code = R"cpp(#include "exporter.h")cpp"; 606 Inputs.ExtraFiles["exporter.h"] = R"cpp( 607 #pragma once 608 #include "foo.h" // IWYU pragma: export 609 )cpp"; 610 Inputs.ExtraFiles["foo.h"] = ""; 611 612 // Create unnamed memory buffers for all the files. 613 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); 614 VFS->addFile(Filename, /*ModificationTime=*/0, 615 llvm::MemoryBuffer::getMemBufferCopy(Code, /*BufferName=*/"")); 616 for (const auto &Extra : Inputs.ExtraFiles) 617 VFS->addFile(Extra.getKey(), /*ModificationTime=*/0, 618 llvm::MemoryBuffer::getMemBufferCopy(Extra.getValue(), 619 /*BufferName=*/"")); 620 621 auto Clang = std::make_unique<CompilerInstance>( 622 std::make_shared<PCHContainerOperations>()); 623 Clang->createDiagnostics(*VFS); 624 625 Clang->setInvocation(std::make_unique<CompilerInvocation>()); 626 ASSERT_TRUE(CompilerInvocation::CreateFromArgs( 627 Clang->getInvocation(), {Filename.data()}, Clang->getDiagnostics(), 628 "clang")); 629 630 auto *FM = Clang->createFileManager(VFS); 631 ASSERT_TRUE(Clang->ExecuteAction(*Inputs.MakeAction())); 632 EXPECT_THAT( 633 PI.getExporters(llvm::cantFail(FM->getFileRef("foo.h")), *FM), 634 testing::ElementsAre(llvm::cantFail(FM->getFileRef("exporter.h")))); 635 } 636 637 TEST_F(PragmaIncludeTest, OutlivesFMAndSM) { 638 Inputs.Code = R"cpp( 639 #include "public.h" 640 )cpp"; 641 Inputs.ExtraFiles["public.h"] = R"cpp( 642 #include "private.h" 643 #include "private2.h" // IWYU pragma: export 644 )cpp"; 645 Inputs.ExtraFiles["private.h"] = R"cpp( 646 // IWYU pragma: private, include "public.h" 647 )cpp"; 648 Inputs.ExtraFiles["private2.h"] = R"cpp( 649 // IWYU pragma: private 650 )cpp"; 651 build(); // Fills up PI, file/source manager used is destroyed afterwards. 652 Inputs.MakeAction = nullptr; // Don't populate PI anymore. 653 654 // Now this build gives us a new File&Source Manager. 655 TestAST Processed = build(/*ResetPragmaIncludes=*/false); 656 auto &FM = Processed.fileManager(); 657 auto PrivateFE = FM.getOptionalFileRef("private.h"); 658 assert(PrivateFE); 659 EXPECT_EQ(PI.getPublic(*PrivateFE), "\"public.h\""); 660 661 auto Private2FE = FM.getOptionalFileRef("private2.h"); 662 assert(Private2FE); 663 EXPECT_THAT(PI.getExporters(*Private2FE, FM), 664 testing::ElementsAre(llvm::cantFail(FM.getFileRef("public.h")))); 665 } 666 667 TEST_F(PragmaIncludeTest, CanRecordManyTimes) { 668 Inputs.Code = R"cpp( 669 #include "public.h" 670 )cpp"; 671 Inputs.ExtraFiles["public.h"] = R"cpp( 672 #include "private.h" 673 )cpp"; 674 Inputs.ExtraFiles["private.h"] = R"cpp( 675 // IWYU pragma: private, include "public.h" 676 )cpp"; 677 678 TestAST Processed = build(); 679 auto &FM = Processed.fileManager(); 680 auto PrivateFE = FM.getOptionalFileRef("private.h"); 681 llvm::StringRef Public = PI.getPublic(*PrivateFE); 682 EXPECT_EQ(Public, "\"public.h\""); 683 684 // This build populates same PI during build, but this time we don't have 685 // any IWYU pragmas. Make sure strings from previous recordings are still 686 // alive. 687 Inputs.Code = ""; 688 build(/*ResetPragmaIncludes=*/false); 689 EXPECT_EQ(Public, "\"public.h\""); 690 } 691 } // namespace 692 } // namespace clang::include_cleaner 693