diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ b/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 diff --git a/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h b/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.h new file mode 100644 --- /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 + +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 IgnoredFilesList; +}; + +} // namespace clang::tidy::misc + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_HEADERINCLUDECYCLECHECK_H diff --git a/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp b/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp new file mode 100644 --- /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 +#include +#include +#include + +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 &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 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 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 Files; + std::optional NextToEnter; + HeaderIncludeCycleCheck &Check; + const SourceManager &SM; + std::vector 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(*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/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp --- 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 "MisleadingBidirectional.h" #include "MisleadingIdentifier.h" #include "MisplacedConstCheck.h" @@ -41,6 +42,8 @@ "misc-const-correctness"); CheckFactories.registerCheck( "misc-definitions-in-headers"); + CheckFactories.registerCheck( + "misc-header-include-cycle"); CheckFactories.registerCheck( "misc-misleading-bidirectional"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -136,6 +136,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 + ` check. + + Check detects cyclic ``#include`` dependencies between user-defined headers. + - New :doc:`modernize-type-traits ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -251,6 +251,7 @@ `misc-confusable-identifiers `_, `misc-const-correctness `_, "Yes" `misc-definitions-in-headers `_, "Yes" + `misc-header-include-cycle `_, `misc-misleading-bidirectional `_, `misc-misleading-identifier `_, `misc-misplaced-const `_, 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 --- /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 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. + +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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /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 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc/header-include-cycle.cpp @@ -0,0 +1,58 @@ +// 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: clang-tidy %s -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="{{warning|error|note}}:" +// RUN: rm -rf %T/misc-header-include-cycle-headers + +#ifndef MAIN_GUARD +#define MAIN_GUARD + +#include +// 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: header-include-cycle.cpp:[[@LINE-5]]:10: note: 'header-include-cycle.first-d.hpp' included from here + +#include +// 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: header-include-cycle.cpp:[[@LINE-5]]:10: note: 'header-include-cycle.first.hpp' included from here + +#include +// 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 +// 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 +// 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 +#include +#include +#include +#include +#include + +// Should not warn about system includes +#include +#include + +// Should not warn about this excluded header +#include + +#include "header-include-cycle.cpp" +// CHECK-MESSAGES: header-include-cycle.cpp:[[@LINE-1]]:10: warning: direct self-inclusion of header file 'header-include-cycle.cpp' [misc-header-include-cycle] +#endif