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