PiotrZSL created this revision.
Herald added subscribers: carlosgalvezp, xazax.hun.
Herald added a reviewer: njames93.
Herald added a project: All.
PiotrZSL updated this revision to Diff 500591.
PiotrZSL added a comment.
Eugene.Zelenko added reviewers: aaron.ballman, carlosgalvezp.
Eugene.Zelenko added a project: clang-tools-extra.
PiotrZSL updated this revision to Diff 501040.
PiotrZSL published this revision for review.
Herald added a subscriber: cfe-commits.

Typo fix.


PiotrZSL added a comment.

Fixes


PiotrZSL added a comment.

Findings on llvm:

  clang-tools-extra/unittests/clang-doc/ClangDocTest.h:12:10: warning: direct 
self-inclusion of header file 'ClangDocTest.h' [misc-header-include-cycle]
  libc/src/__support/FPUtil/x86_64/LongDoubleBits.h:21:10: warning: circular 
header file dependency detected while including 'FPBits.h', please check the 
include path: 'FPBits.h' -> 'LongDoubleBits.h' -> 'FPBits.h' 
[misc-header-include-cycle]
  lldb/include/lldb/API/SBAddress.h:13:10: warning: circular header file 
dependency detected while including 'SBModule.h', please check the include 
path: 'SBModule.h' -> 'SBSymbolContext.h' -> 'SBBlock.h' -> 'SBTarget.h' -> 
'SBAddress.h' -> 'SBModule.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBBlock.h:14:10: warning: circular header file 
dependency detected while including 'SBTarget.h', please check the include 
path: 'SBTarget.h' -> 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 
'SBBlock.h' -> 'SBTarget.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBFunction.h:12:10: warning: circular header file 
dependency detected while including 'SBAddress.h', please check the include 
path: 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 'SBFunction.h' -> 
'SBAddress.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBLineEntry.h:12:10: warning: circular header file 
dependency detected while including 'SBAddress.h', please check the include 
path: 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 'SBLineEntry.h' 
-> 'SBAddress.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBModule.h:15:10: warning: circular header file 
dependency detected while including 'SBSymbolContext.h', please check the 
include path: 'SBSymbolContext.h' -> 'SBBlock.h' -> 'SBTarget.h' -> 
'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbol.h:12:10: warning: circular header file 
dependency detected while including 'SBAddress.h', please check the include 
path: 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 'SBSymbol.h' -> 
'SBAddress.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbol.h:15:10: warning: circular header file 
dependency detected while including 'SBTarget.h', please check the include 
path: 'SBTarget.h' -> 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 
'SBSymbol.h' -> 'SBTarget.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbolContext.h:12:10: warning: circular header file 
dependency detected while including 'SBBlock.h', please check the include path: 
'SBBlock.h' -> 'SBTarget.h' -> 'SBAddress.h' -> 'SBModule.h' -> 
'SBSymbolContext.h' -> 'SBBlock.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbolContext.h:15:10: warning: circular header file 
dependency detected while including 'SBFunction.h', please check the include 
path: 'SBFunction.h' -> 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 
'SBFunction.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbolContext.h:16:10: warning: circular header file 
dependency detected while including 'SBLineEntry.h', please check the include 
path: 'SBLineEntry.h' -> 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' 
-> 'SBLineEntry.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbolContext.h:17:10: warning: circular header file 
dependency detected while including 'SBModule.h', please check the include 
path: 'SBModule.h' -> 'SBSymbolContext.h' -> 'SBModule.h' 
[misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbolContext.h:18:10: warning: circular header file 
dependency detected while including 'SBSymbol.h', please check the include 
path: 'SBSymbol.h' -> 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 
'SBSymbol.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBSymbolContextList.h:13:10: warning: circular header 
file dependency detected while including 'SBSymbolContext.h', please check the 
include path: 'SBSymbolContext.h' -> 'SBBlock.h' -> 'SBTarget.h' -> 
'SBSymbolContextList.h' -> 'SBSymbolContext.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBTarget.h:12:10: warning: circular header file 
dependency detected while including 'SBAddress.h', please check the include 
path: 'SBAddress.h' -> 'SBModule.h' -> 'SBSymbolContext.h' -> 'SBBlock.h' -> 
'SBTarget.h' -> 'SBAddress.h' [misc-header-include-cycle]
  lldb/include/lldb/API/SBTarget.h:20:10: warning: circular header file 
dependency detected while including 'SBSymbolContextList.h', please check the 
include path: 'SBSymbolContextList.h' -> 'SBSymbolContext.h' -> 'SBBlock.h' -> 
'SBTarget.h' -> 'SBSymbolContextList.h' [misc-header-include-cycle]
  lldb/include/lldb/lldb-private-types.h:14:10: warning: circular header file 
dependency detected while including 'lldb-private.h', please check the include 
path: 'lldb-private.h' -> 'lldb-private-types.h' -> 'lldb-private.h' 
[misc-header-include-cycle]
  lldb/include/lldb/lldb-private.h:16:10: warning: circular header file 
dependency detected while including 'lldb-private-types.h', please check the 
include path: 'lldb-private-types.h' -> 'lldb-private.h' -> 
'lldb-private-types.h' [misc-header-include-cycle]
  lldb/source/Plugins/Process/scripted/ScriptedProcess.h:17:10: warning: 
circular header file dependency detected while including 'ScriptedThread.h', 
please check the include path: 'ScriptedThread.h' -> 'ScriptedProcess.h' -> 
'ScriptedThread.h' [misc-header-include-cycle]
  lldb/source/Plugins/Process/scripted/ScriptedThread.h:14:10: warning: 
circular header file dependency detected while including 'ScriptedProcess.h', 
please check the include path: 'ScriptedProcess.h' -> 'ScriptedThread.h' -> 
'ScriptedProcess.h' [misc-header-include-cycle]
  third-party/unittest/googletest/include/gtest/gtest_pred_impl.h:43:10: 
warning: circular header file dependency detected while including 'gtest.h', 
please check the include path: 'gtest.h' -> 'gtest_pred_impl.h' -> 'gtest.h' 
[misc-header-include-cycle]


PiotrZSL added a comment.

Ready for review


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


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D144828

Files:
  clang-tools-extra/clang-tidy/misc/CMakeLists.txt
  clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp
  clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h
  clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  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.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/header-include-cycle.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp
@@ -0,0 +1,17 @@
+// 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: clang-tidy %s -checks='-*,misc-header-include-cycle' -header-filter=.* -- -I%T/misc-header-include-cycle-headers | FileCheck %s -check-prefix=CHECK-MESSAGES -implicit-check-not="{{warning|error}}:"
+// RUN: rm -rf %T/misc-header-include-cycle-headers
+
+#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: 'header-include-cycle.first-d.hpp' -> 'header-include-cycle.second-d.hpp' -> 'header-include-cycle.third-d.hpp' -> 'header-include-cycle.fourth-d.hpp' -> 'header-include-cycle.first-d.hpp' [misc-header-include-cycle]
+
+#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: 'header-include-cycle.first.hpp' -> 'header-include-cycle.second.hpp' -> 'header-include-cycle.third.hpp' -> 'header-include-cycle.fourth.hpp' -> 'header-include-cycle.first.hpp' [misc-header-include-cycle]
+
+#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]
+
+#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]
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third.hpp
===================================================================
--- /dev/null
+++ 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"
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.third-d.hpp
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self.hpp
===================================================================
--- /dev/null
+++ 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"
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.self-d.hpp
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second.hpp
===================================================================
--- /dev/null
+++ 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"
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.second-d.hpp
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth.hpp
===================================================================
--- /dev/null
+++ 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"
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.fourth-d.hpp
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first.hpp
===================================================================
--- /dev/null
+++ 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"
Index: clang-tools-extra/test/clang-tidy/checkers/misc/Inputs/header-include-cycle.first-d.hpp
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/docs/clang-tidy/checks/misc/header-include-cycle.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/misc/header-include-cycle.rst
@@ -0,0 +1,63 @@
+.. 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 difficult 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.
+
+This check has no options to configure.
+
+**Catch header cycles before they catch you - Try this Clang-tidy check today!**
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -249,6 +249,7 @@
    `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-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
    `misc-misleading-identifier <misc/misleading-identifier.html>`_,
    `misc-misplaced-const <misc/misplaced-const.html>`_,
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -120,6 +120,11 @@
   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 check aliases
 ^^^^^^^^^^^^^^^^^
 
Index: clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ 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 "MisleadingBidirectional.h"
 #include "MisleadingIdentifier.h"
 #include "MisplacedConstCheck.h"
@@ -41,6 +42,8 @@
         "misc-const-correctness");
     CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
         "misc-definitions-in-headers");
+    CheckFactories.registerCheck<HeaderIncludeCycleCheck>(
+        "misc-header-include-cycle");
     CheckFactories.registerCheck<MisleadingBidirectionalCheck>(
         "misc-misleading-bidirectional");
     CheckFactories.registerCheck<MisleadingIdentifierCheck>(
Index: clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h
@@ -0,0 +1,30 @@
+//===--- 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"
+
+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)
+      : ClangTidyCheck(Name, Context) {}
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERINCLUDECYCLECHECK_H
Index: clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp
@@ -0,0 +1,146 @@
+//===--- 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 "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 <algorithm>
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+namespace {
+
+class CyclicDependencyCallbacks : public PPCallbacks {
+public:
+  CyclicDependencyCallbacks(HeaderIncludeCycleCheck &Check,
+                            const SourceManager &SM)
+      : Check(Check), SM(SM) {
+    Files.emplace_back(SM.getMainFileID());
+  }
+
+  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 == EnterFile) {
+      if (Files.empty() || Files.back() != Id) {
+        Files.emplace_back(Id);
+      }
+    } else if ((Files.size() > 1U) && (Files.back() == PrevFID) &&
+               (Files[Files.size() - 2U] == Id))
+      Files.pop_back();
+  }
+
+  void FileSkipped(const FileEntryRef &SkippedFile, const Token &FilenameTok,
+                   SrcMgr::CharacteristicKind FileType) override {
+    if (FileType != clang::SrcMgr::C_User)
+      return;
+
+    checkForDoubleInclude(SkippedFile, FilenameTok.getLocation());
+  }
+
+  void InclusionDirective(SourceLocation, const Token &, StringRef, bool,
+                          CharSourceRange FilenameRange,
+                          OptionalFileEntryRef File, StringRef, StringRef,
+                          const Module *,
+                          SrcMgr::CharacteristicKind FileType) override {
+    if (FileType != clang::SrcMgr::C_User)
+      return;
+
+    if (File)
+      checkForDoubleInclude(*File, FilenameRange.getBegin());
+  }
+
+  void EndOfMainFile() override { assert(Files.size() == 1U); }
+
+  void checkForDoubleInclude(const FileEntryRef &File, SourceLocation Loc) {
+    FileID Id = SM.translateFile(File);
+    if (Id.isInvalid())
+      return;
+
+    auto It = std::find(Files.begin(), Files.end(), Id);
+    if (It == Files.end())
+      return;
+
+    SmallVector<llvm::StringRef, 32U> IncludePath;
+
+    while (It != Files.end()) {
+      std::optional<llvm::StringRef> FilePath =
+          SM.getNonBuiltinFilenameForID(*It);
+      ++It;
+      if (!FilePath)
+        continue;
+      IncludePath.emplace_back(getFileName(*FilePath));
+    }
+
+    std::optional<llvm::StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
+    if (!FilePath)
+      return;
+
+    IncludePath.emplace_back(getFileName(*FilePath));
+    reportDiagnostic(Loc, IncludePath);
+  }
+
+  void reportDiagnostic(const SourceLocation &Loc,
+                        const SmallVector<llvm::StringRef, 32U> &History) {
+    if (History.size() == 2U) {
+      Check.diag(Loc, "direct self-inclusion of header file '%0'")
+          << History.back();
+      return;
+    }
+
+    std::string IncludePath;
+    for (const llvm::StringRef &Name : History) {
+      IncludePath += '\'';
+      IncludePath += Name;
+      IncludePath += "' -> ";
+    }
+
+    Check.diag(Loc, "circular header file dependency detected while including "
+                    "'%0', please check the include path: %1")
+        << History.back() << llvm::StringRef(IncludePath).rsplit(" -> ").first;
+  }
+
+  static llvm::StringRef getFileName(llvm::StringRef FilePath) {
+    llvm::StringRef FileName = FilePath;
+    if (FileName.contains('/'))
+      FileName = FileName.rsplit('/').second;
+    if (FileName.contains('\\'))
+      FileName = FileName.rsplit('\\').second;
+    return FileName;
+  }
+
+private:
+  SmallVector<FileID, 1024U> Files;
+  HeaderIncludeCycleCheck &Check;
+  const SourceManager &SM;
+};
+
+} // namespace
+
+void HeaderIncludeCycleCheck::registerPPCallbacks(
+    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
+  PP->addPPCallbacks(std::make_unique<CyclicDependencyCallbacks>(*this, SM));
+}
+
+} // namespace clang::tidy::misc
Index: clang-tools-extra/clang-tidy/misc/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -19,6 +19,7 @@
   ConstCorrectnessCheck.cpp
   DefinitionsInHeadersCheck.cpp
   ConfusableIdentifierCheck.cpp
+  HeaderIncludeCycleCheck.cpp
   MiscTidyModule.cpp
   MisleadingBidirectional.cpp
   MisleadingIdentifier.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to