clang-tools 20.0.0git
IncludeCleanerCheck.cpp
Go to the documentation of this file.
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
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
46using namespace clang::ast_matchers;
47
48namespace clang::tidy::misc {
49
50namespace {
51struct MissingIncludeInfo {
52 include_cleaner::SymbolReference SymRef;
53 include_cleaner::Header Missing;
54};
55} // namespace
56
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
74 Options.store(Opts, "IgnoreHeaders",
76 Options.store(Opts, "DeduplicateFindings", DeduplicateFindings);
77}
78
80 const LangOptions &LangOpts) const {
81 return !LangOpts.ObjC;
82}
83
84void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) {
85 Finder->addMatcher(translationUnitDecl().bind("top"), this);
86}
87
88void 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
96bool 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
113void 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
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
std::string MainFile
include_cleaner::Header Missing
include_cleaner::SymbolReference SymRef
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
Override this to disable registering matchers and PP callbacks if an invalid language version is bein...
IncludeCleanerCheck(StringRef Name, ClangTidyContext *Context)
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap