xref: /llvm-project/clang-tools-extra/clang-tidy/misc/HeaderIncludeCycleCheck.cpp (revision c5ff983fe4a3180e13c7244a6ce9f5994b4379b4)
17f6e0052SPiotr Zegar //===--- HeaderIncludeCycleCheck.cpp - clang-tidy -------------------------===//
27f6e0052SPiotr Zegar //
37f6e0052SPiotr Zegar // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47f6e0052SPiotr Zegar // See https://llvm.org/LICENSE.txt for license information.
57f6e0052SPiotr Zegar // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67f6e0052SPiotr Zegar //
77f6e0052SPiotr Zegar //===----------------------------------------------------------------------===//
87f6e0052SPiotr Zegar 
97f6e0052SPiotr Zegar #include "HeaderIncludeCycleCheck.h"
107f6e0052SPiotr Zegar #include "../utils/OptionsUtils.h"
117f6e0052SPiotr Zegar #include "clang/AST/ASTContext.h"
127f6e0052SPiotr Zegar #include "clang/ASTMatchers/ASTMatchFinder.h"
137f6e0052SPiotr Zegar #include "clang/Lex/PPCallbacks.h"
147f6e0052SPiotr Zegar #include "clang/Lex/Preprocessor.h"
157f6e0052SPiotr Zegar #include "llvm/ADT/SmallVector.h"
167f6e0052SPiotr Zegar #include "llvm/Support/Regex.h"
177f6e0052SPiotr Zegar #include <algorithm>
187f6e0052SPiotr Zegar #include <deque>
197f6e0052SPiotr Zegar #include <optional>
207f6e0052SPiotr Zegar #include <string>
217f6e0052SPiotr Zegar 
227f6e0052SPiotr Zegar using namespace clang::ast_matchers;
237f6e0052SPiotr Zegar 
247f6e0052SPiotr Zegar namespace clang::tidy::misc {
257f6e0052SPiotr Zegar 
267f6e0052SPiotr Zegar namespace {
277f6e0052SPiotr Zegar 
287f6e0052SPiotr Zegar struct Include {
297f6e0052SPiotr Zegar   FileID Id;
307f6e0052SPiotr Zegar   llvm::StringRef Name;
317f6e0052SPiotr Zegar   SourceLocation Loc;
327f6e0052SPiotr Zegar };
337f6e0052SPiotr Zegar 
347f6e0052SPiotr Zegar class CyclicDependencyCallbacks : public PPCallbacks {
357f6e0052SPiotr Zegar public:
CyclicDependencyCallbacks(HeaderIncludeCycleCheck & Check,const SourceManager & SM,const std::vector<StringRef> & IgnoredFilesList)367f6e0052SPiotr Zegar   CyclicDependencyCallbacks(HeaderIncludeCycleCheck &Check,
377f6e0052SPiotr Zegar                             const SourceManager &SM,
387f6e0052SPiotr Zegar                             const std::vector<StringRef> &IgnoredFilesList)
397f6e0052SPiotr Zegar       : Check(Check), SM(SM) {
407f6e0052SPiotr Zegar     IgnoredFilesRegexes.reserve(IgnoredFilesList.size());
417f6e0052SPiotr Zegar     for (const StringRef &It : IgnoredFilesList) {
427f6e0052SPiotr Zegar       if (!It.empty())
437f6e0052SPiotr Zegar         IgnoredFilesRegexes.emplace_back(It);
447f6e0052SPiotr Zegar     }
457f6e0052SPiotr Zegar   }
467f6e0052SPiotr Zegar 
FileChanged(SourceLocation Loc,FileChangeReason Reason,SrcMgr::CharacteristicKind FileType,FileID PrevFID)477f6e0052SPiotr Zegar   void FileChanged(SourceLocation Loc, FileChangeReason Reason,
487f6e0052SPiotr Zegar                    SrcMgr::CharacteristicKind FileType,
497f6e0052SPiotr Zegar                    FileID PrevFID) override {
507f6e0052SPiotr Zegar     if (FileType != clang::SrcMgr::C_User)
517f6e0052SPiotr Zegar       return;
527f6e0052SPiotr Zegar 
537f6e0052SPiotr Zegar     if (Reason != EnterFile && Reason != ExitFile)
547f6e0052SPiotr Zegar       return;
557f6e0052SPiotr Zegar 
567f6e0052SPiotr Zegar     FileID Id = SM.getFileID(Loc);
577f6e0052SPiotr Zegar     if (Id.isInvalid())
587f6e0052SPiotr Zegar       return;
597f6e0052SPiotr Zegar 
607f6e0052SPiotr Zegar     if (Reason == ExitFile) {
617f6e0052SPiotr Zegar       if ((Files.size() > 1U) && (Files.back().Id == PrevFID) &&
627f6e0052SPiotr Zegar           (Files[Files.size() - 2U].Id == Id))
637f6e0052SPiotr Zegar         Files.pop_back();
647f6e0052SPiotr Zegar       return;
657f6e0052SPiotr Zegar     }
667f6e0052SPiotr Zegar 
677f6e0052SPiotr Zegar     if (!Files.empty() && Files.back().Id == Id)
687f6e0052SPiotr Zegar       return;
697f6e0052SPiotr Zegar 
707f6e0052SPiotr Zegar     std::optional<llvm::StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
717f6e0052SPiotr Zegar     llvm::StringRef FileName =
727f6e0052SPiotr Zegar         FilePath ? llvm::sys::path::filename(*FilePath) : llvm::StringRef();
737f6e0052SPiotr Zegar 
747f6e0052SPiotr Zegar     if (!NextToEnter)
757f6e0052SPiotr Zegar       NextToEnter = Include{Id, FileName, SourceLocation()};
767f6e0052SPiotr Zegar 
777f6e0052SPiotr Zegar     assert(NextToEnter->Name == FileName);
787f6e0052SPiotr Zegar     NextToEnter->Id = Id;
797f6e0052SPiotr Zegar     Files.emplace_back(*NextToEnter);
807f6e0052SPiotr Zegar     NextToEnter.reset();
817f6e0052SPiotr Zegar   }
827f6e0052SPiotr Zegar 
InclusionDirective(SourceLocation,const Token &,StringRef FilePath,bool,CharSourceRange Range,OptionalFileEntryRef File,StringRef,StringRef,const Module *,bool,SrcMgr::CharacteristicKind FileType)837f6e0052SPiotr Zegar   void InclusionDirective(SourceLocation, const Token &, StringRef FilePath,
847f6e0052SPiotr Zegar                           bool, CharSourceRange Range,
857f6e0052SPiotr Zegar                           OptionalFileEntryRef File, StringRef, StringRef,
86da95d926SJan Svoboda                           const Module *, bool,
877f6e0052SPiotr Zegar                           SrcMgr::CharacteristicKind FileType) override {
887f6e0052SPiotr Zegar     if (FileType != clang::SrcMgr::C_User)
897f6e0052SPiotr Zegar       return;
907f6e0052SPiotr Zegar 
917f6e0052SPiotr Zegar     llvm::StringRef FileName = llvm::sys::path::filename(FilePath);
927f6e0052SPiotr Zegar     NextToEnter = {FileID(), FileName, Range.getBegin()};
937f6e0052SPiotr Zegar 
947f6e0052SPiotr Zegar     if (!File)
957f6e0052SPiotr Zegar       return;
967f6e0052SPiotr Zegar 
977f6e0052SPiotr Zegar     FileID Id = SM.translateFile(*File);
987f6e0052SPiotr Zegar     if (Id.isInvalid())
997f6e0052SPiotr Zegar       return;
1007f6e0052SPiotr Zegar 
1017f6e0052SPiotr Zegar     checkForDoubleInclude(Id, FileName, Range.getBegin());
1027f6e0052SPiotr Zegar   }
1037f6e0052SPiotr Zegar 
EndOfMainFile()1047f6e0052SPiotr Zegar   void EndOfMainFile() override {
1057f6e0052SPiotr Zegar     if (!Files.empty() && Files.back().Id == SM.getMainFileID())
1067f6e0052SPiotr Zegar       Files.pop_back();
1077f6e0052SPiotr Zegar 
1087f6e0052SPiotr Zegar     assert(Files.empty());
1097f6e0052SPiotr Zegar   }
1107f6e0052SPiotr Zegar 
checkForDoubleInclude(FileID Id,llvm::StringRef FileName,SourceLocation Loc)1117f6e0052SPiotr Zegar   void checkForDoubleInclude(FileID Id, llvm::StringRef FileName,
1127f6e0052SPiotr Zegar                              SourceLocation Loc) {
1137f6e0052SPiotr Zegar     auto It =
1147f6e0052SPiotr Zegar         std::find_if(Files.rbegin(), Files.rend(),
1157f6e0052SPiotr Zegar                      [&](const Include &Entry) { return Entry.Id == Id; });
1167f6e0052SPiotr Zegar     if (It == Files.rend())
1177f6e0052SPiotr Zegar       return;
1187f6e0052SPiotr Zegar 
1197f6e0052SPiotr Zegar     const std::optional<StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);
1207f6e0052SPiotr Zegar     if (!FilePath || isFileIgnored(*FilePath))
1217f6e0052SPiotr Zegar       return;
1227f6e0052SPiotr Zegar 
1237f6e0052SPiotr Zegar     if (It == Files.rbegin()) {
1247f6e0052SPiotr Zegar       Check.diag(Loc, "direct self-inclusion of header file '%0'") << FileName;
1257f6e0052SPiotr Zegar       return;
1267f6e0052SPiotr Zegar     }
1277f6e0052SPiotr Zegar 
1287f6e0052SPiotr Zegar     Check.diag(Loc, "circular header file dependency detected while including "
1297f6e0052SPiotr Zegar                     "'%0', please check the include path")
1307f6e0052SPiotr Zegar         << FileName;
1317f6e0052SPiotr Zegar 
1327f6e0052SPiotr Zegar     const bool IsIncludePathValid =
133*c5ff983fSCongcong Cai         std::all_of(Files.rbegin(), It + 1, [](const Include &Elem) {
1347f6e0052SPiotr Zegar           return !Elem.Name.empty() && Elem.Loc.isValid();
1357f6e0052SPiotr Zegar         });
1367f6e0052SPiotr Zegar     if (!IsIncludePathValid)
1377f6e0052SPiotr Zegar       return;
1387f6e0052SPiotr Zegar 
139*c5ff983fSCongcong Cai     for (const Include &I : llvm::make_range(Files.rbegin(), It + 1))
140*c5ff983fSCongcong Cai       Check.diag(I.Loc, "'%0' included from here", DiagnosticIDs::Note)
141*c5ff983fSCongcong Cai           << I.Name;
1427f6e0052SPiotr Zegar   }
1437f6e0052SPiotr Zegar 
isFileIgnored(StringRef FileName) const1447f6e0052SPiotr Zegar   bool isFileIgnored(StringRef FileName) const {
1457f6e0052SPiotr Zegar     return llvm::any_of(IgnoredFilesRegexes, [&](const llvm::Regex &It) {
1467f6e0052SPiotr Zegar       return It.match(FileName);
1477f6e0052SPiotr Zegar     });
1487f6e0052SPiotr Zegar   }
1497f6e0052SPiotr Zegar 
1507f6e0052SPiotr Zegar private:
1517f6e0052SPiotr Zegar   std::deque<Include> Files;
1527f6e0052SPiotr Zegar   std::optional<Include> NextToEnter;
1537f6e0052SPiotr Zegar   HeaderIncludeCycleCheck &Check;
1547f6e0052SPiotr Zegar   const SourceManager &SM;
1557f6e0052SPiotr Zegar   std::vector<llvm::Regex> IgnoredFilesRegexes;
1567f6e0052SPiotr Zegar };
1577f6e0052SPiotr Zegar 
1587f6e0052SPiotr Zegar } // namespace
1597f6e0052SPiotr Zegar 
HeaderIncludeCycleCheck(StringRef Name,ClangTidyContext * Context)1607f6e0052SPiotr Zegar HeaderIncludeCycleCheck::HeaderIncludeCycleCheck(StringRef Name,
1617f6e0052SPiotr Zegar                                                  ClangTidyContext *Context)
1627f6e0052SPiotr Zegar     : ClangTidyCheck(Name, Context),
1637f6e0052SPiotr Zegar       IgnoredFilesList(utils::options::parseStringList(
1647f6e0052SPiotr Zegar           Options.get("IgnoredFilesList", ""))) {}
1657f6e0052SPiotr Zegar 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)1667f6e0052SPiotr Zegar void HeaderIncludeCycleCheck::registerPPCallbacks(
1677f6e0052SPiotr Zegar     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
1687f6e0052SPiotr Zegar   PP->addPPCallbacks(
1697f6e0052SPiotr Zegar       std::make_unique<CyclicDependencyCallbacks>(*this, SM, IgnoredFilesList));
1707f6e0052SPiotr Zegar }
1717f6e0052SPiotr Zegar 
storeOptions(ClangTidyOptions::OptionMap & Opts)1727f6e0052SPiotr Zegar void HeaderIncludeCycleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
1737f6e0052SPiotr Zegar   Options.store(Opts, "IgnoredFilesList",
1747f6e0052SPiotr Zegar                 utils::options::serializeStringList(IgnoredFilesList));
1757f6e0052SPiotr Zegar }
1767f6e0052SPiotr Zegar 
1777f6e0052SPiotr Zegar } // namespace clang::tidy::misc
178