brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.9 KiB · cbf7bae Raw
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