diff options
author | Viktoriia Bakalova <bakalova@google.com> | 2023-04-19 07:16:51 +0000 |
---|---|---|
committer | Viktoriia Bakalova <bakalova@google.com> | 2023-06-02 15:21:20 +0000 |
commit | c28506ba4b6961950849f8fdecd0cf7e503a14f9 (patch) | |
tree | 1bb908df2988925a524f64954b6180096f2c1cc8 /clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp | |
parent | f9753ef1893c3d953a0244e7935d3997499b079a (diff) | |
download | llvm-c28506ba4b6961950849f8fdecd0cf7e503a14f9.zip llvm-c28506ba4b6961950849f8fdecd0cf7e503a14f9.tar.gz llvm-c28506ba4b6961950849f8fdecd0cf7e503a14f9.tar.bz2 |
[clang-tidy] Implement an include-cleaner check.
Differential Revision: https://reviews.llvm.org/D148793
Diffstat (limited to 'clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp new file mode 100644 index 0000000..c7aca83 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp @@ -0,0 +1,202 @@ +//===--- IncludeCleanerCheck.cpp - clang-tidy -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "IncludeCleanerCheck.h" +#include "../ClangTidyCheck.h" +#include "../ClangTidyDiagnosticConsumer.h" +#include "../ClangTidyOptions.h" +#include "../utils/OptionsUtils.h" +#include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Record.h" +#include "clang-include-cleaner/Types.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileEntry.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Format/Format.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Inclusions/HeaderIncludes.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Regex.h" +#include <optional> +#include <string> +#include <vector> + +using namespace clang::ast_matchers; + +namespace clang::tidy::misc { + +namespace { +struct MissingIncludeInfo { + SourceLocation SymRefLocation; + include_cleaner::Header Missing; +}; +} // namespace + +IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreHeaders(utils::options::parseStringList( + Options.getLocalOrGlobal("IgnoreHeaders", ""))) { + for (const auto &Header : IgnoreHeaders) { + if (!llvm::Regex{Header}.isValid()) + configurationDiag("Invalid ignore headers regex '%0'") << Header; + std::string HeaderSuffix{Header.str()}; + if (!Header.ends_with("$")) + HeaderSuffix += "$"; + IgnoreHeadersRegex.emplace_back(HeaderSuffix); + } +} + +void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreHeaders", + utils::options::serializeStringList(IgnoreHeaders)); +} + +bool IncludeCleanerCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return !LangOpts.ObjC; +} + +void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(translationUnitDecl().bind("top"), this); +} + +void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + PP->addPPCallbacks(RecordedPreprocessor.record(*PP)); + HS = &PP->getHeaderSearchInfo(); + RecordedPI.record(*PP); +} + +bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) { + return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) { + switch (H.kind()) { + case include_cleaner::Header::Standard: + return R.match(H.standard().name()); + case include_cleaner::Header::Verbatim: + return R.match(H.verbatim()); + case include_cleaner::Header::Physical: + return R.match(H.physical()->tryGetRealPathName()); + } + llvm_unreachable("Unknown Header kind."); + }); +} + +void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager *SM = Result.SourceManager; + const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID()); + llvm::DenseSet<const include_cleaner::Include *> Used; + std::vector<MissingIncludeInfo> Missing; + llvm::SmallVector<Decl *> MainFileDecls; + for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) { + if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation()))) + continue; + // FIXME: Filter out implicit template specializations. + MainFileDecls.push_back(D); + } + // FIXME: Find a way to have less code duplication between include-cleaner + // analysis implementation and the below code. + walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI, + *SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef<include_cleaner::Header> Providers) { + bool Satisfied = false; + for (const include_cleaner::Header &H : Providers) { + if (H.kind() == include_cleaner::Header::Physical && + H.physical() == MainFile) + Satisfied = true; + + for (const include_cleaner::Include *I : + RecordedPreprocessor.Includes.match(H)) { + Used.insert(I); + Satisfied = true; + } + } + if (!Satisfied && !Providers.empty() && + Ref.RT == include_cleaner::RefType::Explicit && + !shouldIgnore(Providers.front())) + Missing.push_back({Ref.RefLocation, Providers.front()}); + }); + + std::vector<const include_cleaner::Include *> Unused; + for (const include_cleaner::Include &I : + RecordedPreprocessor.Includes.all()) { + if (Used.contains(&I) || !I.Resolved) + continue; + if (RecordedPI.shouldKeep(I.Line)) + continue; + // Check if main file is the public interface for a private header. If so + // we shouldn't diagnose it as unused. + if (auto PHeader = RecordedPI.getPublic(I.Resolved); !PHeader.empty()) { + PHeader = PHeader.trim("<>\""); + // Since most private -> public mappings happen in a verbatim way, we + // check textually here. This might go wrong in presence of symlinks or + // header mappings. But that's not different than rest of the places. + if (getCurrentMainFile().endswith(PHeader)) + continue; + } + + if (llvm::none_of(IgnoreHeadersRegex, + [Resolved = I.Resolved->tryGetRealPathName()]( + const llvm::Regex &R) { return R.match(Resolved); })) + Unused.push_back(&I); + } + + llvm::StringRef Code = SM->getBufferData(SM->getMainFileID()); + auto FileStyle = + format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(), + format::DefaultFallbackStyle, Code, + &SM->getFileManager().getVirtualFileSystem()); + if (!FileStyle) + FileStyle = format::getLLVMStyle(); + + for (const auto *Inc : Unused) { + diag(Inc->HashLocation, "included header %0 is not used directly") + << Inc->quote() + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1), + SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1))); + } + + tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code, + FileStyle->IncludeStyle); + for (const auto &Inc : Missing) { + std::string Spelling = + include_cleaner::spellHeader(Inc.Missing, *HS, MainFile); + bool Angled = llvm::StringRef{Spelling}.starts_with("<"); + // We might suggest insertion of an existing include in edge cases, e.g., + // include is present in a PP-disabled region, or spelling of the header + // turns out to be the same as one of the unresolved includes in the + // main file. + if (auto Replacement = + HeaderIncludes.insert(llvm::StringRef{Spelling}.trim("\"<>"), + Angled, tooling::IncludeDirective::Include)) + diag(SM->getSpellingLoc(Inc.SymRefLocation), + "no header providing %0 is directly included") + << Spelling + << FixItHint::CreateInsertion( + SM->getComposedLoc(SM->getMainFileID(), + Replacement->getOffset()), + Replacement->getReplacementText()); + } +} + +} // namespace clang::tidy::misc |