brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.7 KiB · 558c368 Raw
254 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 "IncludeCleanerCheck.h"10#include "../ClangTidyCheck.h"11#include "../ClangTidyDiagnosticConsumer.h"12#include "../ClangTidyOptions.h"13#include "../utils/OptionsUtils.h"14#include "clang-include-cleaner/Analysis.h"15#include "clang-include-cleaner/IncludeSpeller.h"16#include "clang-include-cleaner/Record.h"17#include "clang-include-cleaner/Types.h"18#include "clang/AST/ASTContext.h"19#include "clang/AST/Decl.h"20#include "clang/AST/DeclBase.h"21#include "clang/ASTMatchers/ASTMatchFinder.h"22#include "clang/ASTMatchers/ASTMatchers.h"23#include "clang/Basic/Diagnostic.h"24#include "clang/Basic/FileEntry.h"25#include "clang/Basic/LLVM.h"26#include "clang/Basic/LangOptions.h"27#include "clang/Basic/SourceLocation.h"28#include "clang/Format/Format.h"29#include "clang/Lex/Preprocessor.h"30#include "clang/Tooling/Core/Replacement.h"31#include "clang/Tooling/Inclusions/HeaderIncludes.h"32#include "clang/Tooling/Inclusions/StandardLibrary.h"33#include "llvm/ADT/DenseSet.h"34#include "llvm/ADT/STLExtras.h"35#include "llvm/ADT/SmallVector.h"36#include "llvm/ADT/StringRef.h"37#include "llvm/ADT/StringSet.h"38#include "llvm/Support/ErrorHandling.h"39#include "llvm/Support/Path.h"40#include "llvm/Support/Regex.h"41#include <optional>42#include <string>43#include <vector>44 45using namespace clang::ast_matchers;46 47namespace clang::tidy::misc {48 49namespace {50struct MissingIncludeInfo {51  include_cleaner::SymbolReference SymRef;52  include_cleaner::Header Missing;53};54} // namespace55 56IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,57                                         ClangTidyContext *Context)58    : ClangTidyCheck(Name, Context),59      IgnoreHeaders(60          utils::options::parseStringList(Options.get("IgnoreHeaders", ""))),61      DeduplicateFindings(Options.get("DeduplicateFindings", true)),62      UnusedIncludes(Options.get("UnusedIncludes", true)),63      MissingIncludes(Options.get("MissingIncludes", true)) {64  for (const auto &Header : IgnoreHeaders) {65    if (!llvm::Regex{Header}.isValid())66      configurationDiag("Invalid ignore headers regex '%0'") << Header;67    std::string HeaderSuffix{Header.str()};68    if (!Header.ends_with("$"))69      HeaderSuffix += "$";70    IgnoreHeadersRegex.emplace_back(HeaderSuffix);71  }72 73  if (UnusedIncludes == false && MissingIncludes == false)74    this->configurationDiag("The check 'misc-include-cleaner' will not "75                            "perform any analysis because 'UnusedIncludes' and "76                            "'MissingIncludes' are both false.");77}78 79void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {80  Options.store(Opts, "IgnoreHeaders",81                utils::options::serializeStringList(IgnoreHeaders));82  Options.store(Opts, "DeduplicateFindings", DeduplicateFindings);83  Options.store(Opts, "UnusedIncludes", UnusedIncludes);84  Options.store(Opts, "MissingIncludes", MissingIncludes);85}86 87bool IncludeCleanerCheck::isLanguageVersionSupported(88    const LangOptions &LangOpts) const {89  return !LangOpts.ObjC;90}91 92void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) {93  Finder->addMatcher(translationUnitDecl().bind("top"), this);94}95 96void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM,97                                              Preprocessor *PP,98                                              Preprocessor *ModuleExpanderPP) {99  PP->addPPCallbacks(RecordedPreprocessor.record(*PP));100  this->PP = PP;101  RecordedPI.record(*PP);102}103 104bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) {105  return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) {106    switch (H.kind()) {107    case include_cleaner::Header::Standard:108      // We don't trim angle brackets around standard library headers109      // deliberately, so that they are only matched as <vector>, otherwise110      // having just `.*/vector` might yield false positives.111      return R.match(H.standard().name());112    case include_cleaner::Header::Verbatim:113      return R.match(H.verbatim().trim("<>\""));114    case include_cleaner::Header::Physical:115      return R.match(H.physical().getFileEntry().tryGetRealPathName());116    }117    llvm_unreachable("Unknown Header kind.");118  });119}120 121void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) {122  const SourceManager *SM = Result.SourceManager;123  const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID());124  llvm::DenseSet<const include_cleaner::Include *> Used;125  std::vector<MissingIncludeInfo> Missing;126  llvm::SmallVector<Decl *> MainFileDecls;127  for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) {128    if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation())))129      continue;130    // FIXME: Filter out implicit template specializations.131    MainFileDecls.push_back(D);132  }133  llvm::DenseSet<include_cleaner::Symbol> SeenSymbols;134  OptionalDirectoryEntryRef ResourceDir =135      PP->getHeaderSearchInfo().getModuleMap().getBuiltinDir();136  // FIXME: Find a way to have less code duplication between include-cleaner137  // analysis implementation and the below code.138  walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI,139           *PP,140           [&](const include_cleaner::SymbolReference &Ref,141               llvm::ArrayRef<include_cleaner::Header> Providers) {142             // Process each symbol once to reduce noise in the findings.143             // Tidy checks are used in two different workflows:144             // - Ones that show all the findings for a given file. For such145             // workflows there is not much point in showing all the occurences,146             // as one is enough to indicate the issue.147             // - Ones that show only the findings on changed pieces. For such148             // workflows it's useful to show findings on every reference of a149             // symbol as otherwise tools might give incosistent results150             // depending on the parts of the file being edited. But it should151             // still help surface findings for "new violations" (i.e.152             // dependency did not exist in the code at all before).153             if (DeduplicateFindings && !SeenSymbols.insert(Ref.Target).second)154               return;155             bool Satisfied = false;156             for (const include_cleaner::Header &H : Providers) {157               if (H.kind() == include_cleaner::Header::Physical &&158                   (H.physical() == MainFile ||159                    H.physical().getDir() == ResourceDir)) {160                 Satisfied = true;161                 continue;162               }163 164               for (const include_cleaner::Include *I :165                    RecordedPreprocessor.Includes.match(H)) {166                 Used.insert(I);167                 Satisfied = true;168               }169             }170             if (!Satisfied && !Providers.empty() &&171                 Ref.RT == include_cleaner::RefType::Explicit &&172                 !shouldIgnore(Providers.front()))173               Missing.push_back({Ref, Providers.front()});174           });175 176  std::vector<const include_cleaner::Include *> Unused;177  for (const include_cleaner::Include &I :178       RecordedPreprocessor.Includes.all()) {179    if (Used.contains(&I) || !I.Resolved || I.Resolved->getDir() == ResourceDir)180      continue;181    if (RecordedPI.shouldKeep(*I.Resolved))182      continue;183    // Check if main file is the public interface for a private header. If so184    // we shouldn't diagnose it as unused.185    if (auto PHeader = RecordedPI.getPublic(*I.Resolved); !PHeader.empty()) {186      PHeader = PHeader.trim("<>\"");187      // Since most private -> public mappings happen in a verbatim way, we188      // check textually here. This might go wrong in presence of symlinks or189      // header mappings. But that's not different than rest of the places.190      if (getCurrentMainFile().ends_with(PHeader))191        continue;192    }193    auto StdHeader = tooling::stdlib::Header::named(194        I.quote(), PP->getLangOpts().CPlusPlus ? tooling::stdlib::Lang::CXX195                                               : tooling::stdlib::Lang::C);196    if (StdHeader && shouldIgnore(*StdHeader))197      continue;198    if (shouldIgnore(*I.Resolved))199      continue;200    Unused.push_back(&I);201  }202 203  const llvm::StringRef Code = SM->getBufferData(SM->getMainFileID());204  auto FileStyle =205      format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(),206                       format::DefaultFallbackStyle, Code,207                       &SM->getFileManager().getVirtualFileSystem());208  if (!FileStyle)209    FileStyle = format::getLLVMStyle();210 211  if (UnusedIncludes) {212    for (const auto *Inc : Unused) {213      diag(Inc->HashLocation, "included header %0 is not used directly")214          << llvm::sys::path::filename(Inc->Spelled,215                                       llvm::sys::path::Style::posix)216          << FixItHint::CreateRemoval(CharSourceRange::getCharRange(217                 SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),218                 SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));219    }220  }221 222  if (MissingIncludes) {223    const tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,224                                                 FileStyle->IncludeStyle);225    // Deduplicate insertions when running in bulk fix mode.226    llvm::StringSet<> InsertedHeaders{};227    for (const auto &Inc : Missing) {228      const std::string Spelling = include_cleaner::spellHeader(229          {Inc.Missing, PP->getHeaderSearchInfo(), MainFile});230      const bool Angled = llvm::StringRef{Spelling}.starts_with("<");231      // We might suggest insertion of an existing include in edge cases, e.g.,232      // include is present in a PP-disabled region, or spelling of the header233      // turns out to be the same as one of the unresolved includes in the234      // main file.235      if (auto Replacement = HeaderIncludes.insert(236              llvm::StringRef{Spelling}.trim("\"<>"), Angled,237              tooling::IncludeDirective::Include)) {238        const DiagnosticBuilder DB =239            diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),240                 "no header providing \"%0\" is directly included")241            << Inc.SymRef.Target.name();242        if (areDiagsSelfContained() ||243            InsertedHeaders.insert(Replacement->getReplacementText()).second) {244          DB << FixItHint::CreateInsertion(245              SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),246              Replacement->getReplacementText());247        }248      }249    }250  }251}252 253} // namespace clang::tidy::misc254