1 //===--- HeaderIncludeCycleCheck.cpp - clang-tidy -------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "HeaderIncludeCycleCheck.h"
10 #include "../utils/OptionsUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/PPCallbacks.h"
14 #include "clang/Lex/Preprocessor.h"
15 #include "llvm/ADT/SmallVector.h"
16 #include "llvm/Support/Regex.h"
22 using namespace clang::ast_matchers
;
24 namespace clang::tidy::misc
{
34 class CyclicDependencyCallbacks
: public PPCallbacks
{
36 CyclicDependencyCallbacks(HeaderIncludeCycleCheck
&Check
,
37 const SourceManager
&SM
,
38 const std::vector
<StringRef
> &IgnoredFilesList
)
39 : Check(Check
), SM(SM
) {
40 IgnoredFilesRegexes
.reserve(IgnoredFilesList
.size());
41 for (const StringRef
&It
: IgnoredFilesList
) {
43 IgnoredFilesRegexes
.emplace_back(It
);
47 void FileChanged(SourceLocation Loc
, FileChangeReason Reason
,
48 SrcMgr::CharacteristicKind FileType
,
49 FileID PrevFID
) override
{
50 if (FileType
!= clang::SrcMgr::C_User
)
53 if (Reason
!= EnterFile
&& Reason
!= ExitFile
)
56 FileID Id
= SM
.getFileID(Loc
);
60 if (Reason
== ExitFile
) {
61 if ((Files
.size() > 1U) && (Files
.back().Id
== PrevFID
) &&
62 (Files
[Files
.size() - 2U].Id
== Id
))
67 if (!Files
.empty() && Files
.back().Id
== Id
)
70 std::optional
<llvm::StringRef
> FilePath
= SM
.getNonBuiltinFilenameForID(Id
);
71 llvm::StringRef FileName
=
72 FilePath
? llvm::sys::path::filename(*FilePath
) : llvm::StringRef();
75 NextToEnter
= Include
{Id
, FileName
, SourceLocation()};
77 assert(NextToEnter
->Name
== FileName
);
79 Files
.emplace_back(*NextToEnter
);
83 void InclusionDirective(SourceLocation
, const Token
&, StringRef FilePath
,
84 bool, CharSourceRange Range
,
85 OptionalFileEntryRef File
, StringRef
, StringRef
,
87 SrcMgr::CharacteristicKind FileType
) override
{
88 if (FileType
!= clang::SrcMgr::C_User
)
91 llvm::StringRef FileName
= llvm::sys::path::filename(FilePath
);
92 NextToEnter
= {FileID(), FileName
, Range
.getBegin()};
97 FileID Id
= SM
.translateFile(*File
);
101 checkForDoubleInclude(Id
, FileName
, Range
.getBegin());
104 void EndOfMainFile() override
{
105 if (!Files
.empty() && Files
.back().Id
== SM
.getMainFileID())
108 assert(Files
.empty());
111 void checkForDoubleInclude(FileID Id
, llvm::StringRef FileName
,
112 SourceLocation Loc
) {
114 std::find_if(Files
.rbegin(), Files
.rend(),
115 [&](const Include
&Entry
) { return Entry
.Id
== Id
; });
116 if (It
== Files
.rend())
119 const std::optional
<StringRef
> FilePath
= SM
.getNonBuiltinFilenameForID(Id
);
120 if (!FilePath
|| isFileIgnored(*FilePath
))
123 if (It
== Files
.rbegin()) {
124 Check
.diag(Loc
, "direct self-inclusion of header file '%0'") << FileName
;
128 Check
.diag(Loc
, "circular header file dependency detected while including "
129 "'%0', please check the include path")
132 const bool IsIncludePathValid
=
133 std::all_of(Files
.rbegin(), It
+ 1, [](const Include
&Elem
) {
134 return !Elem
.Name
.empty() && Elem
.Loc
.isValid();
136 if (!IsIncludePathValid
)
139 for (const Include
&I
: llvm::make_range(Files
.rbegin(), It
+ 1))
140 Check
.diag(I
.Loc
, "'%0' included from here", DiagnosticIDs::Note
)
144 bool isFileIgnored(StringRef FileName
) const {
145 return llvm::any_of(IgnoredFilesRegexes
, [&](const llvm::Regex
&It
) {
146 return It
.match(FileName
);
151 std::deque
<Include
> Files
;
152 std::optional
<Include
> NextToEnter
;
153 HeaderIncludeCycleCheck
&Check
;
154 const SourceManager
&SM
;
155 std::vector
<llvm::Regex
> IgnoredFilesRegexes
;
160 HeaderIncludeCycleCheck::HeaderIncludeCycleCheck(StringRef Name
,
161 ClangTidyContext
*Context
)
162 : ClangTidyCheck(Name
, Context
),
163 IgnoredFilesList(utils::options::parseStringList(
164 Options
.get("IgnoredFilesList", ""))) {}
166 void HeaderIncludeCycleCheck::registerPPCallbacks(
167 const SourceManager
&SM
, Preprocessor
*PP
, Preprocessor
*ModuleExpanderPP
) {
169 std::make_unique
<CyclicDependencyCallbacks
>(*this, SM
, IgnoredFilesList
));
172 void HeaderIncludeCycleCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
173 Options
.store(Opts
, "IgnoredFilesList",
174 utils::options::serializeStringList(IgnoredFilesList
));
177 } // namespace clang::tidy::misc