1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
|
//===--- IncludeCleanerCheck.cpp - clang-tidy -----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "IncludeCleanerCheck.h"
#include "../ClangTidyCheck.h"
#include "../ClangTidyDiagnosticConsumer.h"
#include "../ClangTidyOptions.h"
#include "../utils/OptionsUtils.h"
#include "clang-include-cleaner/Analysis.h"
#include "clang-include-cleaner/IncludeSpeller.h"
#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileEntry.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Format/Format.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Inclusions/HeaderIncludes.h"
#include "clang/Tooling/Inclusions/StandardLibrary.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Regex.h"
#include <optional>
#include <string>
#include <vector>
using namespace clang::ast_matchers;
namespace clang::tidy::misc {
namespace {
struct MissingIncludeInfo {
include_cleaner::SymbolReference SymRef;
include_cleaner::Header Missing;
};
} // namespace
IncludeCleanerCheck::IncludeCleanerCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
IgnoreHeaders(
utils::options::parseStringList(Options.get("IgnoreHeaders", ""))),
DeduplicateFindings(Options.get("DeduplicateFindings", true)),
UnusedIncludes(Options.get("UnusedIncludes", true)),
MissingIncludes(Options.get("MissingIncludes", true)) {
for (const auto &Header : IgnoreHeaders) {
if (!llvm::Regex{Header}.isValid())
configurationDiag("Invalid ignore headers regex '%0'") << Header;
std::string HeaderSuffix{Header.str()};
if (!Header.ends_with("$"))
HeaderSuffix += "$";
IgnoreHeadersRegex.emplace_back(HeaderSuffix);
}
if (UnusedIncludes == false && MissingIncludes == false)
this->configurationDiag("The check 'misc-include-cleaner' will not "
"perform any analysis because 'UnusedIncludes' and "
"'MissingIncludes' are both false.");
}
void IncludeCleanerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoreHeaders",
utils::options::serializeStringList(IgnoreHeaders));
Options.store(Opts, "DeduplicateFindings", DeduplicateFindings);
Options.store(Opts, "UnusedIncludes", UnusedIncludes);
Options.store(Opts, "MissingIncludes", MissingIncludes);
}
bool IncludeCleanerCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return !LangOpts.ObjC;
}
void IncludeCleanerCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(translationUnitDecl().bind("top"), this);
}
void IncludeCleanerCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
Preprocessor *ModuleExpanderPP) {
PP->addPPCallbacks(RecordedPreprocessor.record(*PP));
this->PP = PP;
RecordedPI.record(*PP);
}
bool IncludeCleanerCheck::shouldIgnore(const include_cleaner::Header &H) {
return llvm::any_of(IgnoreHeadersRegex, [&H](const llvm::Regex &R) {
switch (H.kind()) {
case include_cleaner::Header::Standard:
// We don't trim angle brackets around standard library headers
// deliberately, so that they are only matched as <vector>, otherwise
// having just `.*/vector` might yield false positives.
return R.match(H.standard().name());
case include_cleaner::Header::Verbatim:
return R.match(H.verbatim().trim("<>\""));
case include_cleaner::Header::Physical:
return R.match(H.physical().getFileEntry().tryGetRealPathName());
}
llvm_unreachable("Unknown Header kind.");
});
}
void IncludeCleanerCheck::check(const MatchFinder::MatchResult &Result) {
const SourceManager *SM = Result.SourceManager;
const FileEntry *MainFile = SM->getFileEntryForID(SM->getMainFileID());
llvm::DenseSet<const include_cleaner::Include *> Used;
std::vector<MissingIncludeInfo> Missing;
llvm::SmallVector<Decl *> MainFileDecls;
for (Decl *D : Result.Nodes.getNodeAs<TranslationUnitDecl>("top")->decls()) {
if (!SM->isWrittenInMainFile(SM->getExpansionLoc(D->getLocation())))
continue;
// FIXME: Filter out implicit template specializations.
MainFileDecls.push_back(D);
}
llvm::DenseSet<include_cleaner::Symbol> SeenSymbols;
OptionalDirectoryEntryRef ResourceDir =
PP->getHeaderSearchInfo().getModuleMap().getBuiltinDir();
// FIXME: Find a way to have less code duplication between include-cleaner
// analysis implementation and the below code.
walkUsed(MainFileDecls, RecordedPreprocessor.MacroReferences, &RecordedPI,
*PP,
[&](const include_cleaner::SymbolReference &Ref,
llvm::ArrayRef<include_cleaner::Header> Providers) {
// Process each symbol once to reduce noise in the findings.
// Tidy checks are used in two different workflows:
// - Ones that show all the findings for a given file. For such
// workflows there is not much point in showing all the occurences,
// as one is enough to indicate the issue.
// - Ones that show only the findings on changed pieces. For such
// workflows it's useful to show findings on every reference of a
// symbol as otherwise tools might give incosistent results
// depending on the parts of the file being edited. But it should
// still help surface findings for "new violations" (i.e.
// dependency did not exist in the code at all before).
if (DeduplicateFindings && !SeenSymbols.insert(Ref.Target).second)
return;
bool Satisfied = false;
for (const include_cleaner::Header &H : Providers) {
if (H.kind() == include_cleaner::Header::Physical &&
(H.physical() == MainFile ||
H.physical().getDir() == ResourceDir)) {
Satisfied = true;
continue;
}
for (const include_cleaner::Include *I :
RecordedPreprocessor.Includes.match(H)) {
Used.insert(I);
Satisfied = true;
}
}
if (!Satisfied && !Providers.empty() &&
Ref.RT == include_cleaner::RefType::Explicit &&
!shouldIgnore(Providers.front()))
Missing.push_back({Ref, Providers.front()});
});
std::vector<const include_cleaner::Include *> Unused;
for (const include_cleaner::Include &I :
RecordedPreprocessor.Includes.all()) {
if (Used.contains(&I) || !I.Resolved || I.Resolved->getDir() == ResourceDir)
continue;
if (RecordedPI.shouldKeep(*I.Resolved))
continue;
// Check if main file is the public interface for a private header. If so
// we shouldn't diagnose it as unused.
if (auto PHeader = RecordedPI.getPublic(*I.Resolved); !PHeader.empty()) {
PHeader = PHeader.trim("<>\"");
// Since most private -> public mappings happen in a verbatim way, we
// check textually here. This might go wrong in presence of symlinks or
// header mappings. But that's not different than rest of the places.
if (getCurrentMainFile().ends_with(PHeader))
continue;
}
auto StdHeader = tooling::stdlib::Header::named(
I.quote(), PP->getLangOpts().CPlusPlus ? tooling::stdlib::Lang::CXX
: tooling::stdlib::Lang::C);
if (StdHeader && shouldIgnore(*StdHeader))
continue;
if (shouldIgnore(*I.Resolved))
continue;
Unused.push_back(&I);
}
llvm::StringRef Code = SM->getBufferData(SM->getMainFileID());
auto FileStyle =
format::getStyle(format::DefaultFormatStyle, getCurrentMainFile(),
format::DefaultFallbackStyle, Code,
&SM->getFileManager().getVirtualFileSystem());
if (!FileStyle)
FileStyle = format::getLLVMStyle();
if (UnusedIncludes) {
for (const auto *Inc : Unused) {
diag(Inc->HashLocation, "included header %0 is not used directly")
<< llvm::sys::path::filename(Inc->Spelled,
llvm::sys::path::Style::posix)
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(
SM->translateLineCol(SM->getMainFileID(), Inc->Line, 1),
SM->translateLineCol(SM->getMainFileID(), Inc->Line + 1, 1)));
}
}
if (MissingIncludes) {
tooling::HeaderIncludes HeaderIncludes(getCurrentMainFile(), Code,
FileStyle->IncludeStyle);
// Deduplicate insertions when running in bulk fix mode.
llvm::StringSet<> InsertedHeaders{};
for (const auto &Inc : Missing) {
std::string Spelling = include_cleaner::spellHeader(
{Inc.Missing, PP->getHeaderSearchInfo(), MainFile});
bool Angled = llvm::StringRef{Spelling}.starts_with("<");
// We might suggest insertion of an existing include in edge cases, e.g.,
// include is present in a PP-disabled region, or spelling of the header
// turns out to be the same as one of the unresolved includes in the
// main file.
if (auto Replacement = HeaderIncludes.insert(
llvm::StringRef{Spelling}.trim("\"<>"), Angled,
tooling::IncludeDirective::Include)) {
DiagnosticBuilder DB =
diag(SM->getSpellingLoc(Inc.SymRef.RefLocation),
"no header providing \"%0\" is directly included")
<< Inc.SymRef.Target.name();
if (areDiagsSelfContained() ||
InsertedHeaders.insert(Replacement->getReplacementText()).second) {
DB << FixItHint::CreateInsertion(
SM->getComposedLoc(SM->getMainFileID(), Replacement->getOffset()),
Replacement->getReplacementText());
}
}
}
}
}
} // namespace clang::tidy::misc
|