1c28506baSViktoriia Bakalova //===--- IncludeCleanerCheck.cpp - clang-tidy -----------------------------===// 2c28506baSViktoriia Bakalova // 3c28506baSViktoriia Bakalova // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4c28506baSViktoriia Bakalova // See https://llvm.org/LICENSE.txt for license information. 5c28506baSViktoriia Bakalova // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6c28506baSViktoriia Bakalova // 7c28506baSViktoriia Bakalova //===----------------------------------------------------------------------===// 8c28506baSViktoriia Bakalova 9c28506baSViktoriia Bakalova #include "IncludeCleanerCheck.h" 10c28506baSViktoriia Bakalova #include "../ClangTidyCheck.h" 11c28506baSViktoriia Bakalova #include "../ClangTidyDiagnosticConsumer.h" 12c28506baSViktoriia Bakalova #include "../ClangTidyOptions.h" 13c28506baSViktoriia Bakalova #include "../utils/OptionsUtils.h" 14c28506baSViktoriia Bakalova #include "clang-include-cleaner/Analysis.h" 1590c5fe98SViktoriia Bakalova #include "clang-include-cleaner/IncludeSpeller.h" 16c28506baSViktoriia Bakalova #include "clang-include-cleaner/Record.h" 17c28506baSViktoriia Bakalova #include "clang-include-cleaner/Types.h" 18c28506baSViktoriia Bakalova #include "clang/AST/ASTContext.h" 19c28506baSViktoriia Bakalova #include "clang/AST/Decl.h" 20c28506baSViktoriia Bakalova #include "clang/AST/DeclBase.h" 21c28506baSViktoriia Bakalova #include "clang/ASTMatchers/ASTMatchFinder.h" 22c28506baSViktoriia Bakalova #include "clang/ASTMatchers/ASTMatchers.h" 23c28506baSViktoriia Bakalova #include "clang/Basic/Diagnostic.h" 24c28506baSViktoriia Bakalova #include "clang/Basic/FileEntry.h" 25c28506baSViktoriia Bakalova #include "clang/Basic/LLVM.h" 26c28506baSViktoriia Bakalova #include "clang/Basic/LangOptions.h" 27c28506baSViktoriia Bakalova #include "clang/Basic/SourceLocation.h" 28c28506baSViktoriia Bakalova #include "clang/Format/Format.h" 2943c20367SViktoriia Bakalova #include "clang/Lex/HeaderSearchOptions.h" 30c28506baSViktoriia Bakalova #include "clang/Lex/Preprocessor.h" 31c28506baSViktoriia Bakalova #include "clang/Tooling/Core/Replacement.h" 32c28506baSViktoriia Bakalova #include "clang/Tooling/Inclusions/HeaderIncludes.h" 3377feba5aSkadir çetinkaya #include "clang/Tooling/Inclusions/StandardLibrary.h" 34c28506baSViktoriia Bakalova #include "llvm/ADT/DenseSet.h" 35c28506baSViktoriia Bakalova #include "llvm/ADT/STLExtras.h" 36c28506baSViktoriia Bakalova #include "llvm/ADT/SmallVector.h" 37c28506baSViktoriia Bakalova #include "llvm/ADT/StringRef.h" 38b0831c39SCongcong Cai #include "llvm/ADT/StringSet.h" 39c28506baSViktoriia Bakalova #include "llvm/Support/ErrorHandling.h" 402444fb96SHaojian Wu #include "llvm/Support/Path.h" 41c28506baSViktoriia Bakalova #include "llvm/Support/Regex.h" 42c28506baSViktoriia Bakalova #include <optional> 43c28506baSViktoriia Bakalova #include <string> 44c28506baSViktoriia Bakalova #include <vector> 45c28506baSViktoriia Bakalova 46c28506baSViktoriia Bakalova using namespace clang::ast_matchers; 47c28506baSViktoriia Bakalova 48c28506baSViktoriia Bakalova namespace clang::tidy::misc { 49c28506baSViktoriia Bakalova 50c28506baSViktoriia Bakalova namespace { 51c28506baSViktoriia Bakalova struct MissingIncludeInfo { 52eed4a4d0SHaojian Wu include_cleaner::SymbolReference SymRef; 53c28506baSViktoriia Bakalova include_cleaner::Header Missing; 54c28506baSViktoriia Bakalova }; 55c28506baSViktoriia Bakalova } // namespace 56c28506baSViktoriia Bakalova 57c28506baSViktoriia Bakalova IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name, 58c28506baSViktoriia Bakalova ClangTidyContext *Context) 59c28506baSViktoriia Bakalova : ClangTidyCheck(Name, Context), 60*222dd235SCongcong Cai IgnoreHeaders( 61*222dd235SCongcong Cai utils::options::parseStringList(Options.get("IgnoreHeaders", ""))), 62*222dd235SCongcong Cai DeduplicateFindings(Options.get("DeduplicateFindings", true)) { 63c28506baSViktoriia Bakalova for (const auto &Header : IgnoreHeaders) { 64c28506baSViktoriia Bakalova if (!llvm::Regex{Header}.isValid()) 65c28506baSViktoriia Bakalova configurationDiag("Invalid ignore headers regex '%0'") << Header; 66c28506baSViktoriia Bakalova std::string HeaderSuffix{Header.str()}; 67c28506baSViktoriia Bakalova if (!Header.ends_with("$")) 68c28506baSViktoriia Bakalova HeaderSuffix += "$"; 69c28506baSViktoriia Bakalova IgnoreHeadersRegex.emplace_back(HeaderSuffix); 70c28506baSViktoriia Bakalova } 71c28506baSViktoriia Bakalova } 72c28506baSViktoriia Bakalova 73c28506baSViktoriia Bakalova void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 74c28506baSViktoriia Bakalova Options.store(Opts, "IgnoreHeaders", 75c28506baSViktoriia Bakalova utils::options::serializeStringList(IgnoreHeaders)); 7689d0a76bSKadir Cetinkaya Options.store(Opts, "DeduplicateFindings", DeduplicateFindings); 77c28506baSViktoriia Bakalova } 78c28506baSViktoriia Bakalova 79c28506baSViktoriia Bakalova bool IncludeCleanerCheck::isLanguageVersionSupported( 80c28506baSViktoriia Bakalova const LangOptions &LangOpts) const { 81c28506baSViktoriia Bakalova return !LangOpts.ObjC; 82c28506baSViktoriia Bakalova } 83c28506baSViktoriia Bakalova 84c28506baSViktoriia Bakalova void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) { 85c28506baSViktoriia Bakalova Finder->addMatcher(translationUnitDecl().bind("top"), this); 86c28506baSViktoriia Bakalova } 87c28506baSViktoriia Bakalova 88c28506baSViktoriia Bakalova void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM, 89c28506baSViktoriia Bakalova Preprocessor *PP, 90c28506baSViktoriia Bakalova Preprocessor *ModuleExpanderPP) { 91c28506baSViktoriia Bakalova PP->addPPCallbacks(RecordedPreprocessor.record(*PP)); 9290ecaddeSKadir Cetinkaya this->PP = PP; 93c28506baSViktoriia Bakalova RecordedPI.record(*PP); 94c28506baSViktoriia Bakalova } 95c28506baSViktoriia Bakalova 96c28506baSViktoriia Bakalova bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) { 97c28506baSViktoriia Bakalova return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) { 98c28506baSViktoriia Bakalova switch (H.kind()) { 99c28506baSViktoriia Bakalova case include_cleaner::Header::Standard: 10077feba5aSkadir çetinkaya // We don't trim angle brackets around standard library headers 10177feba5aSkadir çetinkaya // deliberately, so that they are only matched as <vector>, otherwise 10277feba5aSkadir çetinkaya // having just `.*/vector` might yield false positives. 103c28506baSViktoriia Bakalova return R.match(H.standard().name()); 104c28506baSViktoriia Bakalova case include_cleaner::Header::Verbatim: 10577feba5aSkadir çetinkaya return R.match(H.verbatim().trim("<>\"")); 106c28506baSViktoriia Bakalova case include_cleaner::Header::Physical: 10798e6deb6SJan Svoboda return R.match(H.physical().getFileEntry().tryGetRealPathName()); 108c28506baSViktoriia Bakalova } 109c28506baSViktoriia Bakalova llvm_unreachable("Unknown Header kind."); 110c28506baSViktoriia Bakalova }); 111c28506baSViktoriia Bakalova } 112c28506baSViktoriia Bakalova 113c28506baSViktoriia Bakalova void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) { 114c28506baSViktoriia Bakalova const SourceManager *SM = Result.SourceManager; 115c28506baSViktoriia Bakalova const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID()); 116c28506baSViktoriia Bakalova llvm::DenseSet<const include_cleaner::Include *> Used; 117c28506baSViktoriia Bakalova std::vector<MissingIncludeInfo> Missing; 118c28506baSViktoriia Bakalova llvm::SmallVector<Decl *> MainFileDecls; 119c28506baSViktoriia Bakalova for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) { 120c28506baSViktoriia Bakalova if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation()))) 121c28506baSViktoriia Bakalova continue; 122c28506baSViktoriia Bakalova // FIXME: Filter out implicit template specializations. 123c28506baSViktoriia Bakalova MainFileDecls.push_back(D); 124c28506baSViktoriia Bakalova } 12589d0a76bSKadir Cetinkaya llvm::DenseSet<include_cleaner::Symbol> SeenSymbols; 126cb92511cSJan Svoboda OptionalDirectoryEntryRef ResourceDir = 12743c20367SViktoriia Bakalova PP->getHeaderSearchInfo().getModuleMap().getBuiltinDir(); 128c28506baSViktoriia Bakalova // FIXME: Find a way to have less code duplication between include-cleaner 129c28506baSViktoriia Bakalova // analysis implementation and the below code. 130c28506baSViktoriia Bakalova walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI, 13190ecaddeSKadir Cetinkaya *PP, 132c28506baSViktoriia Bakalova [&](const include_cleaner::SymbolReference &Ref, 133c28506baSViktoriia Bakalova llvm::ArrayRef<include_cleaner::Header> Providers) { 13489d0a76bSKadir Cetinkaya // Process each symbol once to reduce noise in the findings. 13589d0a76bSKadir Cetinkaya // Tidy checks are used in two different workflows: 13689d0a76bSKadir Cetinkaya // - Ones that show all the findings for a given file. For such 13789d0a76bSKadir Cetinkaya // workflows there is not much point in showing all the occurences, 13889d0a76bSKadir Cetinkaya // as one is enough to indicate the issue. 13989d0a76bSKadir Cetinkaya // - Ones that show only the findings on changed pieces. For such 14089d0a76bSKadir Cetinkaya // workflows it's useful to show findings on every reference of a 14189d0a76bSKadir Cetinkaya // symbol as otherwise tools might give incosistent results 14289d0a76bSKadir Cetinkaya // depending on the parts of the file being edited. But it should 14389d0a76bSKadir Cetinkaya // still help surface findings for "new violations" (i.e. 14489d0a76bSKadir Cetinkaya // dependency did not exist in the code at all before). 14589d0a76bSKadir Cetinkaya if (DeduplicateFindings && !SeenSymbols.insert(Ref.Target).second) 14689d0a76bSKadir Cetinkaya return; 147c28506baSViktoriia Bakalova bool Satisfied = false; 148c28506baSViktoriia Bakalova for (const include_cleaner::Header &H : Providers) { 149c28506baSViktoriia Bakalova if (H.kind() == include_cleaner::Header::Physical && 15043c20367SViktoriia Bakalova (H.physical() == MainFile || 15198e6deb6SJan Svoboda H.physical().getDir() == ResourceDir)) { 152c28506baSViktoriia Bakalova Satisfied = true; 15343c20367SViktoriia Bakalova continue; 15443c20367SViktoriia Bakalova } 155c28506baSViktoriia Bakalova 156c28506baSViktoriia Bakalova for (const include_cleaner::Include *I : 157c28506baSViktoriia Bakalova RecordedPreprocessor.Includes.match(H)) { 158c28506baSViktoriia Bakalova Used.insert(I); 159c28506baSViktoriia Bakalova Satisfied = true; 160c28506baSViktoriia Bakalova } 161c28506baSViktoriia Bakalova } 162c28506baSViktoriia Bakalova if (!Satisfied && !Providers.empty() && 163c28506baSViktoriia Bakalova Ref.RT == include_cleaner::RefType::Explicit && 164c28506baSViktoriia Bakalova !shouldIgnore(Providers.front())) 165eed4a4d0SHaojian Wu Missing.push_back({Ref, Providers.front()}); 166c28506baSViktoriia Bakalova }); 167c28506baSViktoriia Bakalova 168c28506baSViktoriia Bakalova std::vector<const include_cleaner::Include *> Unused; 169c28506baSViktoriia Bakalova for (const include_cleaner::Include &I : 170c28506baSViktoriia Bakalova RecordedPreprocessor.Includes.all()) { 17143c20367SViktoriia Bakalova if (Used.contains(&I) || !I.Resolved || I.Resolved->getDir() == ResourceDir) 172c28506baSViktoriia Bakalova continue; 17343974333SKadir Cetinkaya if (RecordedPI.shouldKeep(*I.Resolved)) 174c28506baSViktoriia Bakalova continue; 175c28506baSViktoriia Bakalova // Check if main file is the public interface for a private header. If so 176c28506baSViktoriia Bakalova // we shouldn't diagnose it as unused. 177f6307b26SSam McCall if (auto PHeader = RecordedPI.getPublic(*I.Resolved); !PHeader.empty()) { 178c28506baSViktoriia Bakalova PHeader = PHeader.trim("<>\""); 179c28506baSViktoriia Bakalova // Since most private -> public mappings happen in a verbatim way, we 180c28506baSViktoriia Bakalova // check textually here. This might go wrong in presence of symlinks or 181c28506baSViktoriia Bakalova // header mappings. But that's not different than rest of the places. 18276bbbcb4SKazu Hirata if (getCurrentMainFile().ends_with(PHeader)) 183c28506baSViktoriia Bakalova continue; 184c28506baSViktoriia Bakalova } 18577feba5aSkadir çetinkaya auto StdHeader = tooling::stdlib::Header::named( 18677feba5aSkadir çetinkaya I.quote(), PP->getLangOpts().CPlusPlus ? tooling::stdlib::Lang::CXX 18777feba5aSkadir çetinkaya : tooling::stdlib::Lang::C); 18877feba5aSkadir çetinkaya if (StdHeader && shouldIgnore(*StdHeader)) 18977feba5aSkadir çetinkaya continue; 19077feba5aSkadir çetinkaya if (shouldIgnore(*I.Resolved)) 19177feba5aSkadir çetinkaya continue; 192c28506baSViktoriia Bakalova Unused.push_back(&I); 193c28506baSViktoriia Bakalova } 194c28506baSViktoriia Bakalova 195c28506baSViktoriia Bakalova llvm::StringRef Code = SM->getBufferData(SM->getMainFileID()); 196c28506baSViktoriia Bakalova auto FileStyle = 197c28506baSViktoriia Bakalova format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(), 198c28506baSViktoriia Bakalova format::DefaultFallbackStyle, Code, 199c28506baSViktoriia Bakalova &SM->getFileManager().getVirtualFileSystem()); 200c28506baSViktoriia Bakalova if (!FileStyle) 201c28506baSViktoriia Bakalova FileStyle = format::getLLVMStyle(); 202c28506baSViktoriia Bakalova 203c28506baSViktoriia Bakalova for (const auto *Inc : Unused) { 204c28506baSViktoriia Bakalova diag(Inc->HashLocation, "included header %0 is not used directly") 2052444fb96SHaojian Wu << llvm::sys::path::filename(Inc->Spelled, 2062444fb96SHaojian Wu llvm::sys::path::Style::posix) 207c28506baSViktoriia Bakalova << FixItHint::CreateRemoval(CharSourceRange::getCharRange( 208c28506baSViktoriia Bakalova SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1), 209c28506baSViktoriia Bakalova SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1))); 210c28506baSViktoriia Bakalova } 211c28506baSViktoriia Bakalova 212c28506baSViktoriia Bakalova tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code, 213c28506baSViktoriia Bakalova FileStyle->IncludeStyle); 214b0831c39SCongcong Cai // Deduplicate insertions when running in bulk fix mode. 215b0831c39SCongcong Cai llvm::StringSet<> InsertedHeaders{}; 216c28506baSViktoriia Bakalova for (const auto &Inc : Missing) { 21790ecaddeSKadir Cetinkaya std::string Spelling = include_cleaner::spellHeader( 21890ecaddeSKadir Cetinkaya {Inc.Missing, PP->getHeaderSearchInfo(), MainFile}); 219c28506baSViktoriia Bakalova bool Angled = llvm::StringRef{Spelling}.starts_with("<"); 220c28506baSViktoriia Bakalova // We might suggest insertion of an existing include in edge cases, e.g., 221c28506baSViktoriia Bakalova // include is present in a PP-disabled region, or spelling of the header 222c28506baSViktoriia Bakalova // turns out to be the same as one of the unresolved includes in the 223c28506baSViktoriia Bakalova // main file. 224c28506baSViktoriia Bakalova if (auto Replacement = 225c28506baSViktoriia Bakalova HeaderIncludes.insert(llvm::StringRef{Spelling}.trim("\"<>"), 226b0831c39SCongcong Cai Angled, tooling::IncludeDirective::Include)) { 227b0831c39SCongcong Cai DiagnosticBuilder DB = 228eed4a4d0SHaojian Wu diag(SM->getSpellingLoc(Inc.SymRef.RefLocation), 229eed4a4d0SHaojian Wu "no header providing \"%0\" is directly included") 230b0831c39SCongcong Cai << Inc.SymRef.Target.name(); 231b0831c39SCongcong Cai if (areDiagsSelfContained() || 232b0831c39SCongcong Cai InsertedHeaders.insert(Replacement->getReplacementText()).second) { 233b0831c39SCongcong Cai DB << FixItHint::CreateInsertion( 234b0831c39SCongcong Cai SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()), 235c28506baSViktoriia Bakalova Replacement->getReplacementText()); 236c28506baSViktoriia Bakalova } 237c28506baSViktoriia Bakalova } 238b0831c39SCongcong Cai } 239b0831c39SCongcong Cai } 240c28506baSViktoriia Bakalova 241c28506baSViktoriia Bakalova } // namespace clang::tidy::misc 242