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.

This patch implements the initial version of "Location => Header" step:

- define the interface;
- integrate into the existing workflow, and use the PragmaIncludes;


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D137320

Files:
  clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
  clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h
  clang-tools-extra/include-cleaner/lib/Analysis.cpp
  clang-tools-extra/include-cleaner/lib/AnalysisInternal.h
  clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp

Index: clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
===================================================================
--- clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
+++ clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
@@ -7,11 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang-include-cleaner/Analysis.h"
+#include "clang-include-cleaner/Record.h"
 #include "clang-include-cleaner/Types.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/Basic/FileManager.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/FrontendActions.h"
 #include "clang/Testing/TestAST.h"
 #include "clang/Tooling/Inclusions/StandardLibrary.h"
 #include "llvm/ADT/ArrayRef.h"
@@ -26,41 +28,67 @@
 using testing::Pair;
 using testing::UnorderedElementsAre;
 
-TEST(WalkUsed, Basic) {
-  // FIXME: Have a fixture for setting up tests.
-  llvm::Annotations HeaderCode(R"cpp(
-  void foo();
-  namespace std { class vector {}; })cpp");
+class WalkUsedTest : public ::testing::Test {
+protected:
+  TestInputs Inputs;
+  PragmaIncludes PI;
+
+  WalkUsedTest() {
+    Inputs.MakeAction = [this] {
+      struct Hook : public SyntaxOnlyAction {
+      public:
+        Hook(PragmaIncludes *Out) : Out(Out) {}
+        bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
+          Out->record(CI);
+          return true;
+        }
+
+        PragmaIncludes *Out;
+      };
+      return std::make_unique<Hook>(&PI);
+    };
+  }
+
+  llvm::DenseMap</*offset*/ size_t, std::vector<Header>> walk(TestAST &AST) {
+    llvm::SmallVector<Decl *> TopLevelDecls;
+    for (Decl *D : AST.context().getTranslationUnitDecl()->decls())
+      TopLevelDecls.emplace_back(D);
+    auto &SM = AST.sourceManager();
+
+    llvm::DenseMap<size_t, std::vector<Header>> OffsetToProviders;
+    walkUsed(
+        TopLevelDecls, PI,
+        [&](SourceLocation RefLoc, Symbol S, llvm::ArrayRef<Header> Providers) {
+          auto [FID, Offset] = SM.getDecomposedLoc(RefLoc);
+          EXPECT_EQ(FID, SM.getMainFileID());
+          OffsetToProviders.try_emplace(Offset, Providers.vec());
+        });
+    return OffsetToProviders;
+  }
+};
+
+TEST_F(WalkUsedTest, Basic) {
   llvm::Annotations Code(R"cpp(
   void $bar^bar() {
     $foo^foo();
     std::$vector^vector $vconstructor^v;
   }
   )cpp");
-  TestInputs Inputs(Code.code());
-  Inputs.ExtraFiles["header.h"] = HeaderCode.code().str();
+  Inputs.Code = Code.code();
+  Inputs.ExtraFiles["header.h"] = R"cpp(
+  void foo();
+  namespace std { class vector {}; }
+  )cpp";
   Inputs.ExtraArgs.push_back("-include");
   Inputs.ExtraArgs.push_back("header.h");
   TestAST AST(Inputs);
 
-  llvm::SmallVector<Decl *> TopLevelDecls;
-  for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {
-    TopLevelDecls.emplace_back(D);
-  }
-
-  auto &SM = AST.sourceManager();
-  llvm::DenseMap<size_t, std::vector<Header>> OffsetToProviders;
-  walkUsed(TopLevelDecls, [&](SourceLocation RefLoc, Symbol S,
-                              llvm::ArrayRef<Header> Providers) {
-    auto [FID, Offset] = SM.getDecomposedLoc(RefLoc);
-    EXPECT_EQ(FID, SM.getMainFileID());
-    OffsetToProviders.try_emplace(Offset, Providers.vec());
-  });
   auto HeaderFile = Header(AST.fileManager().getFile("header.h").get());
-  auto MainFile = Header(SM.getFileEntryForID(SM.getMainFileID()));
+  auto MainFile = Header(AST.sourceManager().getFileEntryForID(
+      AST.sourceManager().getMainFileID()));
   auto VectorSTL = Header(tooling::stdlib::Header::named("<vector>").value());
   EXPECT_THAT(
-      OffsetToProviders,
+      walk(AST),
       UnorderedElementsAre(
           Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
           Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)),
@@ -68,5 +96,56 @@
           Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL))));
 }
 
+TEST_F(WalkUsedTest, IWYUPrivate) {
+  llvm::Annotations Code(R"cpp(
+  #include "private.h"
+  void $bar^bar($private^Private) {
+  }
+  )cpp");
+  Inputs.Code = Code.code();
+  Inputs.ExtraFiles["private.h"] = R"cpp(
+    // IWYU pragma: private, include "path/public.h"
+    class Private {};
+  )cpp";
+  TestAST AST(Inputs);
+
+  auto PublicH = Header("\"path/public.h\"");
+  auto MainFile = Header(AST.sourceManager().getFileEntryForID(
+      AST.sourceManager().getMainFileID()));
+
+  EXPECT_THAT(walk(AST),
+              UnorderedElementsAre(
+                  Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
+                  Pair(Code.point("private"), UnorderedElementsAre(PublicH))));
+}
+
+TEST_F(WalkUsedTest, IWYUExport) {
+  llvm::Annotations Code(R"cpp(
+  #include "header1.h"
+  void $bar^bar($private^Private) {
+  }
+  )cpp");
+  Inputs.Code = Code.code();
+  Inputs.ExtraFiles["header1.h"] = R"cpp(
+    #include "header2.h"
+  )cpp";
+  Inputs.ExtraFiles["header2.h"] = R"cpp(
+    #include "private.h" // IWYU pragma: export
+  )cpp";
+  Inputs.ExtraFiles["private.h"] = R"cpp(class Private {};)cpp";
+  TestAST AST(Inputs);
+
+  auto Header2File = Header(AST.fileManager().getFile("header2.h").get());
+  auto PrivateFile = Header(AST.fileManager().getFile("private.h").get());
+  auto MainFile = Header(AST.sourceManager().getFileEntryForID(
+      AST.sourceManager().getMainFileID()));
+
+  EXPECT_THAT(walk(AST),
+              UnorderedElementsAre(
+                  Pair(Code.point("bar"), UnorderedElementsAre(MainFile)),
+                  Pair(Code.point("private"),
+                       UnorderedElementsAre(PrivateFile, Header2File))));
+}
+
 } // namespace
 } // namespace clang::include_cleaner
Index: clang-tools-extra/include-cleaner/lib/AnalysisInternal.h
===================================================================
--- clang-tools-extra/include-cleaner/lib/AnalysisInternal.h
+++ clang-tools-extra/include-cleaner/lib/AnalysisInternal.h
@@ -21,6 +21,8 @@
 #ifndef CLANG_INCLUDE_CLEANER_ANALYSISINTERNAL_H
 #define CLANG_INCLUDE_CLEANER_ANALYSISINTERNAL_H
 
+#include "clang-include-cleaner/Record.h"
+#include "clang-include-cleaner/Types.h"
 #include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 
@@ -42,6 +44,12 @@
 /// being analyzed, in order to find all references within it.
 void walkAST(Decl &Root, llvm::function_ref<void(SourceLocation, NamedDecl &)>);
 
+/// Finds the headers that provide the symbol location.
+// FIXME: expose signals
+llvm::SmallVector<Header> findIncludeHeaders(const SymbolLocation &Loc,
+                                             const SourceManager &SM,
+                                             const PragmaIncludes &PI);
+
 /// Write an HTML summary of the analysis to the given stream.
 /// FIXME: Once analysis has a public API, this should be public too.
 void writeHTMLReport(FileID File, llvm::ArrayRef<Decl *> Roots, ASTContext &Ctx,
Index: clang-tools-extra/include-cleaner/lib/Analysis.cpp
===================================================================
--- clang-tools-extra/include-cleaner/lib/Analysis.cpp
+++ clang-tools-extra/include-cleaner/lib/Analysis.cpp
@@ -7,28 +7,19 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang-include-cleaner/Analysis.h"
-#include "clang-include-cleaner/Types.h"
 #include "AnalysisInternal.h"
+#include "clang-include-cleaner/Record.h"
+#include "clang-include-cleaner/Types.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Tooling/Inclusions/StandardLibrary.h"
 #include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 
 namespace clang::include_cleaner {
-namespace {
-llvm::SmallVector<Header>
-toHeader(llvm::ArrayRef<tooling::stdlib::Header> Headers) {
-  llvm::SmallVector<Header> Result;
-  llvm::for_each(Headers, [&](tooling::stdlib::Header H) {
-    Result.emplace_back(Header(H));
-  });
-  return Result;
-}
 
-} // namespace
-void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, UsedSymbolCB CB) {
+void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, const PragmaIncludes &PI,
+              UsedSymbolCB CB) {
   tooling::stdlib::Recognizer Recognizer;
   for (auto *Root : ASTRoots) {
     auto &SM = Root->getASTContext().getSourceManager();
@@ -36,16 +27,45 @@
       if (auto SS = Recognizer(&ND)) {
         // FIXME: Also report forward decls from main-file, so that the caller
         // can decide to insert/ignore a header.
-        return CB(Loc, Symbol(*SS), toHeader(SS->headers()));
+        return CB(Loc, Symbol(*SS), findIncludeHeaders({*SS}, SM, PI));
       }
       // FIXME: Extract locations from redecls.
-      // FIXME: Handle IWYU pragmas, non self-contained files.
       // FIXME: Handle macro locations.
-      if (auto *FE = SM.getFileEntryForID(SM.getFileID(ND.getLocation())))
-        return CB(Loc, Symbol(ND), {Header(FE)});
+      return CB(Loc, Symbol(ND),
+                findIncludeHeaders({ND.getLocation()}, SM, PI));
     });
   }
   // FIXME: Handle references of macros.
 }
 
+llvm::SmallVector<Header> findIncludeHeaders(const SymbolLocation &SLoc,
+                                             const SourceManager &SM,
+                                             const PragmaIncludes &PI) {
+  llvm::SmallVector<Header> Results;
+  if (SLoc.index() == 0) {
+    // FIXME: Handle non self-contained files.
+    SourceLocation Loc = std::get<SourceLocation>(SLoc);
+    FileID FID = SM.getFileID(Loc);
+    const auto *FE = SM.getFileEntryForID(FID);
+    if (!FE)
+      return {};
+
+    llvm::StringRef VerbatimSpelling = PI.getPublic(FE);
+    if (!VerbatimSpelling.empty())
+      return {{VerbatimSpelling}};
+
+    Results = {{FE}};
+    // FIXME: compute a closure of exporter headers.
+    for (const auto *Export : PI.getExporters(FE, SM.getFileManager()))
+      Results.push_back(Export);
+    return Results;
+  }
+  if (SLoc.index() == 1) {
+    for (const auto &H : std::get<tooling::stdlib::Symbol>(SLoc).headers())
+      Results.push_back(H);
+    return Results;
+  }
+  return Results;
+}
+
 } // namespace clang::include_cleaner
Index: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h
===================================================================
--- clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h
+++ clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h
@@ -22,7 +22,10 @@
 #ifndef CLANG_INCLUDE_CLEANER_TYPES_H
 #define CLANG_INCLUDE_CLEANER_TYPES_H
 
+#include "clang/Basic/FileEntry.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Tooling/Inclusions/StandardLibrary.h"
+#include "llvm/ADT/StringRef.h"
 #include <memory>
 #include <vector>
 
@@ -48,14 +51,17 @@
   Header(const FileEntry *FE) : Storage(FE) {}
   /// A logical file representing a stdlib header.
   Header(tooling::stdlib::Header H) : Storage(H) {}
+  Header(llvm::StringRef VerbatimSpelling) : Storage(VerbatimSpelling.str()) {}
 
   bool operator==(const Header &RHS) const { return Storage == RHS.Storage; }
 
 private:
-  // FIXME: Handle verbatim spellings.
-  std::variant<const FileEntry *, tooling::stdlib::Header> Storage;
+  std::variant<const FileEntry *, tooling::stdlib::Header, std::string> Storage;
 };
 
+// FIXME: use a real Class!
+using SymbolLocation = std::variant<SourceLocation, tooling::stdlib::Symbol>;
+
 } // namespace include_cleaner
 } // namespace clang
 
Index: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
===================================================================
--- clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
+++ clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
@@ -11,6 +11,7 @@
 #ifndef CLANG_INCLUDE_CLEANER_ANALYSIS_H
 #define CLANG_INCLUDE_CLEANER_ANALYSIS_H
 
+#include "clang-include-cleaner/Record.h"
 #include "clang-include-cleaner/Types.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
@@ -42,9 +43,8 @@
 ///    headers which don't match any #include in the main file
 ///  - to diagnose unused includes: an #include in the main file does not match
 ///    the headers for any referenced symbol
-/// FIXME: Take in an include structure to improve location to header mappings
-/// (e.g. IWYU pragmas).
-void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, UsedSymbolCB CB);
+void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, const PragmaIncludes &PI,
+              UsedSymbolCB CB);
 
 } // namespace include_cleaner
 } // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to