xref: /llvm-project/clang-tools-extra/clang-tidy/misc/IncludeCleanerCheck.cpp (revision 222dd235ffc39b3695a3c002593097bec216a8fa)
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