clang-tools 19.0.0git
HeaderIncludeCycleCheck.cpp
Go to the documentation of this file.
1//===--- HeaderIncludeCycleCheck.cpp - clang-tidy -------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
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"
17#include <algorithm>
18#include <deque>
19#include <optional>
20#include <string>
21
22using namespace clang::ast_matchers;
23
24namespace clang::tidy::misc {
25
26namespace {
27
28struct Include {
29 FileID Id;
30 llvm::StringRef Name;
31 SourceLocation Loc;
32};
33
34class CyclicDependencyCallbacks : public PPCallbacks {
35public:
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) {
42 if (!It.empty())
43 IgnoredFilesRegexes.emplace_back(It);
44 }
45 }
46
47 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
48 SrcMgr::CharacteristicKind FileType,
49 FileID PrevFID) override {
50 if (FileType != clang::SrcMgr::C_User)
51 return;
52
53 if (Reason != EnterFile && Reason != ExitFile)
54 return;
55
56 FileID Id = SM.getFileID(Loc);
57 if (Id.isInvalid())
58 return;
59
60 if (Reason == ExitFile) {
61 if ((Files.size() > 1U) && (Files.back().Id == PrevFID) &&
62 (Files[Files.size() - 2U].Id == Id))
63 Files.pop_back();
64 return;
65 }
66
67 if (!Files.empty() && Files.back().Id == Id)
68 return;
69
70 std::optional<llvm::StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
71 llvm::StringRef FileName =
72 FilePath ? llvm::sys::path::filename(*FilePath) : llvm::StringRef();
73
74 if (!NextToEnter)
75 NextToEnter = Include{Id, FileName, SourceLocation()};
76
77 assert(NextToEnter->Name == FileName);
78 NextToEnter->Id = Id;
79 Files.emplace_back(*NextToEnter);
80 NextToEnter.reset();
81 }
82
83 void InclusionDirective(SourceLocation, const Token &, StringRef FilePath,
84 bool, CharSourceRange Range,
85 OptionalFileEntryRef File, StringRef, StringRef,
86 const Module *, bool,
87 SrcMgr::CharacteristicKind FileType) override {
88 if (FileType != clang::SrcMgr::C_User)
89 return;
90
91 llvm::StringRef FileName = llvm::sys::path::filename(FilePath);
92 NextToEnter = {FileID(), FileName, Range.getBegin()};
93
94 if (!File)
95 return;
96
97 FileID Id = SM.translateFile(*File);
98 if (Id.isInvalid())
99 return;
100
101 checkForDoubleInclude(Id, FileName, Range.getBegin());
102 }
103
104 void EndOfMainFile() override {
105 if (!Files.empty() && Files.back().Id == SM.getMainFileID())
106 Files.pop_back();
107
108 assert(Files.empty());
109 }
110
111 void checkForDoubleInclude(FileID Id, llvm::StringRef FileName,
112 SourceLocation Loc) {
113 auto It =
114 std::find_if(Files.rbegin(), Files.rend(),
115 [&](const Include &Entry) { return Entry.Id == Id; });
116 if (It == Files.rend())
117 return;
118
119 const std::optional<StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
120 if (!FilePath || isFileIgnored(*FilePath))
121 return;
122
123 if (It == Files.rbegin()) {
124 Check.diag(Loc, "direct self-inclusion of header file '%0'") << FileName;
125 return;
126 }
127
128 Check.diag(Loc, "circular header file dependency detected while including "
129 "'%0', please check the include path")
130 << FileName;
131
132 const bool IsIncludePathValid =
133 std::all_of(Files.rbegin(), It, [](const Include &Elem) {
134 return !Elem.Name.empty() && Elem.Loc.isValid();
135 });
136
137 if (!IsIncludePathValid)
138 return;
139
140 auto CurrentIt = Files.rbegin();
141 do {
142 Check.diag(CurrentIt->Loc, "'%0' included from here", DiagnosticIDs::Note)
143 << CurrentIt->Name;
144 } while (CurrentIt++ != It);
145 }
146
147 bool isFileIgnored(StringRef FileName) const {
148 return llvm::any_of(IgnoredFilesRegexes, [&](const llvm::Regex &It) {
149 return It.match(FileName);
150 });
151 }
152
153private:
154 std::deque<Include> Files;
155 std::optional<Include> NextToEnter;
156 HeaderIncludeCycleCheck &Check;
157 const SourceManager &SM;
158 std::vector<llvm::Regex> IgnoredFilesRegexes;
159};
160
161} // namespace
162
164 ClangTidyContext *Context)
165 : ClangTidyCheck(Name, Context),
166 IgnoredFilesList(utils::options::parseStringList(
167 Options.get("IgnoredFilesList", ""))) {}
168
170 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
171 PP->addPPCallbacks(
172 std::make_unique<CyclicDependencyCallbacks>(*this, SM, IgnoredFilesList));
173}
174
176 Options.store(Opts, "IgnoredFilesList",
177 utils::options::serializeStringList(IgnoredFilesList));
178}
179
180} // namespace clang::tidy::misc
llvm::SmallString< 256U > Name
CharSourceRange Range
SourceRange for the file name.
StringRef FileName
SourceLocation Loc
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
HeaderIncludeCycleCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
Some operations such as code completion produce a set of candidates.
llvm::StringMap< ClangTidyValue > OptionMap