clang-tools 17.0.0git
UnusedUsingDeclsCheck.cpp
Go to the documentation of this file.
1//===--- UnusedUsingDeclsCheck.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 "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::misc {
17
18namespace {
19
20AST_MATCHER_P(DeducedTemplateSpecializationType, refsToTemplatedDecl,
21 clang::ast_matchers::internal::Matcher<NamedDecl>, DeclMatcher) {
22 if (const auto *TD = Node.getTemplateName().getAsTemplateDecl())
23 return DeclMatcher.matches(*TD, Finder, Builder);
24 return false;
25}
26
27} // namespace
28
29// A function that helps to tell whether a TargetDecl in a UsingDecl will be
30// checked. Only variable, function, function template, class template, class,
31// enum declaration and enum constant declaration are considered.
32static bool shouldCheckDecl(const Decl *TargetDecl) {
33 return isa<RecordDecl>(TargetDecl) || isa<ClassTemplateDecl>(TargetDecl) ||
34 isa<FunctionDecl>(TargetDecl) || isa<VarDecl>(TargetDecl) ||
35 isa<FunctionTemplateDecl>(TargetDecl) || isa<EnumDecl>(TargetDecl) ||
36 isa<EnumConstantDecl>(TargetDecl);
37}
38
40 ClangTidyContext *Context)
41 : ClangTidyCheck(Name, Context) {
42 std::optional<StringRef> HeaderFileExtensionsOption =
43 Options.get("HeaderFileExtensions");
44 RawStringHeaderFileExtensions =
45 HeaderFileExtensionsOption.value_or(utils::defaultHeaderFileExtensions());
46 if (HeaderFileExtensionsOption) {
47 if (!utils::parseFileExtensions(RawStringHeaderFileExtensions,
48 HeaderFileExtensions,
50 this->configurationDiag("Invalid header file extension: '%0'")
51 << RawStringHeaderFileExtensions;
52 }
53 } else
54 HeaderFileExtensions = Context->getHeaderFileExtensions();
55}
56
57void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) {
58 Finder->addMatcher(usingDecl(isExpansionInMainFile()).bind("using"), this);
59 auto DeclMatcher = hasDeclaration(namedDecl().bind("used"));
60 Finder->addMatcher(loc(templateSpecializationType(DeclMatcher)), this);
61 Finder->addMatcher(loc(deducedTemplateSpecializationType(
62 refsToTemplatedDecl(namedDecl().bind("used")))),
63 this);
64 Finder->addMatcher(callExpr(callee(unresolvedLookupExpr().bind("used"))),
65 this);
66 Finder->addMatcher(
67 callExpr(hasDeclaration(functionDecl(
68 forEachTemplateArgument(templateArgument().bind("used"))))),
69 this);
70 Finder->addMatcher(loc(templateSpecializationType(forEachTemplateArgument(
71 templateArgument().bind("used")))),
72 this);
73 Finder->addMatcher(userDefinedLiteral().bind("used"), this);
74 // Cases where we can identify the UsingShadowDecl directly, rather than
75 // just its target.
76 // FIXME: cover more cases in this way, as the AST supports it.
77 auto ThroughShadowMatcher = throughUsingDecl(namedDecl().bind("usedShadow"));
78 Finder->addMatcher(declRefExpr(ThroughShadowMatcher), this);
79 Finder->addMatcher(loc(usingType(ThroughShadowMatcher)), this);
80}
81
82void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
83 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
84 return;
85 // We don't emit warnings on unused-using-decls from headers, so bail out if
86 // the main file is a header.
87 if (const auto *MainFile = Result.SourceManager->getFileEntryForID(
88 Result.SourceManager->getMainFileID());
89 utils::isFileExtension(MainFile->getName(), HeaderFileExtensions))
90 return;
91
92 if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
93 // Ignores using-declarations defined in macros.
94 if (Using->getLocation().isMacroID())
95 return;
96
97 // Ignores using-declarations defined in class definition.
98 if (isa<CXXRecordDecl>(Using->getDeclContext()))
99 return;
100
101 // FIXME: We ignore using-decls defined in function definitions at the
102 // moment because of false positives caused by ADL and different function
103 // scopes.
104 if (isa<FunctionDecl>(Using->getDeclContext()))
105 return;
106
107 UsingDeclContext Context(Using);
108 Context.UsingDeclRange = CharSourceRange::getCharRange(
109 Using->getBeginLoc(),
110 Lexer::findLocationAfterToken(
111 Using->getEndLoc(), tok::semi, *Result.SourceManager, getLangOpts(),
112 /*SkipTrailingWhitespaceAndNewLine=*/true));
113 for (const auto *UsingShadow : Using->shadows()) {
114 const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl();
115 if (shouldCheckDecl(TargetDecl))
116 Context.UsingTargetDecls.insert(TargetDecl);
117 }
118 if (!Context.UsingTargetDecls.empty())
119 Contexts.push_back(Context);
120 return;
121 }
122
123 // Mark a corresponding using declaration as used.
124 auto RemoveNamedDecl = [&](const NamedDecl *Used) {
125 removeFromFoundDecls(Used);
126 // Also remove variants of Used.
127 if (const auto *FD = dyn_cast<FunctionDecl>(Used)) {
128 removeFromFoundDecls(FD->getPrimaryTemplate());
129 } else if (const auto *Specialization =
130 dyn_cast<ClassTemplateSpecializationDecl>(Used)) {
131 removeFromFoundDecls(Specialization->getSpecializedTemplate());
132 } else if (const auto *ECD = dyn_cast<EnumConstantDecl>(Used)) {
133 if (const auto *ET = ECD->getType()->getAs<EnumType>())
134 removeFromFoundDecls(ET->getDecl());
135 }
136 };
137 // We rely on the fact that the clang AST is walked in order, usages are only
138 // marked after a corresponding using decl has been found.
139 if (const auto *Used = Result.Nodes.getNodeAs<NamedDecl>("used")) {
140 RemoveNamedDecl(Used);
141 return;
142 }
143
144 if (const auto *UsedShadow =
145 Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) {
146 removeFromFoundDecls(UsedShadow->getTargetDecl());
147 return;
148 }
149
150 if (const auto *Used = Result.Nodes.getNodeAs<TemplateArgument>("used")) {
151 if (Used->getKind() == TemplateArgument::Template) {
152 if (const auto *TD = Used->getAsTemplate().getAsTemplateDecl())
153 removeFromFoundDecls(TD);
154 } else if (Used->getKind() == TemplateArgument::Type) {
155 if (auto *RD = Used->getAsType()->getAsCXXRecordDecl())
156 removeFromFoundDecls(RD);
157 } else if (Used->getKind() == TemplateArgument::Declaration) {
158 RemoveNamedDecl(Used->getAsDecl());
159 }
160 return;
161 }
162
163 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("used")) {
164 RemoveNamedDecl(DRE->getDecl());
165 return;
166 }
167 // Check the uninstantiated template function usage.
168 if (const auto *ULE = Result.Nodes.getNodeAs<UnresolvedLookupExpr>("used")) {
169 for (const NamedDecl *ND : ULE->decls()) {
170 if (const auto *USD = dyn_cast<UsingShadowDecl>(ND))
171 removeFromFoundDecls(USD->getTargetDecl()->getCanonicalDecl());
172 }
173 return;
174 }
175 // Check user-defined literals
176 if (const auto *UDL = Result.Nodes.getNodeAs<UserDefinedLiteral>("used"))
177 removeFromFoundDecls(UDL->getCalleeDecl());
178}
179
180void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) {
181 if (!D)
182 return;
183 // FIXME: Currently, we don't handle the using-decls being used in different
184 // scopes (such as different namespaces, different functions). Instead of
185 // giving an incorrect message, we mark all of them as used.
186 //
187 // FIXME: Use a more efficient way to find a matching context.
188 for (auto &Context : Contexts) {
189 if (Context.UsingTargetDecls.contains(D->getCanonicalDecl()))
190 Context.IsUsed = true;
191 }
192}
193
195 for (const auto &Context : Contexts) {
196 if (!Context.IsUsed) {
197 diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused")
198 << Context.FoundUsingDecl;
199 // Emit a fix and a fix description of the check;
200 diag(Context.FoundUsingDecl->getLocation(),
201 /*Description=*/"remove the using", DiagnosticIDs::Note)
202 << FixItHint::CreateRemoval(Context.UsingDeclRange);
203 }
204 }
205 Contexts.clear();
206}
207
208} // namespace clang::tidy::misc
const FunctionDecl * Decl
CodeCompletionBuilder Builder
std::string MainFile
Token Name
std::optional< StringRef > get(StringRef LocalName) const
Read a named option from the Context.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
const LangOptions & getLangOpts() const
Returns the language options from the context.
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 check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
UnusedUsingDeclsCheck(StringRef Name, ClangTidyContext *Context)
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N)
Matches functions that have at least the specified amount of parameters.
static bool shouldCheckDecl(const Decl *TargetDecl)
StringRef defaultHeaderFileExtensions()
Returns recommended default value for the list of header file extensions.
StringRef defaultFileExtensionDelimiters()
Returns recommended default value for the list of file extension delimiters.
bool isFileExtension(StringRef FileName, const FileExtensionsSet &FileExtensions)
Decides whether a file has one of the specified file extensions.
bool parseFileExtensions(StringRef AllFileExtensions, FileExtensionsSet &FileExtensions, StringRef Delimiters)
Parses header file extensions from a semicolon-separated list.