[clang-tools-extra] 7f6e005 - [clang-tidy] Add misc-header-include-cycle check

Piotr Zegar via cfe-commits cfe-commits at lists.llvm.org
Sat Jun 24 04:01:49 PDT 2023


Author: Piotr Zegar
Date: 2023-06-24T11:00:13Z
New Revision: 7f6e0052a97f13a5f595f3fd0c135c2c4db119d4

URL: https://github.com/llvm/llvm-project/commit/7f6e0052a97f13a5f595f3fd0c135c2c4db119d4
DIFF: https://github.com/llvm/llvm-project/commit/7f6e0052a97f13a5f595f3fd0c135c2c4db119d4.diff

LOG: [clang-tidy] Add misc-header-include-cycle check

Check detects cyclic #include dependencies between user-defined headers.

Reviewed By: njames93

Differential Revision: https://reviews.llvm.org/D144828

Added: 
    clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp
    clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h
    clang-tools-extra/docs/clang-tidy/checks/misc/header-include-cycle.rst
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first-d.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth-d.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second-d.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-d.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-e.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-i.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-n.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-o.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third-d.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.first-s.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.fourth-s.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.second-s.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.self-s.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.third-s.hpp
    clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp

Modified: 
    clang-tools-extra/clang-tidy/misc/CMakeLists.txt
    clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
    clang-tools-extra/docs/ReleaseNotes.rst
    clang-tools-extra/docs/clang-tidy/checks/list.rst

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index a37f7b29ec999..348f3fa6402ec 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -20,6 +20,7 @@ add_clang_library(clangTidyMiscModule
   ConstCorrectnessCheck.cpp
   DefinitionsInHeadersCheck.cpp
   ConfusableIdentifierCheck.cpp
+  HeaderIncludeCycleCheck.cpp
   IncludeCleanerCheck.cpp
   MiscTidyModule.cpp
   MisleadingBidirectional.cpp

diff  --git a/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp b/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp
new file mode 100644
index 0000000000000..bebd6e390ed53
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp
@@ -0,0 +1,180 @@
+//===--- HeaderIncludeCycleCheck.cpp - clang-tidy -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "HeaderIncludeCycleCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/PPCallbacks.h"
+#include "clang/Lex/Preprocessor.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Regex.h"
+#include <algorithm>
+#include <deque>
+#include <optional>
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+namespace {
+
+struct Include {
+  FileID Id;
+  llvm::StringRef Name;
+  SourceLocation Loc;
+};
+
+class CyclicDependencyCallbacks : public PPCallbacks {
+public:
+  CyclicDependencyCallbacks(HeaderIncludeCycleCheck &Check,
+                            const SourceManager &SM,
+                            const std::vector<StringRef> &IgnoredFilesList)
+      : Check(Check), SM(SM) {
+    IgnoredFilesRegexes.reserve(IgnoredFilesList.size());
+    for (const StringRef &It : IgnoredFilesList) {
+      if (!It.empty())
+        IgnoredFilesRegexes.emplace_back(It);
+    }
+  }
+
+  void FileChanged(SourceLocation Loc, FileChangeReason Reason,
+                   SrcMgr::CharacteristicKind FileType,
+                   FileID PrevFID) override {
+    if (FileType != clang::SrcMgr::C_User)
+      return;
+
+    if (Reason != EnterFile && Reason != ExitFile)
+      return;
+
+    FileID Id = SM.getFileID(Loc);
+    if (Id.isInvalid())
+      return;
+
+    if (Reason == ExitFile) {
+      if ((Files.size() > 1U) && (Files.back().Id == PrevFID) &&
+          (Files[Files.size() - 2U].Id == Id))
+        Files.pop_back();
+      return;
+    }
+
+    if (!Files.empty() && Files.back().Id == Id)
+      return;
+
+    std::optional<llvm::StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
+    llvm::StringRef FileName =
+        FilePath ? llvm::sys::path::filename(*FilePath) : llvm::StringRef();
+
+    if (!NextToEnter)
+      NextToEnter = Include{Id, FileName, SourceLocation()};
+
+    assert(NextToEnter->Name == FileName);
+    NextToEnter->Id = Id;
+    Files.emplace_back(*NextToEnter);
+    NextToEnter.reset();
+  }
+
+  void InclusionDirective(SourceLocation, const Token &, StringRef FilePath,
+                          bool, CharSourceRange Range,
+                          OptionalFileEntryRef File, StringRef, StringRef,
+                          const Module *,
+                          SrcMgr::CharacteristicKind FileType) override {
+    if (FileType != clang::SrcMgr::C_User)
+      return;
+
+    llvm::StringRef FileName = llvm::sys::path::filename(FilePath);
+    NextToEnter = {FileID(), FileName, Range.getBegin()};
+
+    if (!File)
+      return;
+
+    FileID Id = SM.translateFile(*File);
+    if (Id.isInvalid())
+      return;
+
+    checkForDoubleInclude(Id, FileName, Range.getBegin());
+  }
+
+  void EndOfMainFile() override {
+    if (!Files.empty() && Files.back().Id == SM.getMainFileID())
+      Files.pop_back();
+
+    assert(Files.empty());
+  }
+
+  void checkForDoubleInclude(FileID Id, llvm::StringRef FileName,
+                             SourceLocation Loc) {
+    auto It =
+        std::find_if(Files.rbegin(), Files.rend(),
+                     [&](const Include &Entry) { return Entry.Id == Id; });
+    if (It == Files.rend())
+      return;
+
+    const std::optional<StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
+    if (!FilePath || isFileIgnored(*FilePath))
+      return;
+
+    if (It == Files.rbegin()) {
+      Check.diag(Loc, "direct self-inclusion of header file '%0'") << FileName;
+      return;
+    }
+
+    Check.diag(Loc, "circular header file dependency detected while including "
+                    "'%0', please check the include path")
+        << FileName;
+
+    const bool IsIncludePathValid =
+        std::all_of(Files.rbegin(), It, [](const Include &Elem) {
+          return !Elem.Name.empty() && Elem.Loc.isValid();
+        });
+
+    if (!IsIncludePathValid)
+      return;
+
+    auto CurrentIt = Files.rbegin();
+    do {
+      Check.diag(CurrentIt->Loc, "'%0' included from here", DiagnosticIDs::Note)
+          << CurrentIt->Name;
+    } while (CurrentIt++ != It);
+  }
+
+  bool isFileIgnored(StringRef FileName) const {
+    return llvm::any_of(IgnoredFilesRegexes, [&](const llvm::Regex &It) {
+      return It.match(FileName);
+    });
+  }
+
+private:
+  std::deque<Include> Files;
+  std::optional<Include> NextToEnter;
+  HeaderIncludeCycleCheck &Check;
+  const SourceManager &SM;
+  std::vector<llvm::Regex> IgnoredFilesRegexes;
+};
+
+} // namespace
+
+HeaderIncludeCycleCheck::HeaderIncludeCycleCheck(StringRef Name,
+                                                 ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      IgnoredFilesList(utils::options::parseStringList(
+          Options.get("IgnoredFilesList", ""))) {}
+
+void HeaderIncludeCycleCheck::registerPPCallbacks(
+    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
+  PP->addPPCallbacks(
+      std::make_unique<CyclicDependencyCallbacks>(*this, SM, IgnoredFilesList));
+}
+
+void HeaderIncludeCycleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "IgnoredFilesList",
+                utils::options::serializeStringList(IgnoredFilesList));
+}
+
+} // namespace clang::tidy::misc

diff  --git a/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h b/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h
new file mode 100644
index 0000000000000..9a1a72399f423
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h
@@ -0,0 +1,34 @@
+//===--- HeaderIncludeCycleCheck.h - clang-tidy -----------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERINCLUDECYCLECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERINCLUDECYCLECHECK_H
+
+#include "../ClangTidyCheck.h"
+#include <vector>
+
+namespace clang::tidy::misc {
+
+/// Check detects cyclic #include dependencies between user-defined headers.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/misc/header-include-cycle.html
+class HeaderIncludeCycleCheck : public ClangTidyCheck {
+public:
+  HeaderIncludeCycleCheck(StringRef Name, ClangTidyContext *Context);
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+  const std::vector<StringRef> IgnoredFilesList;
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERINCLUDECYCLECHECK_H

diff  --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index f63c5ab543feb..92590506e1ec1 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -12,6 +12,7 @@
 #include "ConfusableIdentifierCheck.h"
 #include "ConstCorrectnessCheck.h"
 #include "DefinitionsInHeadersCheck.h"
+#include "HeaderIncludeCycleCheck.h"
 #include "IncludeCleanerCheck.h"
 #include "MisleadingBidirectional.h"
 #include "MisleadingIdentifier.h"
@@ -42,6 +43,8 @@ class MiscModule : public ClangTidyModule {
         "misc-const-correctness");
     CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
         "misc-definitions-in-headers");
+    CheckFactories.registerCheck<HeaderIncludeCycleCheck>(
+        "misc-header-include-cycle");
     CheckFactories.registerCheck<IncludeCleanerCheck>("misc-include-cleaner");
     CheckFactories.registerCheck<MisleadingBidirectionalCheck>(
         "misc-misleading-bidirectional");

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index ce66669168711..e049ff87939a6 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -165,6 +165,11 @@ New checks
   Checks that all implicit and explicit inline functions in header files are
   tagged with the ``LIBC_INLINE`` macro.
 
+- New :doc:`misc-header-include-cycle
+  <clang-tidy/checks/misc/header-include-cycle>` check.
+
+  Check detects cyclic ``#include`` dependencies between user-defined headers.
+
 - New :doc:`misc-include-cleaner
   <clang-tidy/checks/misc/include-cleaner>` check.
 

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 919d5362f45f1..552d1c40c25b8 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -255,6 +255,7 @@ Clang-Tidy Checks
    `misc-confusable-identifiers <misc/confusable-identifiers.html>`_,
    `misc-const-correctness <misc/const-correctness.html>`_, "Yes"
    `misc-definitions-in-headers <misc/definitions-in-headers.html>`_, "Yes"
+   `misc-header-include-cycle <misc/header-include-cycle.html>`_,
    `misc-include-cleaner <misc/include-cleaner.html>`_, "Yes"
    `misc-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
    `misc-misleading-identifier <misc/misleading-identifier.html>`_,

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/misc/header-include-cycle.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/header-include-cycle.rst
new file mode 100644
index 0000000000000..622dc784f3c61
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/header-include-cycle.rst
@@ -0,0 +1,71 @@
+.. title:: clang-tidy - misc-header-include-cycle
+
+misc-header-include-cycle
+=========================
+
+Check detects cyclic ``#include`` dependencies between user-defined headers.
+
+.. code-block:: c++
+
+    // Header A.hpp
+    #pragma once
+    #include "B.hpp"
+
+    // Header B.hpp
+    #pragma once
+    #include "C.hpp"
+
+    // Header C.hpp
+    #pragma once
+    #include "A.hpp"
+
+    // Include chain: A->B->C->A
+
+Header files are a crucial part of many C++ programs, as they provide a way to
+organize declarations and definitions that are shared across multiple source
+files. However, header files can also create problems when they become entangled
+in complex dependency cycles. Such cycles can cause issues with compilation
+times, unnecessary rebuilds, and make it harder to understand the overall
+structure of the code.
+
+To address these issues, this check has been developed. This check is designed
+to detect cyclic dependencies between header files, also known as
+"include cycles". An include cycle occurs when a header file `A` includes a
+header file `B`, and header file `B` (or any later included header file in the
+chain) includes back header file `A`, leading to a circular dependency cycle.
+
+This check operates at the preprocessor level and analyzes user-defined headers
+and their dependencies. It focuses specifically on detecting include cycles,
+and ignores other types or function dependencies. This allows it to provide a
+specialized analysis that is focused on identifying and preventing issues
+related to header file organization.
+
+The benefits of using this check are numerous. By detecting include cycles early
+in the development process, developers can identify and resolve these issues
+before they become more 
diff icult and time-consuming to fix. This can lead to
+faster compile times, improved code quality, and a more maintainable codebase
+overall. Additionally, by ensuring that header files are organized in a way that
+avoids cyclic dependencies, developers can make their code easier to understand
+and modify over time.
+
+It's worth noting that this tool only analyzes user-defined headers and their
+dependencies, excluding system includes such as standard library headers and
+third-party library headers. System includes are usually well-designed and free
+of include cycles, and ignoring them helps to focus on potential issues within
+the project's own codebase. This limitation doesn't diminish the tool's ability
+to detect ``#include`` cycles within the analyzed code. As with any tool,
+developers should use their judgment when evaluating the warnings produced by
+the check and be prepared to make exceptions or modifications to their code as
+needed.
+
+Options
+-------
+
+.. option:: IgnoredFilesList
+
+    Provides a way to exclude specific files/headers from the warnings raised by
+    a check. This can be achieved by specifying a semicolon-separated list of
+    regular expressions or filenames. This option can be used as an alternative
+    to ``//NOLINT`` when using it is not possible.
+    The default value of this option is an empty string, indicating that no
+    files are ignored by default.

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first-d.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first-d.hpp
new file mode 100644
index 0000000000000..6615b451c7fe7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first-d.hpp
@@ -0,0 +1,4 @@
+#ifndef FIRST
+#define FIRST
+#include "header-include-cycle.second-d.hpp"
+#endif

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first.hpp
new file mode 100644
index 0000000000000..a118927647db6
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.second.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth-d.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth-d.hpp
new file mode 100644
index 0000000000000..68e3774a6a226
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth-d.hpp
@@ -0,0 +1,4 @@
+#ifndef FOURTH
+#define FOURTH
+#include "header-include-cycle.first-d.hpp"
+#endif

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth.hpp
new file mode 100644
index 0000000000000..df6ec5c589206
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.first.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second-d.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second-d.hpp
new file mode 100644
index 0000000000000..80202da856180
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second-d.hpp
@@ -0,0 +1,4 @@
+#ifndef SECOND
+#define SECOND
+#include "header-include-cycle.third-d.hpp"
+#endif

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second.hpp
new file mode 100644
index 0000000000000..0e643fef22399
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.third.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-d.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-d.hpp
new file mode 100644
index 0000000000000..16a2bebe96e6a
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-d.hpp
@@ -0,0 +1,4 @@
+#ifndef SELF
+#define SELF
+#include "header-include-cycle.self-d.hpp"
+#endif

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-e.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-e.hpp
new file mode 100644
index 0000000000000..bedbace457d1e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-e.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.self-e.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-i.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-i.hpp
new file mode 100644
index 0000000000000..4dabd71e02531
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-i.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.self-i.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-n.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-n.hpp
new file mode 100644
index 0000000000000..3c163991bb070
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-n.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.self-n.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-o.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-o.hpp
new file mode 100644
index 0000000000000..3c163991bb070
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-o.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.self-n.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self.hpp
new file mode 100644
index 0000000000000..8bf2142b5518c
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.self.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third-d.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third-d.hpp
new file mode 100644
index 0000000000000..e9f288a9bacef
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third-d.hpp
@@ -0,0 +1,4 @@
+#ifndef THIRD
+#define THIRD
+#include "header-include-cycle.fourth-d.hpp"
+#endif

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third.hpp
new file mode 100644
index 0000000000000..b6130028f962e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.fourth.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.first-s.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.first-s.hpp
new file mode 100644
index 0000000000000..400056892a769
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.first-s.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.second-s.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.fourth-s.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.fourth-s.hpp
new file mode 100644
index 0000000000000..fdcf5f11d4f73
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.fourth-s.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.first-s.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.second-s.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.second-s.hpp
new file mode 100644
index 0000000000000..614552605c860
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.second-s.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.third-s.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.self-s.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.self-s.hpp
new file mode 100644
index 0000000000000..dbaf65065d24f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.self-s.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.self-s.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.third-s.hpp b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.third-s.hpp
new file mode 100644
index 0000000000000..66fb02af6dab6
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/system/header-include-cycle.third-s.hpp
@@ -0,0 +1,2 @@
+#pragma once
+#include "header-include-cycle.fourth-s.hpp"

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp
new file mode 100644
index 0000000000000..cdc15f8149d38
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp
@@ -0,0 +1,59 @@
+// RUN: rm -rf %T/misc-header-include-cycle-headers
+// RUN: mkdir %T/misc-header-include-cycle-headers
+// RUN: cp -r %S/Inputs/header-include-cycle* %T/misc-header-include-cycle-headers/
+// RUN: mkdir %T/misc-header-include-cycle-headers/system
+// RUN: cp -r %S/Inputs/system/header-include-cycle* %T/misc-header-include-cycle-headers/system
+// RUN: cp %s %T/header-include-cycle.cpp
+// RUN: clang-tidy %T/header-include-cycle.cpp -checks='-*,misc-header-include-cycle' -header-filter=.* \
+// RUN: -config="{CheckOptions: [{key: misc-header-include-cycle.IgnoredFilesList, value: 'header-include-cycle.self-e.hpp'}]}" \
+// RUN: -- -I%T/misc-header-include-cycle-headers -isystem %T/misc-header-include-cycle-headers/system \
+// RUN: --include %T/misc-header-include-cycle-headers/header-include-cycle.self-i.hpp | FileCheck %s \
+// RUN: -check-prefix=CHECK-MESSAGES "-implicit-check-not={{note|warning|error}}:" --dump-input=fail
+// RUN: rm -rf %T/misc-header-include-cycle-headers
+
+#ifndef MAIN_GUARD
+#define MAIN_GUARD
+
+#include "header-include-cycle.cpp"
+// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: direct self-inclusion of header file 'header-include-cycle.cpp' [misc-header-include-cycle]
+
+#include <header-include-cycle.first-d.hpp>
+// CHECK-MESSAGES: header-include-cycle.fourth-d.hpp:3:10: warning: circular header file dependency detected while including 'header-include-cycle.first-d.hpp', please check the include path [misc-header-include-cycle]
+// CHECK-MESSAGES: header-include-cycle.third-d.hpp:3:10: note: 'header-include-cycle.fourth-d.hpp' included from here
+// CHECK-MESSAGES: header-include-cycle.second-d.hpp:3:10: note: 'header-include-cycle.third-d.hpp' included from here
+// CHECK-MESSAGES: header-include-cycle.first-d.hpp:3:10: note: 'header-include-cycle.second-d.hpp' included from here
+// CHECK-MESSAGES: :[[@LINE-5]]:10: note: 'header-include-cycle.first-d.hpp' included from here
+
+#include <header-include-cycle.first.hpp>
+// CHECK-MESSAGES: header-include-cycle.fourth.hpp:2:10: warning: circular header file dependency detected while including 'header-include-cycle.first.hpp', please check the include path [misc-header-include-cycle]
+// CHECK-MESSAGES: header-include-cycle.third.hpp:2:10: note: 'header-include-cycle.fourth.hpp' included from here
+// CHECK-MESSAGES: header-include-cycle.second.hpp:2:10: note: 'header-include-cycle.third.hpp' included from here
+// CHECK-MESSAGES: header-include-cycle.first.hpp:2:10: note: 'header-include-cycle.second.hpp' included from here
+// CHECK-MESSAGES: :[[@LINE-5]]:10: note: 'header-include-cycle.first.hpp' included from here
+
+#include <header-include-cycle.self-d.hpp>
+// CHECK-MESSAGES: header-include-cycle.self-d.hpp:3:10: warning: direct self-inclusion of header file 'header-include-cycle.self-d.hpp' [misc-header-include-cycle]
+
+// CHECK-MESSAGES: header-include-cycle.self-i.hpp:2:10: warning: direct self-inclusion of header file 'header-include-cycle.self-i.hpp' [misc-header-include-cycle]
+
+#include <header-include-cycle.self-o.hpp>
+// CHECK-MESSAGES: header-include-cycle.self-n.hpp:2:10: warning: direct self-inclusion of header file 'header-include-cycle.self-n.hpp' [misc-header-include-cycle]
+
+#include <header-include-cycle.self.hpp>
+// CHECK-MESSAGES: header-include-cycle.self.hpp:2:10: warning: direct self-inclusion of header file 'header-include-cycle.self.hpp' [misc-header-include-cycle]
+
+// Should not warn about second include of guarded headers:
+#include <header-include-cycle.first.hpp>
+#include <header-include-cycle.first-d.hpp>
+#include <header-include-cycle.self.hpp>
+#include <header-include-cycle.self-d.hpp>
+#include <header-include-cycle.self-o.hpp>
+#include <header-include-cycle.self-n.hpp>
+
+// Should not warn about system includes
+#include <header-include-cycle.first-s.hpp>
+#include <header-include-cycle.self-s.hpp>
+
+// Should not warn about this excluded header
+#include <header-include-cycle.self-e.hpp>
+#endif


        


More information about the cfe-commits mailing list