1d1ec581eSKirill Bobyrev //===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- C++ -*-===// 2d1ec581eSKirill Bobyrev // 3d1ec581eSKirill Bobyrev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4d1ec581eSKirill Bobyrev // See https://llvm.org/LICENSE.txt for license information. 5d1ec581eSKirill Bobyrev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6d1ec581eSKirill Bobyrev // 7d1ec581eSKirill Bobyrev //===----------------------------------------------------------------------===// 8d1ec581eSKirill Bobyrev 9d1ec581eSKirill Bobyrev #include "IncludeCleaner.h" 102e82eb1fSViktoriia Bakalova #include "Diagnostics.h" 1134cc210aSKirill Bobyrev #include "Headers.h" 12f47564eaSKirill Bobyrev #include "ParsedAST.h" 13144562e6SKadir Cetinkaya #include "Preamble.h" 146d314ee5SKirill Bobyrev #include "Protocol.h" 156d314ee5SKirill Bobyrev #include "SourceCode.h" 16939dce12SHaojian Wu #include "clang-include-cleaner/Analysis.h" 1790c5fe98SViktoriia Bakalova #include "clang-include-cleaner/IncludeSpeller.h" 18be39daeaSHaojian Wu #include "clang-include-cleaner/Record.h" 19939dce12SHaojian Wu #include "clang-include-cleaner/Types.h" 20d1ec581eSKirill Bobyrev #include "support/Logger.h" 212e82eb1fSViktoriia Bakalova #include "support/Path.h" 22e3c6090eSKirill Bobyrev #include "support/Trace.h" 23089d9c50SKirill Bobyrev #include "clang/AST/ASTContext.h" 242e82eb1fSViktoriia Bakalova #include "clang/Basic/Diagnostic.h" 252e82eb1fSViktoriia Bakalova #include "clang/Basic/LLVM.h" 26d1ec581eSKirill Bobyrev #include "clang/Basic/SourceLocation.h" 2734cc210aSKirill Bobyrev #include "clang/Basic/SourceManager.h" 282e82eb1fSViktoriia Bakalova #include "clang/Format/Format.h" 29d97a3419SSam McCall #include "clang/Lex/DirectoryLookup.h" 30f47564eaSKirill Bobyrev #include "clang/Lex/HeaderSearch.h" 31f47564eaSKirill Bobyrev #include "clang/Lex/Preprocessor.h" 322e82eb1fSViktoriia Bakalova #include "clang/Tooling/Core/Replacement.h" 332e82eb1fSViktoriia Bakalova #include "clang/Tooling/Inclusions/HeaderIncludes.h" 342e82eb1fSViktoriia Bakalova #include "clang/Tooling/Inclusions/StandardLibrary.h" 35e3c6090eSKirill Bobyrev #include "clang/Tooling/Syntax/Tokens.h" 36bdf0b757SKirill Bobyrev #include "llvm/ADT/ArrayRef.h" 372e82eb1fSViktoriia Bakalova #include "llvm/ADT/DenseSet.h" 3808f0725aSKadir Cetinkaya #include "llvm/ADT/GenericUniformityImpl.h" 392e82eb1fSViktoriia Bakalova #include "llvm/ADT/STLExtras.h" 40bdf0b757SKirill Bobyrev #include "llvm/ADT/SmallString.h" 412e82eb1fSViktoriia Bakalova #include "llvm/ADT/SmallVector.h" 422e82eb1fSViktoriia Bakalova #include "llvm/ADT/StringRef.h" 432e82eb1fSViktoriia Bakalova #include "llvm/Support/Error.h" 442e82eb1fSViktoriia Bakalova #include "llvm/Support/ErrorHandling.h" 456d314ee5SKirill Bobyrev #include "llvm/Support/FormatVariadic.h" 466d314ee5SKirill Bobyrev #include "llvm/Support/Path.h" 47bdf0b757SKirill Bobyrev #include "llvm/Support/Regex.h" 48f3a815aaSKadir Cetinkaya #include <cassert> 492e82eb1fSViktoriia Bakalova #include <iterator> 50209694deSHaojian Wu #include <map> 5171f55735SKazu Hirata #include <optional> 522e82eb1fSViktoriia Bakalova #include <string> 53f3a815aaSKadir Cetinkaya #include <utility> 542e82eb1fSViktoriia Bakalova #include <vector> 55d1ec581eSKirill Bobyrev 56031ffc3eSKadir Cetinkaya namespace clang::clangd { 57d1ec581eSKirill Bobyrev namespace { 58d1ec581eSKirill Bobyrev 59031ffc3eSKadir Cetinkaya bool isIgnored(llvm::StringRef HeaderPath, HeaderFilter IgnoreHeaders) { 602e82eb1fSViktoriia Bakalova // Convert the path to Unix slashes and try to match against the filter. 612e82eb1fSViktoriia Bakalova llvm::SmallString<64> NormalizedPath(HeaderPath); 622e82eb1fSViktoriia Bakalova llvm::sys::path::native(NormalizedPath, llvm::sys::path::Style::posix); 63031ffc3eSKadir Cetinkaya for (auto &Filter : IgnoreHeaders) { 642e82eb1fSViktoriia Bakalova if (Filter(NormalizedPath)) 652e82eb1fSViktoriia Bakalova return true; 662e82eb1fSViktoriia Bakalova } 672e82eb1fSViktoriia Bakalova return false; 682e82eb1fSViktoriia Bakalova } 692e82eb1fSViktoriia Bakalova 70778a5e9bSKadir Cetinkaya bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, 718c5a7a1fSVadim D const include_cleaner::PragmaIncludes *PI, 728c5a7a1fSVadim D bool AnalyzeAngledIncludes) { 73778a5e9bSKadir Cetinkaya assert(Inc.HeaderID); 74778a5e9bSKadir Cetinkaya auto HID = static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID); 75778a5e9bSKadir Cetinkaya auto FE = AST.getSourceManager().getFileManager().getFileRef( 76778a5e9bSKadir Cetinkaya AST.getIncludeStructure().getRealPath(HID)); 77778a5e9bSKadir Cetinkaya assert(FE); 7843c20367SViktoriia Bakalova if (FE->getDir() == AST.getPreprocessor() 7943c20367SViktoriia Bakalova .getHeaderSearchInfo() 8043c20367SViktoriia Bakalova .getModuleMap() 8143c20367SViktoriia Bakalova .getBuiltinDir()) 8243c20367SViktoriia Bakalova return false; 8343974333SKadir Cetinkaya if (PI && PI->shouldKeep(*FE)) 84dcb28244SHaojian Wu return false; 85478863efSSam McCall // FIXME(kirillbobyrev): We currently do not support the umbrella headers. 86478863efSSam McCall // System headers are likely to be standard library headers. 878c5a7a1fSVadim D // Until we have good support for umbrella headers, don't warn about them 888c5a7a1fSVadim D // (unless analysis is explicitly enabled). 898c5a7a1fSVadim D if (Inc.Written.front() == '<') { 908c5a7a1fSVadim D if (tooling::stdlib::Header::named(Inc.Written)) 918c5a7a1fSVadim D return true; 928c5a7a1fSVadim D if (!AnalyzeAngledIncludes) 938c5a7a1fSVadim D return false; 948c5a7a1fSVadim D } 9543fcfdb1SKadir Cetinkaya if (PI) { 9643fcfdb1SKadir Cetinkaya // Check if main file is the public interface for a private header. If so we 9743fcfdb1SKadir Cetinkaya // shouldn't diagnose it as unused. 9843fcfdb1SKadir Cetinkaya if (auto PHeader = PI->getPublic(*FE); !PHeader.empty()) { 9943fcfdb1SKadir Cetinkaya PHeader = PHeader.trim("<>\""); 10043fcfdb1SKadir Cetinkaya // Since most private -> public mappings happen in a verbatim way, we 10143fcfdb1SKadir Cetinkaya // check textually here. This might go wrong in presence of symlinks or 10243fcfdb1SKadir Cetinkaya // header mappings. But that's not different than rest of the places. 103d5953e3eSKazu Hirata if (AST.tuPath().ends_with(PHeader)) 10443fcfdb1SKadir Cetinkaya return false; 10543fcfdb1SKadir Cetinkaya } 10643fcfdb1SKadir Cetinkaya } 107f47564eaSKirill Bobyrev // Headers without include guards have side effects and are not 108f47564eaSKirill Bobyrev // self-contained, skip them. 109f47564eaSKirill Bobyrev if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded( 110b0abc9ddSJan Svoboda *FE)) { 111f47564eaSKirill Bobyrev dlog("{0} doesn't have header guard and will not be considered unused", 1126f3f1e98SSam McCall FE->getName()); 113f47564eaSKirill Bobyrev return false; 114f47564eaSKirill Bobyrev } 115f47564eaSKirill Bobyrev return true; 116c4723785SKirill Bobyrev } 117c4723785SKirill Bobyrev 1182e82eb1fSViktoriia Bakalova std::vector<Diag> generateMissingIncludeDiagnostics( 1192e82eb1fSViktoriia Bakalova ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes, 1205549b017SNathan Ridge llvm::StringRef Code, HeaderFilter IgnoreHeaders, const ThreadsafeFS &TFS) { 1212e82eb1fSViktoriia Bakalova std::vector<Diag> Result; 1222e82eb1fSViktoriia Bakalova const SourceManager &SM = AST.getSourceManager(); 1232e82eb1fSViktoriia Bakalova const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); 1242e82eb1fSViktoriia Bakalova 1253093d731SNathan Ridge auto FileStyle = getFormatStyleForFile(AST.tuPath(), Code, TFS, false); 1262e82eb1fSViktoriia Bakalova 1272e82eb1fSViktoriia Bakalova tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code, 1285549b017SNathan Ridge FileStyle.IncludeStyle); 1292e82eb1fSViktoriia Bakalova for (const auto &SymbolWithMissingInclude : MissingIncludes) { 1302e82eb1fSViktoriia Bakalova llvm::StringRef ResolvedPath = 131507d766dSHaojian Wu SymbolWithMissingInclude.Providers.front().resolvedPath(); 132031ffc3eSKadir Cetinkaya if (isIgnored(ResolvedPath, IgnoreHeaders)) { 1332e82eb1fSViktoriia Bakalova dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by " 1342e82eb1fSViktoriia Bakalova "config", 1352e82eb1fSViktoriia Bakalova ResolvedPath); 1362e82eb1fSViktoriia Bakalova continue; 1372e82eb1fSViktoriia Bakalova } 1382e82eb1fSViktoriia Bakalova 1396a6c7ed5SViktoriia Bakalova std::string Spelling = include_cleaner::spellHeader( 1406a6c7ed5SViktoriia Bakalova {SymbolWithMissingInclude.Providers.front(), 1416a6c7ed5SViktoriia Bakalova AST.getPreprocessor().getHeaderSearchInfo(), MainFile}); 142cd5fcea6SViktoriia Bakalova 1432e82eb1fSViktoriia Bakalova llvm::StringRef HeaderRef{Spelling}; 1442e82eb1fSViktoriia Bakalova bool Angled = HeaderRef.starts_with("<"); 1452e82eb1fSViktoriia Bakalova // We might suggest insertion of an existing include in edge cases, e.g., 1462e82eb1fSViktoriia Bakalova // include is present in a PP-disabled region, or spelling of the header 1472e82eb1fSViktoriia Bakalova // turns out to be the same as one of the unresolved includes in the 1482e82eb1fSViktoriia Bakalova // main file. 1492e82eb1fSViktoriia Bakalova std::optional<tooling::Replacement> Replacement = HeaderIncludes.insert( 1502e82eb1fSViktoriia Bakalova HeaderRef.trim("\"<>"), Angled, tooling::IncludeDirective::Include); 1512e82eb1fSViktoriia Bakalova if (!Replacement.has_value()) 1522e82eb1fSViktoriia Bakalova continue; 1532e82eb1fSViktoriia Bakalova 1542e82eb1fSViktoriia Bakalova Diag &D = Result.emplace_back(); 1552e82eb1fSViktoriia Bakalova D.Message = 1562e82eb1fSViktoriia Bakalova llvm::formatv("No header providing \"{0}\" is directly included", 157eed4a4d0SHaojian Wu SymbolWithMissingInclude.Symbol.name()); 1582e82eb1fSViktoriia Bakalova D.Name = "missing-includes"; 1592e82eb1fSViktoriia Bakalova D.Source = Diag::DiagSource::Clangd; 1602e82eb1fSViktoriia Bakalova D.File = AST.tuPath(); 1612e82eb1fSViktoriia Bakalova D.InsideMainFile = true; 1628f847978SSam McCall // We avoid the "warning" severity here in favor of LSP's "information". 1638f847978SSam McCall // 1648f847978SSam McCall // Users treat most warnings on code being edited as high-priority. 1658f847978SSam McCall // They don't think of include cleanups the same way: they want to edit 1668f847978SSam McCall // lines with existing violations without fixing them. 1678f847978SSam McCall // Diagnostics at the same level tend to be visually indistinguishable, 1688f847978SSam McCall // and a few missing includes can cause many diagnostics. 1698f847978SSam McCall // Marking these as "information" leaves them visible, but less intrusive. 1708f847978SSam McCall // 1718f847978SSam McCall // (These concerns don't apply to unused #include warnings: these are fewer, 1728f847978SSam McCall // they appear on infrequently-edited lines with few other warnings, and 1738f847978SSam McCall // the 'Unneccesary' tag often result in a different rendering) 1748f847978SSam McCall // 1758f847978SSam McCall // Usually clang's "note" severity usually has special semantics, being 1768f847978SSam McCall // translated into LSP RelatedInformation of a parent diagnostic. 1778f847978SSam McCall // But not here: these aren't processed by clangd's DiagnosticConsumer. 1788f847978SSam McCall D.Severity = DiagnosticsEngine::Note; 1792e82eb1fSViktoriia Bakalova D.Range = clangd::Range{ 1802e82eb1fSViktoriia Bakalova offsetToPosition(Code, 1812e82eb1fSViktoriia Bakalova SymbolWithMissingInclude.SymRefRange.beginOffset()), 1822e82eb1fSViktoriia Bakalova offsetToPosition(Code, 1832e82eb1fSViktoriia Bakalova SymbolWithMissingInclude.SymRefRange.endOffset())}; 1842e82eb1fSViktoriia Bakalova auto &F = D.Fixes.emplace_back(); 1852e82eb1fSViktoriia Bakalova F.Message = "#include " + Spelling; 1862e82eb1fSViktoriia Bakalova TextEdit Edit = replacementToEdit(Code, *Replacement); 1872e82eb1fSViktoriia Bakalova F.Edits.emplace_back(std::move(Edit)); 1882e82eb1fSViktoriia Bakalova } 1892e82eb1fSViktoriia Bakalova return Result; 1902e82eb1fSViktoriia Bakalova } 1912e82eb1fSViktoriia Bakalova 1922e82eb1fSViktoriia Bakalova std::vector<Diag> generateUnusedIncludeDiagnostics( 1932e82eb1fSViktoriia Bakalova PathRef FileName, llvm::ArrayRef<const Inclusion *> UnusedIncludes, 194031ffc3eSKadir Cetinkaya llvm::StringRef Code, HeaderFilter IgnoreHeaders) { 1952e82eb1fSViktoriia Bakalova std::vector<Diag> Result; 1962e82eb1fSViktoriia Bakalova for (const auto *Inc : UnusedIncludes) { 197031ffc3eSKadir Cetinkaya if (isIgnored(Inc->Resolved, IgnoreHeaders)) 198031ffc3eSKadir Cetinkaya continue; 1992e82eb1fSViktoriia Bakalova Diag &D = Result.emplace_back(); 2002e82eb1fSViktoriia Bakalova D.Message = 2012e82eb1fSViktoriia Bakalova llvm::formatv("included header {0} is not used directly", 2022e82eb1fSViktoriia Bakalova llvm::sys::path::filename( 2032e82eb1fSViktoriia Bakalova Inc->Written.substr(1, Inc->Written.size() - 2), 2042e82eb1fSViktoriia Bakalova llvm::sys::path::Style::posix)); 2052e82eb1fSViktoriia Bakalova D.Name = "unused-includes"; 2062e82eb1fSViktoriia Bakalova D.Source = Diag::DiagSource::Clangd; 2072e82eb1fSViktoriia Bakalova D.File = FileName; 2082e82eb1fSViktoriia Bakalova D.InsideMainFile = true; 2092e82eb1fSViktoriia Bakalova D.Severity = DiagnosticsEngine::Warning; 2102e82eb1fSViktoriia Bakalova D.Tags.push_back(Unnecessary); 21196e50797SViktoriia Bakalova D.Range = rangeTillEOL(Code, Inc->HashOffset); 2122e82eb1fSViktoriia Bakalova // FIXME(kirillbobyrev): Removing inclusion might break the code if the 2132e82eb1fSViktoriia Bakalova // used headers are only reachable transitively through this one. Suggest 2142e82eb1fSViktoriia Bakalova // including them directly instead. 2152e82eb1fSViktoriia Bakalova // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas 2162e82eb1fSViktoriia Bakalova // (keep/export) remove the warning once we support IWYU pragmas. 2172e82eb1fSViktoriia Bakalova auto &F = D.Fixes.emplace_back(); 2182e82eb1fSViktoriia Bakalova F.Message = "remove #include directive"; 2192e82eb1fSViktoriia Bakalova F.Edits.emplace_back(); 2202e82eb1fSViktoriia Bakalova F.Edits.back().range.start.line = Inc->HashLine; 2212e82eb1fSViktoriia Bakalova F.Edits.back().range.end.line = Inc->HashLine + 1; 2222e82eb1fSViktoriia Bakalova } 2232e82eb1fSViktoriia Bakalova return Result; 2242e82eb1fSViktoriia Bakalova } 225031ffc3eSKadir Cetinkaya 226031ffc3eSKadir Cetinkaya std::optional<Fix> 227031ffc3eSKadir Cetinkaya removeAllUnusedIncludes(llvm::ArrayRef<Diag> UnusedIncludes) { 228031ffc3eSKadir Cetinkaya if (UnusedIncludes.empty()) 229031ffc3eSKadir Cetinkaya return std::nullopt; 230031ffc3eSKadir Cetinkaya 231031ffc3eSKadir Cetinkaya Fix RemoveAll; 232031ffc3eSKadir Cetinkaya RemoveAll.Message = "remove all unused includes"; 233031ffc3eSKadir Cetinkaya for (const auto &Diag : UnusedIncludes) { 234031ffc3eSKadir Cetinkaya assert(Diag.Fixes.size() == 1 && "Expected exactly one fix."); 235031ffc3eSKadir Cetinkaya RemoveAll.Edits.insert(RemoveAll.Edits.end(), 236031ffc3eSKadir Cetinkaya Diag.Fixes.front().Edits.begin(), 237031ffc3eSKadir Cetinkaya Diag.Fixes.front().Edits.end()); 238031ffc3eSKadir Cetinkaya } 239031ffc3eSKadir Cetinkaya return RemoveAll; 240031ffc3eSKadir Cetinkaya } 241031ffc3eSKadir Cetinkaya 242031ffc3eSKadir Cetinkaya std::optional<Fix> 243031ffc3eSKadir Cetinkaya addAllMissingIncludes(llvm::ArrayRef<Diag> MissingIncludeDiags) { 244031ffc3eSKadir Cetinkaya if (MissingIncludeDiags.empty()) 245031ffc3eSKadir Cetinkaya return std::nullopt; 246031ffc3eSKadir Cetinkaya 247031ffc3eSKadir Cetinkaya Fix AddAllMissing; 248031ffc3eSKadir Cetinkaya AddAllMissing.Message = "add all missing includes"; 249031ffc3eSKadir Cetinkaya // A map to deduplicate the edits with the same new text. 250031ffc3eSKadir Cetinkaya // newText (#include "my_missing_header.h") -> TextEdit. 251209694deSHaojian Wu std::map<std::string, TextEdit> Edits; 252031ffc3eSKadir Cetinkaya for (const auto &Diag : MissingIncludeDiags) { 253031ffc3eSKadir Cetinkaya assert(Diag.Fixes.size() == 1 && "Expected exactly one fix."); 254031ffc3eSKadir Cetinkaya for (const auto &Edit : Diag.Fixes.front().Edits) { 255031ffc3eSKadir Cetinkaya Edits.try_emplace(Edit.newText, Edit); 256031ffc3eSKadir Cetinkaya } 257031ffc3eSKadir Cetinkaya } 2582336f792Skadir çetinkaya for (auto &It : Edits) 259209694deSHaojian Wu AddAllMissing.Edits.push_back(std::move(It.second)); 260031ffc3eSKadir Cetinkaya return AddAllMissing; 261031ffc3eSKadir Cetinkaya } 262031ffc3eSKadir Cetinkaya Fix fixAll(const Fix &RemoveAllUnused, const Fix &AddAllMissing) { 263031ffc3eSKadir Cetinkaya Fix FixAll; 264031ffc3eSKadir Cetinkaya FixAll.Message = "fix all includes"; 265031ffc3eSKadir Cetinkaya 266031ffc3eSKadir Cetinkaya for (const auto &F : RemoveAllUnused.Edits) 267031ffc3eSKadir Cetinkaya FixAll.Edits.push_back(F); 268031ffc3eSKadir Cetinkaya for (const auto &F : AddAllMissing.Edits) 269031ffc3eSKadir Cetinkaya FixAll.Edits.push_back(F); 270031ffc3eSKadir Cetinkaya return FixAll; 271031ffc3eSKadir Cetinkaya } 272031ffc3eSKadir Cetinkaya 273031ffc3eSKadir Cetinkaya std::vector<const Inclusion *> 274031ffc3eSKadir Cetinkaya getUnused(ParsedAST &AST, 2758c5a7a1fSVadim D const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles, 2768c5a7a1fSVadim D bool AnalyzeAngledIncludes) { 277031ffc3eSKadir Cetinkaya trace::Span Tracer("IncludeCleaner::getUnused"); 278031ffc3eSKadir Cetinkaya std::vector<const Inclusion *> Unused; 279031ffc3eSKadir Cetinkaya for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) { 280031ffc3eSKadir Cetinkaya if (!MFI.HeaderID) 281031ffc3eSKadir Cetinkaya continue; 282031ffc3eSKadir Cetinkaya auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID); 283031ffc3eSKadir Cetinkaya if (ReferencedFiles.contains(IncludeID)) 284031ffc3eSKadir Cetinkaya continue; 2858c5a7a1fSVadim D if (!mayConsiderUnused(MFI, AST, &AST.getPragmaIncludes(), 2868c5a7a1fSVadim D AnalyzeAngledIncludes)) { 287031ffc3eSKadir Cetinkaya dlog("{0} was not used, but is not eligible to be diagnosed as unused", 288031ffc3eSKadir Cetinkaya MFI.Written); 289031ffc3eSKadir Cetinkaya continue; 290031ffc3eSKadir Cetinkaya } 291031ffc3eSKadir Cetinkaya Unused.push_back(&MFI); 292031ffc3eSKadir Cetinkaya } 293031ffc3eSKadir Cetinkaya return Unused; 294031ffc3eSKadir Cetinkaya } 295031ffc3eSKadir Cetinkaya 296d1ec581eSKirill Bobyrev } // namespace 297d1ec581eSKirill Bobyrev 298655baae2SViktoriia Bakalova std::vector<include_cleaner::SymbolReference> 299655baae2SViktoriia Bakalova collectMacroReferences(ParsedAST &AST) { 300655baae2SViktoriia Bakalova const auto &SM = AST.getSourceManager(); 301655baae2SViktoriia Bakalova auto &PP = AST.getPreprocessor(); 3026585dd3bSHaojian Wu std::vector<include_cleaner::SymbolReference> Macros; 3036585dd3bSHaojian Wu for (const auto &[_, Refs] : AST.getMacros().MacroRefs) { 3046585dd3bSHaojian Wu for (const auto &Ref : Refs) { 3056585dd3bSHaojian Wu auto Loc = SM.getComposedLoc(SM.getMainFileID(), Ref.StartOffset); 3065f1adf04SUtkarsh Saxena const auto *Tok = AST.getTokens().spelledTokenContaining(Loc); 3076585dd3bSHaojian Wu if (!Tok) 3086585dd3bSHaojian Wu continue; 3096585dd3bSHaojian Wu auto Macro = locateMacroAt(*Tok, PP); 310655baae2SViktoriia Bakalova if (!Macro) 311655baae2SViktoriia Bakalova continue; 3126585dd3bSHaojian Wu auto DefLoc = Macro->NameLoc; 3136585dd3bSHaojian Wu if (!DefLoc.isValid()) 3146585dd3bSHaojian Wu continue; 315655baae2SViktoriia Bakalova Macros.push_back( 3166585dd3bSHaojian Wu {include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok->text(SM)), 317655baae2SViktoriia Bakalova DefLoc}, 3186585dd3bSHaojian Wu Tok->location(), 3196585dd3bSHaojian Wu Ref.InConditionalDirective ? include_cleaner::RefType::Ambiguous 3206585dd3bSHaojian Wu : include_cleaner::RefType::Explicit}); 321655baae2SViktoriia Bakalova } 3226585dd3bSHaojian Wu } 3236585dd3bSHaojian Wu 324655baae2SViktoriia Bakalova return Macros; 325655baae2SViktoriia Bakalova } 326655baae2SViktoriia Bakalova 327d97a3419SSam McCall include_cleaner::Includes convertIncludes(const ParsedAST &AST) { 328d97a3419SSam McCall auto &SM = AST.getSourceManager(); 329d97a3419SSam McCall 3302bececb8SViktoriia Bakalova include_cleaner::Includes ConvertedIncludes; 331d97a3419SSam McCall // We satisfy Includes's contract that search dirs and included files have 332d97a3419SSam McCall // matching path styles: both ultimately use FileManager::getCanonicalName(). 333d97a3419SSam McCall for (const auto &Dir : AST.getIncludeStructure().SearchPathsCanonical) 334d97a3419SSam McCall ConvertedIncludes.addSearchDirectory(Dir); 335d97a3419SSam McCall 336d97a3419SSam McCall for (const Inclusion &Inc : AST.getIncludeStructure().MainFileIncludes) { 3372bececb8SViktoriia Bakalova include_cleaner::Include TransformedInc; 3382bececb8SViktoriia Bakalova llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written); 3392bececb8SViktoriia Bakalova TransformedInc.Spelled = WrittenRef.trim("\"<>"); 3402bececb8SViktoriia Bakalova TransformedInc.HashLocation = 3412bececb8SViktoriia Bakalova SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); 3422bececb8SViktoriia Bakalova TransformedInc.Line = Inc.HashLine + 1; 3432bececb8SViktoriia Bakalova TransformedInc.Angled = WrittenRef.starts_with("<"); 344d97a3419SSam McCall // Inc.Resolved is canonicalized with clangd::getCanonicalPath(), 345d97a3419SSam McCall // which is based on FileManager::getCanonicalName(ParentDir). 346f6307b26SSam McCall auto FE = SM.getFileManager().getFileRef(Inc.Resolved); 3472bececb8SViktoriia Bakalova if (!FE) { 3482bececb8SViktoriia Bakalova elog("IncludeCleaner: Failed to get an entry for resolved path {0}: {1}", 349f6307b26SSam McCall Inc.Resolved, FE.takeError()); 3502bececb8SViktoriia Bakalova continue; 3512bececb8SViktoriia Bakalova } 3522bececb8SViktoriia Bakalova TransformedInc.Resolved = *FE; 3532bececb8SViktoriia Bakalova ConvertedIncludes.add(std::move(TransformedInc)); 3542bececb8SViktoriia Bakalova } 3552bececb8SViktoriia Bakalova return ConvertedIncludes; 3562bececb8SViktoriia Bakalova } 3572bececb8SViktoriia Bakalova 3588c5a7a1fSVadim D IncludeCleanerFindings 3598c5a7a1fSVadim D computeIncludeCleanerFindings(ParsedAST &AST, bool AnalyzeAngledIncludes) { 360031ffc3eSKadir Cetinkaya // Interaction is only polished for C/CPP. 361031ffc3eSKadir Cetinkaya if (AST.getLangOpts().ObjC) 362031ffc3eSKadir Cetinkaya return {}; 363939dce12SHaojian Wu const auto &SM = AST.getSourceManager(); 364d97a3419SSam McCall include_cleaner::Includes ConvertedIncludes = convertIncludes(AST); 3652e82eb1fSViktoriia Bakalova const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); 366c03d1844SJan Svoboda auto PreamblePatch = PreamblePatch::getPatchEntry(AST.tuPath(), SM); 367e028c974SViktoriia Bakalova 3682e82eb1fSViktoriia Bakalova std::vector<include_cleaner::SymbolReference> Macros = 3692e82eb1fSViktoriia Bakalova collectMacroReferences(AST); 3702e82eb1fSViktoriia Bakalova std::vector<MissingIncludeDiagInfo> MissingIncludes; 371939dce12SHaojian Wu llvm::DenseSet<IncludeStructure::HeaderID> Used; 3722e82eb1fSViktoriia Bakalova trace::Span Tracer("include_cleaner::walkUsed"); 373cb92511cSJan Svoboda OptionalDirectoryEntryRef ResourceDir = AST.getPreprocessor() 37443c20367SViktoriia Bakalova .getHeaderSearchInfo() 37543c20367SViktoriia Bakalova .getModuleMap() 37643c20367SViktoriia Bakalova .getBuiltinDir(); 377939dce12SHaojian Wu include_cleaner::walkUsed( 378939dce12SHaojian Wu AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros, 3797837110eSkadir çetinkaya &AST.getPragmaIncludes(), AST.getPreprocessor(), 380939dce12SHaojian Wu [&](const include_cleaner::SymbolReference &Ref, 381939dce12SHaojian Wu llvm::ArrayRef<include_cleaner::Header> Providers) { 3822e82eb1fSViktoriia Bakalova bool Satisfied = false; 383939dce12SHaojian Wu for (const auto &H : Providers) { 3842e82eb1fSViktoriia Bakalova if (H.kind() == include_cleaner::Header::Physical && 38543c20367SViktoriia Bakalova (H.physical() == MainFile || H.physical() == PreamblePatch || 38698e6deb6SJan Svoboda H.physical().getDir() == ResourceDir)) { 3872e82eb1fSViktoriia Bakalova Satisfied = true; 388fd8c9ef2SViktoriia Bakalova continue; 389fd8c9ef2SViktoriia Bakalova } 3902e82eb1fSViktoriia Bakalova for (auto *Inc : ConvertedIncludes.match(H)) { 3912e82eb1fSViktoriia Bakalova Satisfied = true; 392d97a3419SSam McCall auto HeaderID = 393d97a3419SSam McCall AST.getIncludeStructure().getID(&Inc->Resolved->getFileEntry()); 3942e82eb1fSViktoriia Bakalova assert(HeaderID.has_value() && 3952e82eb1fSViktoriia Bakalova "ConvertedIncludes only contains resolved includes."); 3962e82eb1fSViktoriia Bakalova Used.insert(*HeaderID); 397939dce12SHaojian Wu } 398939dce12SHaojian Wu } 39946447e0bSViktoriia Bakalova 4002e82eb1fSViktoriia Bakalova if (Satisfied || Providers.empty() || 4012e82eb1fSViktoriia Bakalova Ref.RT != include_cleaner::RefType::Explicit) 4022e82eb1fSViktoriia Bakalova return; 4032e82eb1fSViktoriia Bakalova 404*2f5dc596Skadir çetinkaya // Check if we have any headers with the same spelling, in edge cases 405*2f5dc596Skadir çetinkaya // like `#include_next "foo.h"`, the user can't ever include the 406*2f5dc596Skadir çetinkaya // physical foo.h, but can have a spelling that refers to it. 407*2f5dc596Skadir çetinkaya // We postpone this check because spelling a header for every usage is 408*2f5dc596Skadir çetinkaya // expensive. 409*2f5dc596Skadir çetinkaya std::string Spelling = include_cleaner::spellHeader( 410*2f5dc596Skadir çetinkaya {Providers.front(), AST.getPreprocessor().getHeaderSearchInfo(), 411*2f5dc596Skadir çetinkaya MainFile}); 412*2f5dc596Skadir çetinkaya for (auto *Inc : 413*2f5dc596Skadir çetinkaya ConvertedIncludes.match(include_cleaner::Header{Spelling})) { 414*2f5dc596Skadir çetinkaya Satisfied = true; 415*2f5dc596Skadir çetinkaya auto HeaderID = 416*2f5dc596Skadir çetinkaya AST.getIncludeStructure().getID(&Inc->Resolved->getFileEntry()); 417*2f5dc596Skadir çetinkaya assert(HeaderID.has_value() && 418*2f5dc596Skadir çetinkaya "ConvertedIncludes only contains resolved includes."); 419*2f5dc596Skadir çetinkaya Used.insert(*HeaderID); 420*2f5dc596Skadir çetinkaya } 421*2f5dc596Skadir çetinkaya if (Satisfied) 422*2f5dc596Skadir çetinkaya return; 423*2f5dc596Skadir çetinkaya 424481f8885SViktoriia Bakalova // We actually always want to map usages to their spellings, but 425481f8885SViktoriia Bakalova // spelling locations can point into preamble section. Using these 426481f8885SViktoriia Bakalova // offsets could lead into crashes in presence of stale preambles. Hence 427481f8885SViktoriia Bakalova // we use "getFileLoc" instead to make sure it always points into main 428481f8885SViktoriia Bakalova // file. 429481f8885SViktoriia Bakalova // FIXME: Use presumed locations to map such usages back to patched 430481f8885SViktoriia Bakalova // locations safely. 431481f8885SViktoriia Bakalova auto Loc = SM.getFileLoc(Ref.RefLocation); 432f3a815aaSKadir Cetinkaya // File locations can be outside of the main file if macro is expanded 433f3a815aaSKadir Cetinkaya // through an #include. 434f3a815aaSKadir Cetinkaya while (SM.getFileID(Loc) != SM.getMainFileID()) 435f3a815aaSKadir Cetinkaya Loc = SM.getIncludeLoc(SM.getFileID(Loc)); 43625240001SHaojian Wu auto TouchingTokens = 43725240001SHaojian Wu syntax::spelledTokensTouching(Loc, AST.getTokens()); 43825240001SHaojian Wu assert(!TouchingTokens.empty()); 43925240001SHaojian Wu // Loc points to the start offset of the ref token, here we use the last 44025240001SHaojian Wu // element of the TouchingTokens, e.g. avoid getting the "::" for 44125240001SHaojian Wu // "ns::^abc". 44225240001SHaojian Wu MissingIncludeDiagInfo DiagInfo{ 44325240001SHaojian Wu Ref.Target, TouchingTokens.back().range(SM), Providers}; 4442e82eb1fSViktoriia Bakalova MissingIncludes.push_back(std::move(DiagInfo)); 4452e82eb1fSViktoriia Bakalova }); 44608f0725aSKadir Cetinkaya // Put possibly equal diagnostics together for deduplication. 44708f0725aSKadir Cetinkaya // The duplicates might be from macro arguments that get expanded multiple 44808f0725aSKadir Cetinkaya // times. 44908f0725aSKadir Cetinkaya llvm::stable_sort(MissingIncludes, [](const MissingIncludeDiagInfo &LHS, 45008f0725aSKadir Cetinkaya const MissingIncludeDiagInfo &RHS) { 4515e74a3dcSKadir Cetinkaya // First sort by reference location. 4525e74a3dcSKadir Cetinkaya if (LHS.SymRefRange != RHS.SymRefRange) { 45308f0725aSKadir Cetinkaya // We can get away just by comparing the offsets as all the ranges are in 45408f0725aSKadir Cetinkaya // main file. 45508f0725aSKadir Cetinkaya return LHS.SymRefRange.beginOffset() < RHS.SymRefRange.beginOffset(); 45608f0725aSKadir Cetinkaya } 4575e74a3dcSKadir Cetinkaya // For the same location, break ties using the symbol. Note that this won't 4585e74a3dcSKadir Cetinkaya // be stable across runs. 4595e74a3dcSKadir Cetinkaya using MapInfo = llvm::DenseMapInfo<include_cleaner::Symbol>; 4605e74a3dcSKadir Cetinkaya return MapInfo::getHashValue(LHS.Symbol) < 4615e74a3dcSKadir Cetinkaya MapInfo::getHashValue(RHS.Symbol); 46208f0725aSKadir Cetinkaya }); 46308f0725aSKadir Cetinkaya MissingIncludes.erase(llvm::unique(MissingIncludes), MissingIncludes.end()); 4648c5a7a1fSVadim D std::vector<const Inclusion *> UnusedIncludes = 4658c5a7a1fSVadim D getUnused(AST, Used, AnalyzeAngledIncludes); 4662e82eb1fSViktoriia Bakalova return {std::move(UnusedIncludes), std::move(MissingIncludes)}; 4672e82eb1fSViktoriia Bakalova } 4682e82eb1fSViktoriia Bakalova 469d97a3419SSam McCall bool isPreferredProvider(const Inclusion &Inc, 470d97a3419SSam McCall const include_cleaner::Includes &Includes, 471d97a3419SSam McCall llvm::ArrayRef<include_cleaner::Header> Providers) { 472d97a3419SSam McCall for (const auto &H : Providers) { 473d97a3419SSam McCall auto Matches = Includes.match(H); 474d97a3419SSam McCall for (const include_cleaner::Include *Match : Matches) 475d97a3419SSam McCall if (Match->Line == unsigned(Inc.HashLine + 1)) 476d97a3419SSam McCall return true; // this header is (equal) best 477d97a3419SSam McCall if (!Matches.empty()) 478d97a3419SSam McCall return false; // another header is better 479d97a3419SSam McCall } 480d97a3419SSam McCall return false; // no header provides the symbol 481d97a3419SSam McCall } 482d97a3419SSam McCall 483031ffc3eSKadir Cetinkaya std::vector<Diag> 484031ffc3eSKadir Cetinkaya issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, 485031ffc3eSKadir Cetinkaya const IncludeCleanerFindings &Findings, 4865549b017SNathan Ridge const ThreadsafeFS &TFS, 487031ffc3eSKadir Cetinkaya HeaderFilter IgnoreHeaders) { 488031ffc3eSKadir Cetinkaya trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics"); 4894b1cec06SHaojian Wu std::vector<Diag> UnusedIncludes = generateUnusedIncludeDiagnostics( 490031ffc3eSKadir Cetinkaya AST.tuPath(), Findings.UnusedIncludes, Code, IgnoreHeaders); 491d0e89116SHaojian Wu std::optional<Fix> RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes); 4924b1cec06SHaojian Wu 4934b1cec06SHaojian Wu std::vector<Diag> MissingIncludeDiags = generateMissingIncludeDiagnostics( 4945549b017SNathan Ridge AST, Findings.MissingIncludes, Code, IgnoreHeaders, TFS); 495d0e89116SHaojian Wu std::optional<Fix> AddAllMissing = addAllMissingIncludes(MissingIncludeDiags); 4964b1cec06SHaojian Wu 4974b1cec06SHaojian Wu std::optional<Fix> FixAll; 4984b1cec06SHaojian Wu if (RemoveAllUnused && AddAllMissing) 4994b1cec06SHaojian Wu FixAll = fixAll(*RemoveAllUnused, *AddAllMissing); 5004b1cec06SHaojian Wu 5014b1cec06SHaojian Wu auto AddBatchFix = [](const std::optional<Fix> &F, clang::clangd::Diag *Out) { 502031ffc3eSKadir Cetinkaya if (!F) 503031ffc3eSKadir Cetinkaya return; 5044b1cec06SHaojian Wu Out->Fixes.push_back(*F); 5054b1cec06SHaojian Wu }; 5064b1cec06SHaojian Wu for (auto &Diag : MissingIncludeDiags) { 507031ffc3eSKadir Cetinkaya AddBatchFix(MissingIncludeDiags.size() > 1 ? AddAllMissing : std::nullopt, 508d0e89116SHaojian Wu &Diag); 5094b1cec06SHaojian Wu AddBatchFix(FixAll, &Diag); 5104b1cec06SHaojian Wu } 5114b1cec06SHaojian Wu for (auto &Diag : UnusedIncludes) { 512031ffc3eSKadir Cetinkaya AddBatchFix(UnusedIncludes.size() > 1 ? RemoveAllUnused : std::nullopt, 513d0e89116SHaojian Wu &Diag); 5144b1cec06SHaojian Wu AddBatchFix(FixAll, &Diag); 5154b1cec06SHaojian Wu } 5164b1cec06SHaojian Wu 5174b1cec06SHaojian Wu auto Result = std::move(MissingIncludeDiags); 518031ffc3eSKadir Cetinkaya llvm::move(UnusedIncludes, std::back_inserter(Result)); 5194b1cec06SHaojian Wu return Result; 5204b1cec06SHaojian Wu } 5214b1cec06SHaojian Wu 522031ffc3eSKadir Cetinkaya } // namespace clang::clangd 523