hokein created this revision.
hokein added reviewers: kadircet, sammccall.
Herald added a project: All.
hokein requested review of this revision.
Herald added a project: clang-tools-extra.

- add support to PragmaIncludes to handle IWYU export/begin_exports/end_exports 
pragma;
- implement an API to retrieve the direct exporter headers;


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D137319

Files:
  clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h
  clang-tools-extra/include-cleaner/lib/Record.cpp
  clang-tools-extra/include-cleaner/unittests/RecordTest.cpp

Index: clang-tools-extra/include-cleaner/unittests/RecordTest.cpp
===================================================================
--- clang-tools-extra/include-cleaner/unittests/RecordTest.cpp
+++ clang-tools-extra/include-cleaner/unittests/RecordTest.cpp
@@ -30,6 +30,13 @@
   return false;
 }
 
+MATCHER_P(FileNamed, N, "") {
+  if (arg->tryGetRealPathName() == N)
+    return true;
+  *result_listener << arg->tryGetRealPathName().str();
+  return false;
+}
+
 class RecordASTTest : public ::testing::Test {
 protected:
   TestInputs Inputs;
@@ -117,13 +124,31 @@
   Inputs.Code = R"cpp(// Line 1
     #include "keep1.h" // IWYU pragma: keep
     #include "keep2.h" /* IWYU pragma: keep */
+
+    #include "export1.h" // IWYU pragma: export // line 5
+    // IWYU pragma: begin_exports
+    #include "export2.h" // Line 7
+    #include "export3.h"
+    // IWYU pragma: end_exports
+
+    #include "normal.h" // Line 11
   )cpp";
-  Inputs.ExtraFiles["keep1.h"] = Inputs.ExtraFiles["keep2.h"] = "";
+  Inputs.ExtraFiles["keep1.h"] = Inputs.ExtraFiles["keep2.h"] =
+      Inputs.ExtraFiles["export1.h"] = Inputs.ExtraFiles["export2.h"] =
+          Inputs.ExtraFiles["export3.h"] = Inputs.ExtraFiles["normal.h"] = "";
 
   TestAST Processed = build();
   EXPECT_FALSE(PI.shouldKeep(1));
+  // Keep
   EXPECT_TRUE(PI.shouldKeep(2));
   EXPECT_TRUE(PI.shouldKeep(3));
+
+  // Exports
+  EXPECT_TRUE(PI.shouldKeep(5));
+  EXPECT_TRUE(PI.shouldKeep(7));
+  EXPECT_TRUE(PI.shouldKeep(8));
+
+  EXPECT_FALSE(PI.shouldKeep(11));
 }
 
 TEST_F(PragmaIncludeTest, IWYUPrivate) {
@@ -144,5 +169,69 @@
   EXPECT_EQ(PI.getPublic(PublicFE.get()), ""); // no mapping.
 }
 
+TEST_F(PragmaIncludeTest, IWYUExport) {
+  Inputs.Code = R"cpp(// Line 1
+    #include "export1.h"
+    #include "export2.h"
+  )cpp";
+  Inputs.ExtraFiles["export1.h"] = R"cpp(
+    #include "private.h" // IWYU pragma: export
+  )cpp";
+  Inputs.ExtraFiles["export2.h"] = R"cpp(
+    #include "export3.h"
+  )cpp";
+  Inputs.ExtraFiles["export3.h"] = R"cpp(
+    #include "private.h" // IWYU pragma: export
+  )cpp";
+  Inputs.ExtraFiles["private.h"] = "";
+  TestAST Processed = build();
+  auto &FM = Processed.fileManager();
+
+  EXPECT_THAT(PI.getExporters(FM.getFile("private.h").get(), FM),
+              testing::UnorderedElementsAre(FileNamed("export1.h"),
+                                            FileNamed("export3.h")));
+  EXPECT_TRUE(PI.getExporters(FM.getFile("export3.h").get(), FM).empty());
+}
+
+TEST_F(PragmaIncludeTest, IWYUExportBlock) {
+  Inputs.Code = R"cpp(// Line 1
+   #include "normal.h"
+  )cpp";
+  Inputs.ExtraFiles["normal.h"] = R"cpp(
+    #include "foo.h"
+
+    // IWYU pragma: begin_exports
+    #include "export1.h"
+    #include "private1.h"
+    // IWYU pragma: end_exports
+  )cpp";
+  Inputs.ExtraFiles["export1.h"] = R"cpp(
+    // IWYU pragma: begin_exports
+    #include "private1.h"
+    #include "private2.h"
+    // IWYU pragma: end_exports
+
+    #include "bar.h"
+    #include "private3.h" // IWYU pragma: export
+  )cpp";
+
+  Inputs.ExtraFiles["private1.h"] = Inputs.ExtraFiles["private2.h"] =
+      Inputs.ExtraFiles["private3.h"] = "";
+  Inputs.ExtraFiles["foo.h"] = Inputs.ExtraFiles["bar.h"] = "";
+  TestAST Processed = build();
+  auto &FM = Processed.fileManager();
+
+  EXPECT_THAT(PI.getExporters(FM.getFile("private1.h").get(), FM),
+              testing::UnorderedElementsAre(FileNamed("export1.h"),
+                                            FileNamed("normal.h")));
+  EXPECT_THAT(PI.getExporters(FM.getFile("private2.h").get(), FM),
+              testing::UnorderedElementsAre(FileNamed("export1.h")));
+  EXPECT_THAT(PI.getExporters(FM.getFile("private3.h").get(), FM),
+              testing::UnorderedElementsAre(FileNamed("export1.h")));
+
+  EXPECT_TRUE(PI.getExporters(FM.getFile("foo.h").get(), FM).empty());
+  EXPECT_TRUE(PI.getExporters(FM.getFile("bar.h").get(), FM).empty());
+}
+
 } // namespace
 } // namespace clang::include_cleaner
Index: clang-tools-extra/include-cleaner/lib/Record.cpp
===================================================================
--- clang-tools-extra/include-cleaner/lib/Record.cpp
+++ clang-tools-extra/include-cleaner/lib/Record.cpp
@@ -43,6 +43,12 @@
     InMainFile = SM.isWrittenInMainFile(Loc);
   }
 
+  void EndOfMainFile() override {
+    const auto *MainFile = SM.getFileEntryForID(SM.getMainFileID());
+    Out->RealPathNamesByUID.try_emplace(MainFile->getUniqueID(),
+                                        MainFile->tryGetRealPathName());
+  }
+
   void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
                           llvm::StringRef FileName, bool IsAngled,
                           CharSourceRange /*FilenameRange*/,
@@ -51,6 +57,22 @@
                           llvm::StringRef /*RelativePath*/,
                           const clang::Module * /*Imported*/,
                           SrcMgr::CharacteristicKind FileKind) override {
+    Out->RealPathNamesByUID.try_emplace(
+        File->getUniqueID(), File->getFileEntry().tryGetRealPathName());
+
+    if (!ExportStack.empty() &&
+        ExportStack.back().Exporter == SM.getFileID(HashLoc)) {
+      auto Top = ExportStack.back();
+      Out->IWYUExportBy[File->getFileEntry().getUniqueID()].insert(
+          SM.getFileEntryForID(Top.Exporter)->getUniqueID());
+      // main-file #include with export pragma should never be removed.
+      if (Top.Exporter == SM.getMainFileID())
+        Out->ShouldKeep.insert(
+            SM.getLineNumber(SM.getMainFileID(), SM.getFileOffset(HashLoc)));
+      if (!Top.Block) // Pop immediately for sing-line export pragma.
+        ExportStack.pop_back();
+    }
+
     if (!InMainFile)
       return;
     int HashLine =
@@ -76,6 +98,20 @@
       return false;
     }
 
+    // Handle export pragma.
+    if (Pragma->startswith("export")) {
+      ExportStack.push_back({SM.getFileID(Range.getBegin()), false});
+    } else if (Pragma->startswith("begin_exports")) {
+      ExportStack.push_back({SM.getFileID(Range.getBegin()), true});
+    } else if (Pragma->startswith("end_exports")) {
+      // FIXME: be robust on unmatching cases. We should only pop the stack if
+      // the begin_exports and end_exports is in the same file.
+      if (!ExportStack.empty()) {
+        assert(ExportStack.back().Block);
+        ExportStack.pop_back();
+      }
+    }
+
     if (InMainFile) {
       if (!Pragma->startswith("keep"))
         return false;
@@ -105,6 +141,15 @@
   PragmaIncludes *Out;
   // Track the last line "IWYU pragma: keep" was seen in the main file, 1-based.
   int LastPragmaKeepInMainFileLine = -1;
+  struct State {
+    // The file where we saw the export pragma.
+    FileID Exporter;
+    // true if it is a block begin/end_exports pragma; false if it is a
+    // single-line export pragma
+    bool Block = false;
+  };
+  // A stack for tracking all open begin_exports or single-line export.
+  std::vector<State> ExportStack;
 };
 
 void PragmaIncludes::record(const CompilerInstance &CI) {
@@ -120,6 +165,24 @@
   return It->getSecond();
 }
 
+llvm::SmallVector<const FileEntry *>
+PragmaIncludes::getExporters(const FileEntry *File, FileManager &FM) const {
+  auto It = IWYUExportBy.find(File->getUniqueID());
+  if (It == IWYUExportBy.end())
+    return {};
+
+  llvm::SmallVector<const FileEntry *> Results;
+  for (auto Export : It->getSecond()) {
+    if (auto RealExport = RealPathNamesByUID.find(Export);
+        RealExport != RealPathNamesByUID.end()) {
+      auto FE = FM.getFileRef(RealExport->getSecond());
+      assert(FE);
+      Results.push_back(FE.get());
+    }
+  }
+  return Results;
+}
+
 std::unique_ptr<ASTConsumer> RecordedAST::record() {
   class Recorder : public ASTConsumer {
     RecordedAST *Out;
Index: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h
===================================================================
--- clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h
+++ clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h
@@ -19,6 +19,7 @@
 
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/FileSystem/UniqueID.h"
 #include <memory>
 #include <vector>
@@ -29,6 +30,7 @@
 class CompilerInstance;
 class Decl;
 class FileEntry;
+class FileManager;
 
 namespace include_cleaner {
 
@@ -55,6 +57,11 @@
   /// Returns "" if there is none.
   llvm::StringRef getPublic(const FileEntry *File) const;
 
+  /// Returns all direct exporter headers for the given header file.
+  /// Returns empty if there is none.
+  llvm::SmallVector<const FileEntry *> getExporters(const FileEntry *File,
+                                                    FileManager &FM) const;
+
 private:
   class RecordPragma;
   /// 1-based Line numbers for the #include directives of the main file that
@@ -70,7 +77,15 @@
   llvm::DenseMap<llvm::sys::fs::UniqueID, std::string /*VerbatimSpelling*/>
       IWYUPublic;
 
-  // FIXME: add other IWYU supports (export etc)
+  /// A reverse map from the underlying header to its exporter headers.
+  llvm::DenseMap<llvm::sys::fs::UniqueID,
+                 llvm::DenseSet<llvm::sys::fs::UniqueID>>
+      IWYUExportBy;
+  /// A mapping from FileEntry to RealPathNames for all opened files during the
+  /// parsing.
+  llvm::DenseMap<llvm::sys::fs::UniqueID, std::string /*RealPath*/>
+      RealPathNamesByUID;
+
   // FIXME: add support for clang use_instead pragma
   // FIXME: add selfcontained file.
 };
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to