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