[AMDGPU][AsmParser][NFC] Translate parsed MIMG instructions to MCInsts automatically.
[llvm-project.git] / clang-tools-extra / clang-tidy / misc / HeaderIncludeCycleCheck.cpp
blobbebd6e390ed53c65e1c819f24b6202914e4df6ed
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 //===----------------------------------------------------------------------===//
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"
17 #include <algorithm>
18 #include <deque>
19 #include <optional>
20 #include <string>
22 using namespace clang::ast_matchers;
24 namespace clang::tidy::misc {
26 namespace {
28 struct Include {
29 FileID Id;
30 llvm::StringRef Name;
31 SourceLocation Loc;
34 class CyclicDependencyCallbacks : public PPCallbacks {
35 public:
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);
47 void FileChanged(SourceLocation Loc, FileChangeReason Reason,
48 SrcMgr::CharacteristicKind FileType,
49 FileID PrevFID) override {
50 if (FileType != clang::SrcMgr::C_User)
51 return;
53 if (Reason != EnterFile && Reason != ExitFile)
54 return;
56 FileID Id = SM.getFileID(Loc);
57 if (Id.isInvalid())
58 return;
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;
67 if (!Files.empty() && Files.back().Id == Id)
68 return;
70 std::optional<llvm::StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
71 llvm::StringRef FileName =
72 FilePath ? llvm::sys::path::filename(*FilePath) : llvm::StringRef();
74 if (!NextToEnter)
75 NextToEnter = Include{Id, FileName, SourceLocation()};
77 assert(NextToEnter->Name == FileName);
78 NextToEnter->Id = Id;
79 Files.emplace_back(*NextToEnter);
80 NextToEnter.reset();
83 void InclusionDirective(SourceLocation, const Token &, StringRef FilePath,
84 bool, CharSourceRange Range,
85 OptionalFileEntryRef File, StringRef, StringRef,
86 const Module *,
87 SrcMgr::CharacteristicKind FileType) override {
88 if (FileType != clang::SrcMgr::C_User)
89 return;
91 llvm::StringRef FileName = llvm::sys::path::filename(FilePath);
92 NextToEnter = {FileID(), FileName, Range.getBegin()};
94 if (!File)
95 return;
97 FileID Id = SM.translateFile(*File);
98 if (Id.isInvalid())
99 return;
101 checkForDoubleInclude(Id, FileName, Range.getBegin());
104 void EndOfMainFile() override {
105 if (!Files.empty() && Files.back().Id == SM.getMainFileID())
106 Files.pop_back();
108 assert(Files.empty());
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;
119 const std::optional<StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
120 if (!FilePath || isFileIgnored(*FilePath))
121 return;
123 if (It == Files.rbegin()) {
124 Check.diag(Loc, "direct self-inclusion of header file '%0'") << FileName;
125 return;
128 Check.diag(Loc, "circular header file dependency detected while including "
129 "'%0', please check the include path")
130 << FileName;
132 const bool IsIncludePathValid =
133 std::all_of(Files.rbegin(), It, [](const Include &Elem) {
134 return !Elem.Name.empty() && Elem.Loc.isValid();
137 if (!IsIncludePathValid)
138 return;
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);
147 bool isFileIgnored(StringRef FileName) const {
148 return llvm::any_of(IgnoredFilesRegexes, [&](const llvm::Regex &It) {
149 return It.match(FileName);
153 private:
154 std::deque<Include> Files;
155 std::optional<Include> NextToEnter;
156 HeaderIncludeCycleCheck &Check;
157 const SourceManager &SM;
158 std::vector<llvm::Regex> IgnoredFilesRegexes;
161 } // namespace
163 HeaderIncludeCycleCheck::HeaderIncludeCycleCheck(StringRef Name,
164 ClangTidyContext *Context)
165 : ClangTidyCheck(Name, Context),
166 IgnoredFilesList(utils::options::parseStringList(
167 Options.get("IgnoredFilesList", ""))) {}
169 void HeaderIncludeCycleCheck::registerPPCallbacks(
170 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
171 PP->addPPCallbacks(
172 std::make_unique<CyclicDependencyCallbacks>(*this, SM, IgnoredFilesList));
175 void HeaderIncludeCycleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
176 Options.store(Opts, "IgnoredFilesList",
177 utils::options::serializeStringList(IgnoredFilesList));
180 } // namespace clang::tidy::misc