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