Bug Summary

File:build/source/clang-tools-extra/clangd/IncludeCleaner.cpp
Warning:line 160, column 12
Called C++ object pointer is null

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name IncludeCleaner.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=none -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -ffunction-sections -fdata-sections -fcoverage-compilation-dir=/build/source/build-llvm/tools/clang/stage2-bins -resource-dir /usr/lib/llvm-17/lib/clang/17 -D CLANG_REPOSITORY_STRING="++20230510111145+7df43bdb42ae-1~exp1~20230510111303.1288" -D _DEBUG -D _GLIBCXX_ASSERTIONS -D _GNU_SOURCE -D _LIBCPP_ENABLE_ASSERTIONS -D __STDC_CONSTANT_MACROS -D __STDC_FORMAT_MACROS -D __STDC_LIMIT_MACROS -I tools/clang/tools/extra/clangd -I /build/source/clang-tools-extra/clangd -I /build/source/clang-tools-extra/clangd/../include-cleaner/include -I tools/clang/tools/extra/clangd/../clang-tidy -I /build/source/clang/include -I tools/clang/include -I include -I /build/source/llvm/include -I /build/source/clang-tools-extra/pseudo/lib/../include -D _FORTIFY_SOURCE=2 -D NDEBUG -U NDEBUG -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/x86_64-linux-gnu/c++/10 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/backward -internal-isystem /usr/lib/llvm-17/lib/clang/17/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fmacro-prefix-map=/build/source/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fmacro-prefix-map=/build/source/= -fcoverage-prefix-map=/build/source/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fcoverage-prefix-map=/build/source/= -source-date-epoch 1683717183 -O2 -Wno-unused-command-line-argument -Wno-unused-parameter -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -Wno-maybe-uninitialized -Wno-class-memaccess -Wno-redundant-move -Wno-pessimizing-move -Wno-noexcept-type -Wno-comment -Wno-misleading-indentation -std=c++17 -fdeprecated-macro -fdebug-compilation-dir=/build/source/build-llvm/tools/clang/stage2-bins -fdebug-prefix-map=/build/source/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fdebug-prefix-map=/build/source/= -ferror-limit 19 -fvisibility-inlines-hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2023-05-10-133810-16478-1 -x c++ /build/source/clang-tools-extra/clangd/IncludeCleaner.cpp
1//===--- IncludeCleaner.cpp - Unused/Missing Headers Analysis ---*- C++ -*-===//
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 "IncludeCleaner.h"
10#include "Config.h"
11#include "Diagnostics.h"
12#include "Headers.h"
13#include "ParsedAST.h"
14#include "Preamble.h"
15#include "Protocol.h"
16#include "SourceCode.h"
17#include "URI.h"
18#include "clang-include-cleaner/Analysis.h"
19#include "clang-include-cleaner/Record.h"
20#include "clang-include-cleaner/Types.h"
21#include "support/Logger.h"
22#include "support/Path.h"
23#include "support/Trace.h"
24#include "clang/AST/ASTContext.h"
25#include "clang/AST/DeclCXX.h"
26#include "clang/AST/Expr.h"
27#include "clang/AST/ExprCXX.h"
28#include "clang/AST/TemplateName.h"
29#include "clang/AST/Type.h"
30#include "clang/Basic/Diagnostic.h"
31#include "clang/Basic/LLVM.h"
32#include "clang/Basic/SourceLocation.h"
33#include "clang/Basic/SourceManager.h"
34#include "clang/Format/Format.h"
35#include "clang/Lex/HeaderSearch.h"
36#include "clang/Lex/Preprocessor.h"
37#include "clang/Tooling/Core/Replacement.h"
38#include "clang/Tooling/Inclusions/HeaderIncludes.h"
39#include "clang/Tooling/Inclusions/StandardLibrary.h"
40#include "clang/Tooling/Syntax/Tokens.h"
41#include "llvm/ADT/ArrayRef.h"
42#include "llvm/ADT/DenseSet.h"
43#include "llvm/ADT/GenericUniformityImpl.h"
44#include "llvm/ADT/STLExtras.h"
45#include "llvm/ADT/STLFunctionalExtras.h"
46#include "llvm/ADT/SmallString.h"
47#include "llvm/ADT/SmallVector.h"
48#include "llvm/ADT/StringMap.h"
49#include "llvm/ADT/StringRef.h"
50#include "llvm/ADT/StringSet.h"
51#include "llvm/Support/Casting.h"
52#include "llvm/Support/Error.h"
53#include "llvm/Support/ErrorHandling.h"
54#include "llvm/Support/FormatVariadic.h"
55#include "llvm/Support/Path.h"
56#include "llvm/Support/Regex.h"
57#include <cassert>
58#include <iterator>
59#include <optional>
60#include <string>
61#include <utility>
62#include <vector>
63
64namespace clang {
65namespace clangd {
66
67static bool AnalyzeStdlib = false;
68void setIncludeCleanerAnalyzesStdlib(bool B) { AnalyzeStdlib = B; }
69
70namespace {
71
72// Returns the range starting at '#' and ending at EOL. Escaped newlines are not
73// handled.
74clangd::Range getDiagnosticRange(llvm::StringRef Code, unsigned HashOffset) {
75 clangd::Range Result;
76 Result.end = Result.start = offsetToPosition(Code, HashOffset);
77
78 // Span the warning until the EOL or EOF.
79 Result.end.character +=
80 lspLength(Code.drop_front(HashOffset).take_until([](char C) {
81 return C == '\n' || C == '\r';
82 }));
83 return Result;
84}
85
86bool isFilteredByConfig(const Config &Cfg, llvm::StringRef HeaderPath) {
87 // Convert the path to Unix slashes and try to match against the filter.
88 llvm::SmallString<64> NormalizedPath(HeaderPath);
89 llvm::sys::path::native(NormalizedPath, llvm::sys::path::Style::posix);
90 for (auto &Filter : Cfg.Diagnostics.Includes.IgnoreHeader) {
91 if (Filter(NormalizedPath))
92 return true;
93 }
94 return false;
95}
96
97static bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,
98 const Config &Cfg,
99 const include_cleaner::PragmaIncludes *PI) {
100 // FIXME(kirillbobyrev): We currently do not support the umbrella headers.
101 // System headers are likely to be standard library headers.
102 // Until we have good support for umbrella headers, don't warn about them.
103 if (Inc.Written.front() == '<') {
104 if (AnalyzeStdlib && tooling::stdlib::Header::named(Inc.Written))
105 return true;
106 return false;
107 }
108 assert(Inc.HeaderID)(static_cast <bool> (Inc.HeaderID) ? void (0) : __assert_fail
("Inc.HeaderID", "clang-tools-extra/clangd/IncludeCleaner.cpp"
, 108, __extension__ __PRETTY_FUNCTION__))
;
109 auto HID = static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID);
110 auto FE = AST.getSourceManager().getFileManager().getFileRef(
111 AST.getIncludeStructure().getRealPath(HID));
112 assert(FE)(static_cast <bool> (FE) ? void (0) : __assert_fail ("FE"
, "clang-tools-extra/clangd/IncludeCleaner.cpp", 112, __extension__
__PRETTY_FUNCTION__))
;
113 if (PI) {
114 if (PI->shouldKeep(Inc.HashLine + 1))
115 return false;
116 // Check if main file is the public interface for a private header. If so we
117 // shouldn't diagnose it as unused.
118 if (auto PHeader = PI->getPublic(*FE); !PHeader.empty()) {
119 PHeader = PHeader.trim("<>\"");
120 // Since most private -> public mappings happen in a verbatim way, we
121 // check textually here. This might go wrong in presence of symlinks or
122 // header mappings. But that's not different than rest of the places.
123 if (AST.tuPath().endswith(PHeader))
124 return false;
125 }
126 }
127 // Headers without include guards have side effects and are not
128 // self-contained, skip them.
129 if (!AST.getPreprocessor().getHeaderSearchInfo().isFileMultipleIncludeGuarded(
130 &FE->getFileEntry())) {
131 dlog("{0} doesn't have header guard and will not be considered unused",do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "{0} doesn't have header guard and will not be considered unused"
, FE->getName()); } } while (false)
132 FE->getName())do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "{0} doesn't have header guard and will not be considered unused"
, FE->getName()); } } while (false)
;
133 return false;
134 }
135
136 if (isFilteredByConfig(Cfg, Inc.Resolved)) {
137 dlog("{0} header is filtered out by the configuration", FE->getName())do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "{0} header is filtered out by the configuration"
, FE->getName()); } } while (false)
;
138 return false;
139 }
140 return true;
141}
142
143llvm::StringRef getResolvedPath(const include_cleaner::Header &SymProvider) {
144 switch (SymProvider.kind()) {
145 case include_cleaner::Header::Physical:
146 return SymProvider.physical()->tryGetRealPathName();
147 case include_cleaner::Header::Standard:
148 return SymProvider.standard().name().trim("<>\"");
149 case include_cleaner::Header::Verbatim:
150 return SymProvider.verbatim().trim("<>\"");
151 }
152 llvm_unreachable("Unknown header kind")::llvm::llvm_unreachable_internal("Unknown header kind", "clang-tools-extra/clangd/IncludeCleaner.cpp"
, 152)
;
153}
154
155std::string getSymbolName(const include_cleaner::Symbol &Sym) {
156 switch (Sym.kind()) {
20
Control jumps to 'case Declaration:' at line 159
157 case include_cleaner::Symbol::Macro:
158 return Sym.macro().Name->getName().str();
159 case include_cleaner::Symbol::Declaration:
160 return llvm::dyn_cast<NamedDecl>(&Sym.declaration())
21
Assuming the object is not a 'CastReturnType'
22
Called C++ object pointer is null
161 ->getQualifiedNameAsString();
162 }
163 llvm_unreachable("Unknown symbol kind")::llvm::llvm_unreachable_internal("Unknown symbol kind", "clang-tools-extra/clangd/IncludeCleaner.cpp"
, 163)
;
164}
165
166std::vector<Diag> generateMissingIncludeDiagnostics(
167 ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes,
168 llvm::StringRef Code) {
169 std::vector<Diag> Result;
170 const Config &Cfg = Config::current();
171 if (Cfg.Diagnostics.MissingIncludes != Config::IncludesPolicy::Strict ||
10
Assuming field 'MissingIncludes' is equal to Strict
13
Taking false branch
172 Cfg.Diagnostics.SuppressAll ||
11
Assuming field 'SuppressAll' is false
173 Cfg.Diagnostics.Suppress.contains("missing-includes")) {
12
Assuming the condition is false
174 return Result;
175 }
176
177 const SourceManager &SM = AST.getSourceManager();
178 const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
179
180 auto FileStyle = format::getStyle(
181 format::DefaultFormatStyle, AST.tuPath(), format::DefaultFallbackStyle,
182 Code, &SM.getFileManager().getVirtualFileSystem());
183 if (!FileStyle) {
14
Taking true branch
184 elog("Couldn't infer style", FileStyle.takeError());
185 FileStyle = format::getLLVMStyle();
186 }
187
188 tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code,
189 FileStyle->IncludeStyle);
190 for (const auto &SymbolWithMissingInclude : MissingIncludes) {
15
Assuming '__begin2' is not equal to '__end2'
191 llvm::StringRef ResolvedPath =
192 getResolvedPath(SymbolWithMissingInclude.Providers.front());
193 if (isFilteredByConfig(Cfg, ResolvedPath)) {
16
Taking false branch
194 dlog("IncludeCleaner: not diagnosing missing include {0}, filtered by "do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "IncludeCleaner: not diagnosing missing include {0}, filtered by "
"config", ResolvedPath); } } while (false)
195 "config",do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "IncludeCleaner: not diagnosing missing include {0}, filtered by "
"config", ResolvedPath); } } while (false)
196 ResolvedPath)do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "IncludeCleaner: not diagnosing missing include {0}, filtered by "
"config", ResolvedPath); } } while (false)
;
197 continue;
198 }
199
200 std::string Spelling =
201 spellHeader(AST, MainFile, SymbolWithMissingInclude.Providers.front());
202 llvm::StringRef HeaderRef{Spelling};
203 bool Angled = HeaderRef.starts_with("<");
204 // We might suggest insertion of an existing include in edge cases, e.g.,
205 // include is present in a PP-disabled region, or spelling of the header
206 // turns out to be the same as one of the unresolved includes in the
207 // main file.
208 std::optional<tooling::Replacement> Replacement = HeaderIncludes.insert(
209 HeaderRef.trim("\"<>"), Angled, tooling::IncludeDirective::Include);
210 if (!Replacement.has_value())
17
Assuming the condition is false
18
Taking false branch
211 continue;
212
213 Diag &D = Result.emplace_back();
214 D.Message =
215 llvm::formatv("No header providing \"{0}\" is directly included",
216 getSymbolName(SymbolWithMissingInclude.Symbol));
19
Calling 'getSymbolName'
217 D.Name = "missing-includes";
218 D.Source = Diag::DiagSource::Clangd;
219 D.File = AST.tuPath();
220 D.InsideMainFile = true;
221 D.Severity = DiagnosticsEngine::Warning;
222 D.Range = clangd::Range{
223 offsetToPosition(Code,
224 SymbolWithMissingInclude.SymRefRange.beginOffset()),
225 offsetToPosition(Code,
226 SymbolWithMissingInclude.SymRefRange.endOffset())};
227 auto &F = D.Fixes.emplace_back();
228 F.Message = "#include " + Spelling;
229 TextEdit Edit = replacementToEdit(Code, *Replacement);
230 F.Edits.emplace_back(std::move(Edit));
231 }
232 return Result;
233}
234
235std::vector<Diag> generateUnusedIncludeDiagnostics(
236 PathRef FileName, llvm::ArrayRef<const Inclusion *> UnusedIncludes,
237 llvm::StringRef Code) {
238 std::vector<Diag> Result;
239 const Config &Cfg = Config::current();
240 if (Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::None ||
241 Cfg.Diagnostics.SuppressAll ||
242 Cfg.Diagnostics.Suppress.contains("unused-includes")) {
243 return Result;
244 }
245 for (const auto *Inc : UnusedIncludes) {
246 Diag &D = Result.emplace_back();
247 D.Message =
248 llvm::formatv("included header {0} is not used directly",
249 llvm::sys::path::filename(
250 Inc->Written.substr(1, Inc->Written.size() - 2),
251 llvm::sys::path::Style::posix));
252 D.Name = "unused-includes";
253 D.Source = Diag::DiagSource::Clangd;
254 D.File = FileName;
255 D.InsideMainFile = true;
256 D.Severity = DiagnosticsEngine::Warning;
257 D.Tags.push_back(Unnecessary);
258 D.Range = getDiagnosticRange(Code, Inc->HashOffset);
259 // FIXME(kirillbobyrev): Removing inclusion might break the code if the
260 // used headers are only reachable transitively through this one. Suggest
261 // including them directly instead.
262 // FIXME(kirillbobyrev): Add fix suggestion for adding IWYU pragmas
263 // (keep/export) remove the warning once we support IWYU pragmas.
264 auto &F = D.Fixes.emplace_back();
265 F.Message = "remove #include directive";
266 F.Edits.emplace_back();
267 F.Edits.back().range.start.line = Inc->HashLine;
268 F.Edits.back().range.end.line = Inc->HashLine + 1;
269 }
270 return Result;
271}
272} // namespace
273
274std::vector<include_cleaner::SymbolReference>
275collectMacroReferences(ParsedAST &AST) {
276 const auto &SM = AST.getSourceManager();
277 // FIXME: !!this is a hacky way to collect macro references.
278 std::vector<include_cleaner::SymbolReference> Macros;
279 auto &PP = AST.getPreprocessor();
280 for (const syntax::Token &Tok :
281 AST.getTokens().spelledTokens(SM.getMainFileID())) {
282 auto Macro = locateMacroAt(Tok, PP);
283 if (!Macro)
284 continue;
285 if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid())
286 Macros.push_back(
287 {Tok.location(),
288 include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)),
289 DefLoc},
290 include_cleaner::RefType::Explicit});
291 }
292 return Macros;
293}
294
295include_cleaner::Includes
296convertIncludes(const SourceManager &SM,
297 const llvm::ArrayRef<Inclusion> Includes) {
298 include_cleaner::Includes ConvertedIncludes;
299 for (const Inclusion &Inc : Includes) {
300 include_cleaner::Include TransformedInc;
301 llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written);
302 TransformedInc.Spelled = WrittenRef.trim("\"<>");
303 TransformedInc.HashLocation =
304 SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset);
305 TransformedInc.Line = Inc.HashLine + 1;
306 TransformedInc.Angled = WrittenRef.starts_with("<");
307 auto FE = SM.getFileManager().getFile(Inc.Resolved);
308 if (!FE) {
309 elog("IncludeCleaner: Failed to get an entry for resolved path {0}: {1}",
310 Inc.Resolved, FE.getError().message());
311 continue;
312 }
313 TransformedInc.Resolved = *FE;
314 ConvertedIncludes.add(std::move(TransformedInc));
315 }
316 return ConvertedIncludes;
317}
318
319std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile,
320 include_cleaner::Header Provider) {
321 if (Provider.kind() == include_cleaner::Header::Physical) {
322 if (auto CanonicalPath = getCanonicalPath(Provider.physical()->getLastRef(),
323 AST.getSourceManager())) {
324 std::string SpelledHeader =
325 llvm::cantFail(URI::includeSpelling(URI::create(*CanonicalPath)));
326 if (!SpelledHeader.empty())
327 return SpelledHeader;
328 }
329 }
330 return include_cleaner::spellHeader(
331 Provider, AST.getPreprocessor().getHeaderSearchInfo(), MainFile);
332}
333
334std::vector<const Inclusion *>
335getUnused(ParsedAST &AST,
336 const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles,
337 const llvm::StringSet<> &ReferencedPublicHeaders) {
338 trace::Span Tracer("IncludeCleaner::getUnused");
339 const Config &Cfg = Config::current();
340 std::vector<const Inclusion *> Unused;
341 for (const Inclusion &MFI : AST.getIncludeStructure().MainFileIncludes) {
342 if (!MFI.HeaderID)
343 continue;
344 if (ReferencedPublicHeaders.contains(MFI.Written))
345 continue;
346 auto IncludeID = static_cast<IncludeStructure::HeaderID>(*MFI.HeaderID);
347 bool Used = ReferencedFiles.contains(IncludeID);
348 if (!Used && !mayConsiderUnused(MFI, AST, Cfg, AST.getPragmaIncludes())) {
349 dlog("{0} was not used, but is not eligible to be diagnosed as unused",do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "{0} was not used, but is not eligible to be diagnosed as unused"
, MFI.Written); } } while (false)
350 MFI.Written)do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "{0} was not used, but is not eligible to be diagnosed as unused"
, MFI.Written); } } while (false)
;
351 continue;
352 }
353 if (!Used)
354 Unused.push_back(&MFI);
355 dlog("{0} is {1}", MFI.Written, Used ? "USED" : "UNUSED")do { if (::llvm::DebugFlag && ::llvm::isCurrentDebugType
(::clang::clangd::detail::debugType("clang-tools-extra/clangd/IncludeCleaner.cpp"
))) { ::clang::clangd::detail::log(Logger::Debug, "{0} is {1}"
, MFI.Written, Used ? "USED" : "UNUSED"); } } while (false)
;
356 }
357 return Unused;
358}
359
360IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST) {
361 const auto &SM = AST.getSourceManager();
362 const auto &Includes = AST.getIncludeStructure();
363 include_cleaner::Includes ConvertedIncludes =
364 convertIncludes(SM, Includes.MainFileIncludes);
365 const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
366 auto *PreamblePatch = PreamblePatch::getPatchEntry(AST.tuPath(), SM);
367
368 std::vector<include_cleaner::SymbolReference> Macros =
369 collectMacroReferences(AST);
370 std::vector<MissingIncludeDiagInfo> MissingIncludes;
371 llvm::DenseSet<IncludeStructure::HeaderID> Used;
372 trace::Span Tracer("include_cleaner::walkUsed");
373 include_cleaner::walkUsed(
374 AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros,
375 AST.getPragmaIncludes(), SM,
376 [&](const include_cleaner::SymbolReference &Ref,
377 llvm::ArrayRef<include_cleaner::Header> Providers) {
378 bool Satisfied = false;
379 for (const auto &H : Providers) {
380 if (H.kind() == include_cleaner::Header::Physical &&
381 (H.physical() == MainFile || H.physical() == PreamblePatch)) {
382 Satisfied = true;
383 continue;
384 }
385 for (auto *Inc : ConvertedIncludes.match(H)) {
386 Satisfied = true;
387 auto HeaderID = Includes.getID(Inc->Resolved);
388 assert(HeaderID.has_value() &&(static_cast <bool> (HeaderID.has_value() && "ConvertedIncludes only contains resolved includes."
) ? void (0) : __assert_fail ("HeaderID.has_value() && \"ConvertedIncludes only contains resolved includes.\""
, "clang-tools-extra/clangd/IncludeCleaner.cpp", 389, __extension__
__PRETTY_FUNCTION__))
389 "ConvertedIncludes only contains resolved includes.")(static_cast <bool> (HeaderID.has_value() && "ConvertedIncludes only contains resolved includes."
) ? void (0) : __assert_fail ("HeaderID.has_value() && \"ConvertedIncludes only contains resolved includes.\""
, "clang-tools-extra/clangd/IncludeCleaner.cpp", 389, __extension__
__PRETTY_FUNCTION__))
;
390 Used.insert(*HeaderID);
391 }
392 }
393
394 if (Satisfied || Providers.empty() ||
395 Ref.RT != include_cleaner::RefType::Explicit)
396 return;
397
398 // We actually always want to map usages to their spellings, but
399 // spelling locations can point into preamble section. Using these
400 // offsets could lead into crashes in presence of stale preambles. Hence
401 // we use "getFileLoc" instead to make sure it always points into main
402 // file.
403 // FIXME: Use presumed locations to map such usages back to patched
404 // locations safely.
405 auto Loc = SM.getFileLoc(Ref.RefLocation);
406 // File locations can be outside of the main file if macro is expanded
407 // through an #include.
408 while (SM.getFileID(Loc) != SM.getMainFileID())
409 Loc = SM.getIncludeLoc(SM.getFileID(Loc));
410 auto TouchingTokens =
411 syntax::spelledTokensTouching(Loc, AST.getTokens());
412 assert(!TouchingTokens.empty())(static_cast <bool> (!TouchingTokens.empty()) ? void (0
) : __assert_fail ("!TouchingTokens.empty()", "clang-tools-extra/clangd/IncludeCleaner.cpp"
, 412, __extension__ __PRETTY_FUNCTION__))
;
413 // Loc points to the start offset of the ref token, here we use the last
414 // element of the TouchingTokens, e.g. avoid getting the "::" for
415 // "ns::^abc".
416 MissingIncludeDiagInfo DiagInfo{
417 Ref.Target, TouchingTokens.back().range(SM), Providers};
418 MissingIncludes.push_back(std::move(DiagInfo));
419 });
420 // Put possibly equal diagnostics together for deduplication.
421 // The duplicates might be from macro arguments that get expanded multiple
422 // times.
423 llvm::stable_sort(MissingIncludes, [](const MissingIncludeDiagInfo &LHS,
424 const MissingIncludeDiagInfo &RHS) {
425 // First sort by reference location.
426 if (LHS.SymRefRange != RHS.SymRefRange) {
427 // We can get away just by comparing the offsets as all the ranges are in
428 // main file.
429 return LHS.SymRefRange.beginOffset() < RHS.SymRefRange.beginOffset();
430 }
431 // For the same location, break ties using the symbol. Note that this won't
432 // be stable across runs.
433 using MapInfo = llvm::DenseMapInfo<include_cleaner::Symbol>;
434 return MapInfo::getHashValue(LHS.Symbol) <
435 MapInfo::getHashValue(RHS.Symbol);
436 });
437 MissingIncludes.erase(llvm::unique(MissingIncludes), MissingIncludes.end());
438 std::vector<const Inclusion *> UnusedIncludes =
439 getUnused(AST, Used, /*ReferencedPublicHeaders*/ {});
440 return {std::move(UnusedIncludes), std::move(MissingIncludes)};
441}
442
443Fix removeAllUnusedIncludes(llvm::ArrayRef<Diag> UnusedIncludes) {
444 assert(!UnusedIncludes.empty())(static_cast <bool> (!UnusedIncludes.empty()) ? void (0
) : __assert_fail ("!UnusedIncludes.empty()", "clang-tools-extra/clangd/IncludeCleaner.cpp"
, 444, __extension__ __PRETTY_FUNCTION__))
;
445
446 Fix RemoveAll;
447 RemoveAll.Message = "remove all unused includes";
448 for (const auto &Diag : UnusedIncludes) {
449 assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.")(static_cast <bool> (Diag.Fixes.size() == 1 && "Expected exactly one fix."
) ? void (0) : __assert_fail ("Diag.Fixes.size() == 1 && \"Expected exactly one fix.\""
, "clang-tools-extra/clangd/IncludeCleaner.cpp", 449, __extension__
__PRETTY_FUNCTION__))
;
450 RemoveAll.Edits.insert(RemoveAll.Edits.end(),
451 Diag.Fixes.front().Edits.begin(),
452 Diag.Fixes.front().Edits.end());
453 }
454
455 // TODO(hokein): emit a suitable text for the label.
456 ChangeAnnotation Annotation = {/*label=*/"",
457 /*needsConfirmation=*/true,
458 /*description=*/""};
459 static const ChangeAnnotationIdentifier RemoveAllUnusedID =
460 "RemoveAllUnusedIncludes";
461 for (unsigned I = 0; I < RemoveAll.Edits.size(); ++I) {
462 ChangeAnnotationIdentifier ID = RemoveAllUnusedID + std::to_string(I);
463 RemoveAll.Edits[I].annotationId = ID;
464 RemoveAll.Annotations.push_back({ID, Annotation});
465 }
466 return RemoveAll;
467}
468Fix addAllMissingIncludes(llvm::ArrayRef<Diag> MissingIncludeDiags) {
469 assert(!MissingIncludeDiags.empty())(static_cast <bool> (!MissingIncludeDiags.empty()) ? void
(0) : __assert_fail ("!MissingIncludeDiags.empty()", "clang-tools-extra/clangd/IncludeCleaner.cpp"
, 469, __extension__ __PRETTY_FUNCTION__))
;
470
471 Fix AddAllMissing;
472 AddAllMissing.Message = "add all missing includes";
473 // A map to deduplicate the edits with the same new text.
474 // newText (#include "my_missing_header.h") -> TextEdit.
475 llvm::StringMap<TextEdit> Edits;
476 for (const auto &Diag : MissingIncludeDiags) {
477 assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.")(static_cast <bool> (Diag.Fixes.size() == 1 && "Expected exactly one fix."
) ? void (0) : __assert_fail ("Diag.Fixes.size() == 1 && \"Expected exactly one fix.\""
, "clang-tools-extra/clangd/IncludeCleaner.cpp", 477, __extension__
__PRETTY_FUNCTION__))
;
478 for (const auto& Edit : Diag.Fixes.front().Edits) {
479 Edits.try_emplace(Edit.newText, Edit);
480 }
481 }
482 // FIXME(hokein): emit used symbol reference in the annotation.
483 ChangeAnnotation Annotation = {/*label=*/"",
484 /*needsConfirmation=*/true,
485 /*description=*/""};
486 static const ChangeAnnotationIdentifier AddAllMissingID =
487 "AddAllMissingIncludes";
488 unsigned I = 0;
489 for (auto &It : Edits) {
490 ChangeAnnotationIdentifier ID = AddAllMissingID + std::to_string(I++);
491 AddAllMissing.Edits.push_back(std::move(It.getValue()));
492 AddAllMissing.Edits.back().annotationId = ID;
493
494 AddAllMissing.Annotations.push_back({ID, Annotation});
495 }
496 return AddAllMissing;
497}
498Fix fixAll(const Fix& RemoveAllUnused, const Fix& AddAllMissing) {
499 Fix FixAll;
500 FixAll.Message = "fix all includes";
501
502 for (const auto &F : RemoveAllUnused.Edits)
503 FixAll.Edits.push_back(F);
504 for (const auto &F : AddAllMissing.Edits)
505 FixAll.Edits.push_back(F);
506
507 for (const auto& A : RemoveAllUnused.Annotations)
508 FixAll.Annotations.push_back(A);
509 for (const auto& A : AddAllMissing.Annotations)
510 FixAll.Annotations.push_back(A);
511 return FixAll;
512}
513
514std::vector<Diag> generateIncludeCleanerDiagnostic(
515 ParsedAST &AST, const IncludeCleanerFindings &Findings,
516 llvm::StringRef Code) {
517 std::vector<Diag> UnusedIncludes = generateUnusedIncludeDiagnostics(
518 AST.tuPath(), Findings.UnusedIncludes, Code);
519 std::optional<Fix> RemoveAllUnused;;
520 if (UnusedIncludes.size() > 1)
7
Assuming the condition is false
8
Taking false branch
521 RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes);
522
523 std::vector<Diag> MissingIncludeDiags = generateMissingIncludeDiagnostics(
9
Calling 'generateMissingIncludeDiagnostics'
524 AST, Findings.MissingIncludes, Code);
525 std::optional<Fix> AddAllMissing;
526 if (MissingIncludeDiags.size() > 1)
527 AddAllMissing = addAllMissingIncludes(MissingIncludeDiags);
528
529 std::optional<Fix> FixAll;
530 if (RemoveAllUnused && AddAllMissing)
531 FixAll = fixAll(*RemoveAllUnused, *AddAllMissing);
532
533 auto AddBatchFix = [](const std::optional<Fix> &F, clang::clangd::Diag *Out) {
534 if (!F) return;
535 Out->Fixes.push_back(*F);
536 };
537 for (auto &Diag : MissingIncludeDiags) {
538 AddBatchFix(AddAllMissing, &Diag);
539 AddBatchFix(FixAll, &Diag);
540 }
541 for (auto &Diag : UnusedIncludes) {
542 AddBatchFix(RemoveAllUnused, &Diag);
543 AddBatchFix(FixAll, &Diag);
544 }
545
546 auto Result = std::move(MissingIncludeDiags);
547 llvm::move(UnusedIncludes,
548 std::back_inserter(Result));
549 return Result;
550}
551
552std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
553 llvm::StringRef Code) {
554 // Interaction is only polished for C/CPP.
555 if (AST.getLangOpts().ObjC)
1
Assuming field 'ObjC' is 0
2
Taking false branch
556 return {};
557
558 trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics");
559
560 const Config &Cfg = Config::current();
561 IncludeCleanerFindings Findings;
562 if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict ||
3
Assuming field 'MissingIncludes' is not equal to Strict
5
Taking false branch
563 Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Strict) {
4
Assuming field 'UnusedIncludes' is not equal to Strict
564 // will need include-cleaner results, call it once
565 Findings = computeIncludeCleanerFindings(AST);
566 }
567 return generateIncludeCleanerDiagnostic(AST, Findings, Code);
6
Calling 'generateIncludeCleanerDiagnostic'
568}
569
570std::optional<include_cleaner::Header>
571firstMatchedProvider(const include_cleaner::Includes &Includes,
572 llvm::ArrayRef<include_cleaner::Header> Providers) {
573 for (const auto &H : Providers) {
574 if (!Includes.match(H).empty())
575 return H;
576 }
577 // No match for this provider in the includes list.
578 return std::nullopt;
579}
580} // namespace clangd
581} // namespace clang