171 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "HeaderIncludeCycleCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/Lex/PPCallbacks.h"13#include "clang/Lex/Preprocessor.h"14#include "llvm/Support/Regex.h"15#include <algorithm>16#include <optional>17#include <vector>18 19using namespace clang::ast_matchers;20 21namespace clang::tidy::misc {22 23namespace {24 25struct Include {26 const FileEntry *File;27 StringRef Name;28 SourceLocation Loc;29};30 31class CyclicDependencyCallbacks : public PPCallbacks {32public:33 CyclicDependencyCallbacks(HeaderIncludeCycleCheck &Check,34 const SourceManager &SM,35 const std::vector<StringRef> &IgnoredFilesList)36 : Check(Check), SM(SM) {37 IgnoredFilesRegexes.reserve(IgnoredFilesList.size());38 for (const StringRef &It : IgnoredFilesList) {39 if (!It.empty())40 IgnoredFilesRegexes.emplace_back(It);41 }42 }43 44 void FileChanged(SourceLocation Loc, FileChangeReason Reason,45 SrcMgr::CharacteristicKind FileType,46 FileID PrevFID) override {47 if (FileType != clang::SrcMgr::C_User)48 return;49 50 if (Reason != EnterFile && Reason != ExitFile)51 return;52 53 const FileID Id = SM.getFileID(Loc);54 if (Id.isInvalid())55 return;56 57 const FileEntry *NewFile = SM.getFileEntryForID(Id);58 const FileEntry *PrevFile = SM.getFileEntryForID(PrevFID);59 60 if (Reason == ExitFile) {61 if ((Files.size() > 1U) && (Files.back().File == PrevFile) &&62 (Files[Files.size() - 2U].File == NewFile))63 Files.pop_back();64 return;65 }66 67 if (!Files.empty() && Files.back().File == NewFile)68 return;69 70 const std::optional<StringRef> FilePath = SM.getNonBuiltinFilenameForID(Id);71 const StringRef FileName =72 FilePath ? llvm::sys::path::filename(*FilePath) : StringRef();73 Files.push_back({NewFile, FileName, std::exchange(NextToEnter, {})});74 }75 76 void InclusionDirective(SourceLocation, const Token &, StringRef FilePath,77 bool, CharSourceRange Range,78 OptionalFileEntryRef File, StringRef, StringRef,79 const Module *, bool,80 SrcMgr::CharacteristicKind FileType) override {81 if (FileType != clang::SrcMgr::C_User)82 return;83 84 NextToEnter = Range.getBegin();85 86 if (!File)87 return;88 89 checkForDoubleInclude(&File->getFileEntry(),90 llvm::sys::path::filename(FilePath),91 Range.getBegin());92 }93 94 void checkForDoubleInclude(const FileEntry *File, StringRef FileName,95 SourceLocation Loc) {96 const auto It =97 llvm::find_if(llvm::reverse(Files),98 [&](const Include &Entry) { return Entry.File == File; });99 if (It == Files.rend())100 return;101 102 const StringRef FilePath = File->tryGetRealPathName();103 if (FilePath.empty() || isFileIgnored(FilePath))104 return;105 106 if (It == Files.rbegin()) {107 Check.diag(Loc, "direct self-inclusion of header file '%0'") << FileName;108 return;109 }110 111 Check.diag(Loc, "circular header file dependency detected while including "112 "'%0', please check the include path")113 << FileName;114 115 const bool IsIncludePathValid =116 std::all_of(Files.rbegin(), It + 1, [](const Include &Elem) {117 return !Elem.Name.empty() && Elem.Loc.isValid();118 });119 if (!IsIncludePathValid)120 return;121 122 for (const Include &I : llvm::make_range(Files.rbegin(), It + 1))123 Check.diag(I.Loc, "'%0' included from here", DiagnosticIDs::Note)124 << I.Name;125 }126 127 bool isFileIgnored(StringRef FileName) const {128 return llvm::any_of(IgnoredFilesRegexes, [&](const llvm::Regex &It) {129 return It.match(FileName);130 });131 }132 133#ifndef NDEBUG134 void EndOfMainFile() override {135 if (!Files.empty() &&136 Files.back().File == SM.getFileEntryForID(SM.getMainFileID()))137 Files.pop_back();138 139 assert(Files.empty());140 }141#endif142 143private:144 std::vector<Include> Files;145 SourceLocation NextToEnter;146 HeaderIncludeCycleCheck &Check;147 const SourceManager &SM;148 std::vector<llvm::Regex> IgnoredFilesRegexes;149};150 151} // namespace152 153HeaderIncludeCycleCheck::HeaderIncludeCycleCheck(StringRef Name,154 ClangTidyContext *Context)155 : ClangTidyCheck(Name, Context),156 IgnoredFilesList(utils::options::parseStringList(157 Options.get("IgnoredFilesList", ""))) {}158 159void HeaderIncludeCycleCheck::registerPPCallbacks(160 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {161 PP->addPPCallbacks(162 std::make_unique<CyclicDependencyCallbacks>(*this, SM, IgnoredFilesList));163}164 165void HeaderIncludeCycleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {166 Options.store(Opts, "IgnoredFilesList",167 utils::options::serializeStringList(IgnoredFilesList));168}169 170} // namespace clang::tidy::misc171