1 //===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- C++ -*-===// 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 "IncludeCleaner.h" 10 #include "Config.h" 11 #include "Headers.h" 12 #include "ParsedAST.h" 13 #include "Protocol.h" 14 #include "SourceCode.h" 15 #include "index/CanonicalIncludes.h" 16 #include "support/Logger.h" 17 #include "support/Trace.h" 18 #include "clang/AST/ASTContext.h" 19 #include "clang/AST/ExprCXX.h" 20 #include "clang/AST/RecursiveASTVisitor.h" 21 #include "clang/Basic/SourceLocation.h" 22 #include "clang/Basic/SourceManager.h" 23 #include "clang/Lex/HeaderSearch.h" 24 #include "clang/Lex/Preprocessor.h" 25 #include "clang/Tooling/Syntax/Tokens.h" 26 #include "llvm/ADT/STLFunctionalExtras.h" 27 #include "llvm/ADT/StringSet.h" 28 #include "llvm/Support/FormatVariadic.h" 29 #include "llvm/Support/Path.h" 30 31 namespace clang { 32 namespace clangd { 33 34 static bool AnalyzeStdlib = false; 35 void setIncludeCleanerAnalyzesStdlib(bool B) { AnalyzeStdlib = B; } 36 37 namespace { 38 39 /// Crawler traverses the AST and feeds in the locations of (sometimes 40 /// implicitly) used symbols into \p Result. 41 class ReferencedLocationCrawler 42 : public RecursiveASTVisitor<ReferencedLocationCrawler> { 43 public: 44 ReferencedLocationCrawler(ReferencedLocations &Result, 45 const SourceManager &SM) 46 : Result(Result), SM(SM) {} 47 48 bool VisitDeclRefExpr(DeclRefExpr *DRE) { 49 add(DRE->getDecl()); 50 add(DRE->getFoundDecl()); 51 return true; 52 } 53 54 bool VisitMemberExpr(MemberExpr *ME) { 55 add(ME->getMemberDecl()); 56 add(ME->getFoundDecl().getDecl()); 57 return true; 58 } 59 60 bool VisitTagType(TagType *TT) { 61 add(TT->getDecl()); 62 return true; 63 } 64 65 bool VisitFunctionDecl(FunctionDecl *FD) { 66 // Function definition will require redeclarations to be included. 67 if (FD->isThisDeclarationADefinition()) 68 add(FD); 69 return true; 70 } 71 72 bool VisitCXXConstructExpr(CXXConstructExpr *CCE) { 73 add(CCE->getConstructor()); 74 return true; 75 } 76 77 bool VisitTemplateSpecializationType(TemplateSpecializationType *TST) { 78 add(TST->getTemplateName().getAsTemplateDecl()); // Primary template. 79 add(TST->getAsCXXRecordDecl()); // Specialization 80 return true; 81 } 82 83 bool VisitUsingType(UsingType *UT) { 84 add(UT->getFoundDecl()); 85 return true; 86 } 87 88 bool VisitTypedefType(TypedefType *TT) { 89 add(TT->getDecl()); 90 return true; 91 } 92 93 // Consider types of any subexpression used, even if the type is not named. 94 // This is helpful in getFoo().bar(), where Foo must be complete. 95 // FIXME(kirillbobyrev): Should we tweak this? It may not be desirable to 96 // consider types "used" when they are not directly spelled in code. 97 bool VisitExpr(Expr *E) { 98 TraverseType(E->getType()); 99 return true; 100 } 101 102 bool TraverseType(QualType T) { 103 if (isNew(T.getTypePtrOrNull())) // don't care about quals 104 Base::TraverseType(T); 105 return true; 106 } 107 108 bool VisitUsingDecl(UsingDecl *D) { 109 for (const auto *Shadow : D->shadows()) 110 add(Shadow->getTargetDecl()); 111 return true; 112 } 113 114 // Enums may be usefully forward-declared as *complete* types by specifying 115 // an underlying type. In this case, the definition should see the declaration 116 // so they can be checked for compatibility. 117 bool VisitEnumDecl(EnumDecl *D) { 118 if (D->isThisDeclarationADefinition() && D->getIntegerTypeSourceInfo()) 119 add(D); 120 return true; 121 } 122 123 // When the overload is not resolved yet, mark all candidates as used. 124 bool VisitOverloadExpr(OverloadExpr *E) { 125 for (const auto *ResolutionDecl : E->decls()) 126 add(ResolutionDecl); 127 return true; 128 } 129 130 private: 131 using Base = RecursiveASTVisitor<ReferencedLocationCrawler>; 132 133 void add(const Decl *D) { 134 if (!D || !isNew(D->getCanonicalDecl())) 135 return; 136 if (auto SS = StdRecognizer(D)) { 137 Result.Stdlib.insert(*SS); 138 return; 139 } 140 // Special case RecordDecls, as it is common for them to be forward 141 // declared multiple times. The most common cases are: 142 // - Definition available in TU, only mark that one as usage. The rest is 143 // likely to be unnecessary. This might result in false positives when an 144 // internal definition is visible. 145 // - There's a forward declaration in the main file, no need for other 146 // redecls. 147 if (const auto *RD = llvm::dyn_cast<RecordDecl>(D)) { 148 if (const auto *Definition = RD->getDefinition()) { 149 Result.User.insert(Definition->getLocation()); 150 return; 151 } 152 if (SM.isInMainFile(RD->getMostRecentDecl()->getLocation())) 153 return; 154 } 155 for (const Decl *Redecl : D->redecls()) 156 Result.User.insert(Redecl->getLocation()); 157 } 158 159 bool isNew(const void *P) { return P && Visited.insert(P).second; } 160 161 ReferencedLocations &Result; 162 llvm::DenseSet<const void *> Visited; 163 const SourceManager &SM; 164 tooling::stdlib::Recognizer StdRecognizer; 165 }; 166 167 // Given a set of referenced FileIDs, determines all the potentially-referenced 168 // files and macros by traversing expansion/spelling locations of macro IDs. 169 // This is used to map the referenced SourceLocations onto real files. 170 struct ReferencedFilesBuilder { 171 ReferencedFilesBuilder(const SourceManager &SM) : SM(SM) {} 172 llvm::DenseSet<FileID> Files; 173 llvm::DenseSet<FileID> Macros; 174 const SourceManager &SM; 175 176 void add(SourceLocation Loc) { add(SM.getFileID(Loc), Loc); } 177 178 void add(FileID FID, SourceLocation Loc) { 179 if (FID.isInvalid()) 180 return; 181 assert(SM.isInFileID(Loc, FID)); 182 if (Loc.isFileID()) { 183 Files.insert(FID); 184 return; 185 } 186 // Don't process the same macro FID twice. 187 if (!Macros.insert(FID).second) 188 return; 189 const auto &Exp = SM.getSLocEntry(FID).getExpansion(); 190 add(Exp.getSpellingLoc()); 191 add(Exp.getExpansionLocStart()); 192 add(Exp.getExpansionLocEnd()); 193 } 194 }; 195 196 // Returns the range starting at '#' and ending at EOL. Escaped newlines are not 197 // handled. 198 clangd::Range getDiagnosticRange(llvm::StringRef Code, unsigned HashOffset) { 199 clangd::Range Result; 200 Result.end = Result.start = offsetToPosition(Code, HashOffset); 201 202 // Span the warning until the EOL or EOF. 203 Result.end.character += 204 lspLength(Code.drop_front(HashOffset).take_until([](char C) { 205 return C == '\n' || C == '\r'; 206 })); 207 return Result; 208 } 209 210 // Finds locations of macros referenced from within the main file. That includes 211 // references that were not yet expanded, e.g `BAR` in `#define FOO BAR`. 212 void findReferencedMacros(const SourceManager &SM, Preprocessor &PP, 213 const syntax::TokenBuffer *Tokens, 214 ReferencedLocations &Result) { 215 trace::Span Tracer("IncludeCleaner::findReferencedMacros"); 216 // FIXME(kirillbobyrev): The macros from the main file are collected in 217 // ParsedAST's MainFileMacros. However, we can't use it here because it 218 // doesn't handle macro references that were not expanded, e.g. in macro 219 // definitions or preprocessor-disabled sections. 220 // 221 // Extending MainFileMacros to collect missing references and switching to 222 // this mechanism (as opposed to iterating through all tokens) will improve 223 // the performance of findReferencedMacros and also improve other features 224 // relying on MainFileMacros. 225 for (const syntax::Token &Tok : Tokens->spelledTokens(SM.getMainFileID())) { 226 auto Macro = locateMacroAt(Tok, PP); 227 if (!Macro) 228 continue; 229 auto Loc = Macro->Info->getDefinitionLoc(); 230 if (Loc.isValid()) 231 Result.User.insert(Loc); 232 // FIXME: support stdlib macros 233 } 234 } 235 236 static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST) { 237 if (Inc.BehindPragmaKeep) 238 return false; 239 240 // FIXME(kirillbobyrev): We currently do not support the umbrella headers. 241 // System headers are likely to be standard library headers. 242 // Until we have good support for umbrella headers, don't warn about them. 243 if (Inc.Written.front() == '<') { 244 if (AnalyzeStdlib && tooling::stdlib::Header::named(Inc.Written)) 245 return true; 246 return false; 247 } 248 // Headers without include guards have side effects and are not 249 // self-contained, skip them. 250 assert(Inc.HeaderID); 251 auto FE = AST.getSourceManager().getFileManager().getFileRef( 252 AST.getIncludeStructure().getRealPath( 253 static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID))); 254 assert(FE); 255 if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded( 256 &FE->getFileEntry())) { 257 dlog("{0} doesn't have header guard and will not be considered unused", 258 FE->getName()); 259 return false; 260 } 261 return true; 262 } 263 264 // In case symbols are coming from non self-contained header, we need to find 265 // its first includer that is self-contained. This is the header users can 266 // include, so it will be responsible for bringing the symbols from given 267 // header into the scope. 268 FileID headerResponsible(FileID ID, const SourceManager &SM, 269 const IncludeStructure &Includes) { 270 // Unroll the chain of non self-contained headers until we find the one that 271 // can be included. 272 for (const FileEntry *FE = SM.getFileEntryForID(ID); ID != SM.getMainFileID(); 273 FE = SM.getFileEntryForID(ID)) { 274 // If FE is nullptr, we consider it to be the responsible header. 275 if (!FE) 276 break; 277 auto HID = Includes.getID(FE); 278 assert(HID && "We're iterating over headers already existing in " 279 "IncludeStructure"); 280 if (Includes.isSelfContained(*HID)) 281 break; 282 // The header is not self-contained: put the responsibility for its symbols 283 // on its includer. 284 ID = SM.getFileID(SM.getIncludeLoc(ID)); 285 } 286 return ID; 287 } 288 289 } // namespace 290 291 ReferencedLocations findReferencedLocations(ASTContext &Ctx, Preprocessor &PP, 292 const syntax::TokenBuffer *Tokens) { 293 trace::Span Tracer("IncludeCleaner::findReferencedLocations"); 294 ReferencedLocations Result; 295 const auto &SM = Ctx.getSourceManager(); 296 ReferencedLocationCrawler Crawler(Result, SM); 297 Crawler.TraverseAST(Ctx); 298 if (Tokens) 299 findReferencedMacros(SM, PP, Tokens, Result); 300 return Result; 301 } 302 303 ReferencedLocations findReferencedLocations(ParsedAST &AST) { 304 return findReferencedLocations(AST.getASTContext(), AST.getPreprocessor(), 305 &AST.getTokens()); 306 } 307 308 ReferencedFiles findReferencedFiles( 309 const ReferencedLocations &Locs, const SourceManager &SM, 310 llvm::function_ref<FileID(FileID)> HeaderResponsible, 311 llvm::function_ref<Optional<StringRef>(FileID)> UmbrellaHeader) { 312 std::vector<SourceLocation> Sorted{Locs.User.begin(), Locs.User.end()}; 313 llvm::sort(Sorted); // Group by FileID. 314 ReferencedFilesBuilder Builder(SM); 315 for (auto It = Sorted.begin(); It < Sorted.end();) { 316 FileID FID = SM.getFileID(*It); 317 Builder.add(FID, *It); 318 // Cheaply skip over all the other locations from the same FileID. 319 // This avoids lots of redundant Loc->File lookups for the same file. 320 do 321 ++It; 322 while (It != Sorted.end() && SM.isInFileID(*It, FID)); 323 } 324 325 // If a header is not self-contained, we consider its symbols a logical part 326 // of the including file. Therefore, mark the parents of all used 327 // non-self-contained FileIDs as used. Perform this on FileIDs rather than 328 // HeaderIDs, as each inclusion of a non-self-contained file is distinct. 329 llvm::DenseSet<FileID> UserFiles; 330 llvm::StringSet<> PublicHeaders; 331 for (FileID ID : Builder.Files) { 332 UserFiles.insert(HeaderResponsible(ID)); 333 if (auto PublicHeader = UmbrellaHeader(ID)) { 334 PublicHeaders.insert(*PublicHeader); 335 } 336 } 337 338 llvm::DenseSet<tooling::stdlib::Header> StdlibFiles; 339 for (const auto &Symbol : Locs.Stdlib) 340 for (const auto &Header : Symbol.headers()) 341 StdlibFiles.insert(Header); 342 343 return {std::move(UserFiles), std::move(StdlibFiles), 344 std::move(PublicHeaders)}; 345 } 346 347 ReferencedFiles findReferencedFiles(const ReferencedLocations &Locs, 348 const IncludeStructure &Includes, 349 const CanonicalIncludes &CanonIncludes, 350 const SourceManager &SM) { 351 return findReferencedFiles( 352 Locs, SM, 353 [&SM, &Includes](FileID ID) { 354 return headerResponsible(ID, SM, Includes); 355 }, 356 [&SM, &CanonIncludes](FileID ID) -> Optional<StringRef> { 357 auto Entry = SM.getFileEntryRefForID(ID); 358 if (!Entry) 359 return llvm::None; 360 auto PublicHeader = CanonIncludes.mapHeader(*Entry); 361 if (PublicHeader.empty()) 362 return llvm::None; 363 return PublicHeader; 364 }); 365 } 366 367 std::vector<const Inclusion *> 368 getUnused(ParsedAST &AST, 369 const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles, 370 const llvm::StringSet<> &ReferencedPublicHeaders) { 371 trace::Span Tracer("IncludeCleaner::getUnused"); 372 std::vector<const Inclusion *> Unused; 373 for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) { 374 if (!MFI.HeaderID) 375 continue; 376 if (ReferencedPublicHeaders.contains(MFI.Written)) 377 continue; 378 auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID); 379 bool Used = ReferencedFiles.contains(IncludeID); 380 if (!Used && !mayConsiderUnused(MFI, AST)) { 381 dlog("{0} was not used, but is not eligible to be diagnosed as unused", 382 MFI.Written); 383 continue; 384 } 385 if (!Used) 386 Unused.push_back(&MFI); 387 dlog("{0} is {1}", MFI.Written, Used ? "USED" : "UNUSED"); 388 } 389 return Unused; 390 } 391 392 #ifndef NDEBUG 393 // Is FID a <built-in>, <scratch space> etc? 394 static bool isSpecialBuffer(FileID FID, const SourceManager &SM) { 395 const SrcMgr::FileInfo &FI = SM.getSLocEntry(FID).getFile(); 396 return FI.getName().startswith("<"); 397 } 398 #endif 399 400 llvm::DenseSet<IncludeStructure::HeaderID> 401 translateToHeaderIDs(const ReferencedFiles &Files, 402 const IncludeStructure &Includes, 403 const SourceManager &SM) { 404 trace::Span Tracer("IncludeCleaner::translateToHeaderIDs"); 405 llvm::DenseSet<IncludeStructure::HeaderID> TranslatedHeaderIDs; 406 TranslatedHeaderIDs.reserve(Files.User.size()); 407 for (FileID FID : Files.User) { 408 const FileEntry *FE = SM.getFileEntryForID(FID); 409 if (!FE) { 410 assert(isSpecialBuffer(FID, SM)); 411 continue; 412 } 413 const auto File = Includes.getID(FE); 414 assert(File); 415 TranslatedHeaderIDs.insert(*File); 416 } 417 for (tooling::stdlib::Header StdlibUsed : Files.Stdlib) 418 for (auto HID : Includes.StdlibHeaders.lookup(StdlibUsed)) 419 TranslatedHeaderIDs.insert(HID); 420 return TranslatedHeaderIDs; 421 } 422 423 std::vector<const Inclusion *> computeUnusedIncludes(ParsedAST &AST) { 424 const auto &SM = AST.getSourceManager(); 425 426 auto Refs = findReferencedLocations(AST); 427 auto ReferencedFiles = 428 findReferencedFiles(Refs, AST.getIncludeStructure(), 429 AST.getCanonicalIncludes(), AST.getSourceManager()); 430 auto ReferencedHeaders = 431 translateToHeaderIDs(ReferencedFiles, AST.getIncludeStructure(), SM); 432 return getUnused(AST, ReferencedHeaders, ReferencedFiles.SpelledUmbrellas); 433 } 434 435 std::vector<Diag> issueUnusedIncludesDiagnostics(ParsedAST &AST, 436 llvm::StringRef Code) { 437 const Config &Cfg = Config::current(); 438 if (Cfg.Diagnostics.UnusedIncludes != Config::UnusedIncludesPolicy::Strict || 439 Cfg.Diagnostics.SuppressAll || 440 Cfg.Diagnostics.Suppress.contains("unused-includes")) 441 return {}; 442 trace::Span Tracer("IncludeCleaner::issueUnusedIncludesDiagnostics"); 443 std::vector<Diag> Result; 444 std::string FileName = 445 AST.getSourceManager() 446 .getFileEntryRefForID(AST.getSourceManager().getMainFileID()) 447 ->getName() 448 .str(); 449 for (const auto *Inc : computeUnusedIncludes(AST)) { 450 Diag D; 451 D.Message = 452 llvm::formatv("included header {0} is not used", 453 llvm::sys::path::filename( 454 Inc->Written.substr(1, Inc->Written.size() - 2), 455 llvm::sys::path::Style::posix)); 456 D.Name = "unused-includes"; 457 D.Source = Diag::DiagSource::Clangd; 458 D.File = FileName; 459 D.Severity = DiagnosticsEngine::Warning; 460 D.Tags.push_back(Unnecessary); 461 D.Range = getDiagnosticRange(Code, Inc->HashOffset); 462 // FIXME(kirillbobyrev): Removing inclusion might break the code if the 463 // used headers are only reachable transitively through this one. Suggest 464 // including them directly instead. 465 // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas 466 // (keep/export) remove the warning once we support IWYU pragmas. 467 D.Fixes.emplace_back(); 468 D.Fixes.back().Message = "remove #include directive"; 469 D.Fixes.back().Edits.emplace_back(); 470 D.Fixes.back().Edits.back().range.start.line = Inc->HashLine; 471 D.Fixes.back().Edits.back().range.end.line = Inc->HashLine + 1; 472 D.InsideMainFile = true; 473 Result.push_back(std::move(D)); 474 } 475 return Result; 476 } 477 478 } // namespace clangd 479 } // namespace clang 480