xref: /llvm-project/clang-tools-extra/clangd/IncludeCleaner.cpp (revision 2f5dc596b5719e5ed7f6978dafbce994f425a033)
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