brintos

brintos / llvm-project-archived public Read only

0
0
Text · 5.2 KiB · a0e7ac1 Raw
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