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 | |
9 | #include "UnusedUsingDeclsCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/Decl.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | #include "clang/ASTMatchers/ASTMatchers.h" |
14 | #include "clang/Lex/Lexer.h" |
15 | |
16 | using namespace clang::ast_matchers; |
17 | |
18 | namespace clang::tidy::misc { |
19 | |
20 | namespace { |
21 | |
22 | AST_MATCHER_P(DeducedTemplateSpecializationType, refsToTemplatedDecl, |
23 | clang::ast_matchers::internal::Matcher<NamedDecl>, DeclMatcher) { |
24 | if (const auto *TD = Node.getTemplateName().getAsTemplateDecl()) |
25 | return DeclMatcher.matches(*TD, Finder, Builder); |
26 | return false; |
27 | } |
28 | |
29 | AST_MATCHER_P(Type, asTagDecl, clang::ast_matchers::internal::Matcher<TagDecl>, |
30 | DeclMatcher) { |
31 | if (const TagDecl *ND = Node.getAsTagDecl()) |
32 | return DeclMatcher.matches(Node: *ND, Finder, Builder); |
33 | return false; |
34 | } |
35 | |
36 | } // namespace |
37 | |
38 | // A function that helps to tell whether a TargetDecl in a UsingDecl will be |
39 | // checked. Only variable, function, function template, class template, class, |
40 | // enum declaration and enum constant declaration are considered. |
41 | static bool shouldCheckDecl(const Decl *TargetDecl) { |
42 | return isa<RecordDecl>(Val: TargetDecl) || isa<ClassTemplateDecl>(Val: TargetDecl) || |
43 | isa<FunctionDecl>(Val: TargetDecl) || isa<VarDecl>(Val: TargetDecl) || |
44 | isa<FunctionTemplateDecl>(Val: TargetDecl) || isa<EnumDecl>(Val: TargetDecl) || |
45 | isa<EnumConstantDecl>(Val: TargetDecl); |
46 | } |
47 | |
48 | UnusedUsingDeclsCheck::UnusedUsingDeclsCheck(StringRef Name, |
49 | ClangTidyContext *Context) |
50 | : ClangTidyCheck(Name, Context), |
51 | HeaderFileExtensions(Context->getHeaderFileExtensions()) {} |
52 | |
53 | void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) { |
54 | // We don't emit warnings on unused-using-decls from headers, so bail out if |
55 | // the main file is a header. |
56 | if (utils::isFileExtension(FileName: getCurrentMainFile(), FileExtensions: HeaderFileExtensions)) |
57 | return; |
58 | Finder->addMatcher(NodeMatch: usingDecl(isExpansionInMainFile()).bind(ID: "using" ), Action: this); |
59 | auto DeclMatcher = hasDeclaration(InnerMatcher: namedDecl().bind(ID: "used" )); |
60 | Finder->addMatcher(NodeMatch: loc(InnerMatcher: templateSpecializationType(DeclMatcher)), Action: this); |
61 | Finder->addMatcher(NodeMatch: loc(InnerMatcher: deducedTemplateSpecializationType( |
62 | refsToTemplatedDecl(DeclMatcher: namedDecl().bind(ID: "used" )))), |
63 | Action: this); |
64 | Finder->addMatcher(NodeMatch: callExpr(callee(InnerMatcher: unresolvedLookupExpr().bind(ID: "used" ))), |
65 | Action: this); |
66 | Finder->addMatcher( |
67 | NodeMatch: callExpr(hasDeclaration(InnerMatcher: functionDecl( |
68 | forEachTemplateArgument(InnerMatcher: templateArgument().bind(ID: "used" ))))), |
69 | Action: this); |
70 | Finder->addMatcher(NodeMatch: loc(InnerMatcher: templateSpecializationType(forEachTemplateArgument( |
71 | InnerMatcher: templateArgument().bind(ID: "used" )))), |
72 | Action: this); |
73 | Finder->addMatcher(NodeMatch: userDefinedLiteral().bind(ID: "used" ), Action: this); |
74 | Finder->addMatcher( |
75 | NodeMatch: loc(InnerMatcher: elaboratedType(unless(hasQualifier(InnerMatcher: nestedNameSpecifier())), |
76 | hasUnqualifiedDesugaredType( |
77 | InnerMatcher: type(asTagDecl(DeclMatcher: tagDecl().bind(ID: "used" )))))), |
78 | Action: this); |
79 | // Cases where we can identify the UsingShadowDecl directly, rather than |
80 | // just its target. |
81 | // FIXME: cover more cases in this way, as the AST supports it. |
82 | auto ThroughShadowMatcher = throughUsingDecl(Inner: namedDecl().bind(ID: "usedShadow" )); |
83 | Finder->addMatcher(NodeMatch: declRefExpr(ThroughShadowMatcher), Action: this); |
84 | Finder->addMatcher(NodeMatch: loc(InnerMatcher: usingType(ThroughShadowMatcher)), Action: this); |
85 | } |
86 | |
87 | void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) { |
88 | if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred()) |
89 | return; |
90 | |
91 | if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(ID: "using" )) { |
92 | // Ignores using-declarations defined in macros. |
93 | if (Using->getLocation().isMacroID()) |
94 | return; |
95 | |
96 | // Ignores using-declarations defined in class definition. |
97 | if (isa<CXXRecordDecl>(Using->getDeclContext())) |
98 | return; |
99 | |
100 | // FIXME: We ignore using-decls defined in function definitions at the |
101 | // moment because of false positives caused by ADL and different function |
102 | // scopes. |
103 | if (isa<FunctionDecl>(Using->getDeclContext())) |
104 | return; |
105 | |
106 | UsingDeclContext Context(Using); |
107 | Context.UsingDeclRange = CharSourceRange::getCharRange( |
108 | Using->getBeginLoc(), |
109 | Lexer::findLocationAfterToken( |
110 | loc: Using->getEndLoc(), TKind: tok::semi, SM: *Result.SourceManager, LangOpts: getLangOpts(), |
111 | /*SkipTrailingWhitespaceAndNewLine=*/true)); |
112 | for (const auto *UsingShadow : Using->shadows()) { |
113 | const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl(); |
114 | if (shouldCheckDecl(TargetDecl)) { |
115 | Context.UsingTargetDecls.insert(TargetDecl); |
116 | UsingTargetDeclsCache.insert(TargetDecl); |
117 | } |
118 | } |
119 | if (!Context.UsingTargetDecls.empty()) |
120 | Contexts.push_back(x: Context); |
121 | return; |
122 | } |
123 | |
124 | // Mark a corresponding using declaration as used. |
125 | auto RemoveNamedDecl = [&](const NamedDecl *Used) { |
126 | removeFromFoundDecls(Used); |
127 | // Also remove variants of Used. |
128 | if (const auto *FD = dyn_cast<FunctionDecl>(Val: Used)) { |
129 | removeFromFoundDecls(FD->getPrimaryTemplate()); |
130 | return; |
131 | } |
132 | if (const auto *Specialization = |
133 | dyn_cast<ClassTemplateSpecializationDecl>(Val: Used)) { |
134 | removeFromFoundDecls(Specialization->getSpecializedTemplate()); |
135 | return; |
136 | } |
137 | if (const auto *ECD = dyn_cast<EnumConstantDecl>(Val: Used)) { |
138 | if (const auto *ET = ECD->getType()->getAs<EnumType>()) |
139 | removeFromFoundDecls(D: ET->getDecl()); |
140 | } |
141 | }; |
142 | // We rely on the fact that the clang AST is walked in order, usages are only |
143 | // marked after a corresponding using decl has been found. |
144 | if (const auto *Used = Result.Nodes.getNodeAs<NamedDecl>(ID: "used" )) { |
145 | RemoveNamedDecl(Used); |
146 | return; |
147 | } |
148 | |
149 | if (const auto *UsedShadow = |
150 | Result.Nodes.getNodeAs<UsingShadowDecl>(ID: "usedShadow" )) { |
151 | removeFromFoundDecls(UsedShadow->getTargetDecl()); |
152 | return; |
153 | } |
154 | |
155 | if (const auto *Used = Result.Nodes.getNodeAs<TemplateArgument>(ID: "used" )) { |
156 | if (Used->getKind() == TemplateArgument::Template) { |
157 | if (const auto *TD = Used->getAsTemplate().getAsTemplateDecl()) |
158 | removeFromFoundDecls(TD); |
159 | return; |
160 | } |
161 | |
162 | if (Used->getKind() == TemplateArgument::Type) { |
163 | if (auto *RD = Used->getAsType()->getAsCXXRecordDecl()) |
164 | removeFromFoundDecls(RD); |
165 | return; |
166 | } |
167 | |
168 | if (Used->getKind() == TemplateArgument::Declaration) { |
169 | RemoveNamedDecl(Used->getAsDecl()); |
170 | } |
171 | return; |
172 | } |
173 | |
174 | if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>(ID: "used" )) { |
175 | RemoveNamedDecl(DRE->getDecl()); |
176 | return; |
177 | } |
178 | // Check the uninstantiated template function usage. |
179 | if (const auto *ULE = Result.Nodes.getNodeAs<UnresolvedLookupExpr>(ID: "used" )) { |
180 | for (const NamedDecl *ND : ULE->decls()) { |
181 | if (const auto *USD = dyn_cast<UsingShadowDecl>(ND)) |
182 | removeFromFoundDecls(USD->getTargetDecl()->getCanonicalDecl()); |
183 | } |
184 | return; |
185 | } |
186 | // Check user-defined literals |
187 | if (const auto *UDL = Result.Nodes.getNodeAs<UserDefinedLiteral>(ID: "used" )) { |
188 | const Decl *CalleeDecl = UDL->getCalleeDecl(); |
189 | if (const auto *FD = dyn_cast<FunctionDecl>(CalleeDecl)) { |
190 | if (const FunctionTemplateDecl *FPT = FD->getPrimaryTemplate()) { |
191 | removeFromFoundDecls(FPT); |
192 | return; |
193 | } |
194 | } |
195 | removeFromFoundDecls(D: CalleeDecl); |
196 | } |
197 | } |
198 | |
199 | void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) { |
200 | if (!D) |
201 | return; |
202 | const Decl *CanonicalDecl = D->getCanonicalDecl(); |
203 | if (!UsingTargetDeclsCache.contains(Ptr: CanonicalDecl)) |
204 | return; |
205 | // FIXME: Currently, we don't handle the using-decls being used in different |
206 | // scopes (such as different namespaces, different functions). Instead of |
207 | // giving an incorrect message, we mark all of them as used. |
208 | for (auto &Context : Contexts) { |
209 | if (Context.IsUsed) |
210 | continue; |
211 | if (Context.UsingTargetDecls.contains(Ptr: CanonicalDecl)) |
212 | Context.IsUsed = true; |
213 | } |
214 | } |
215 | |
216 | void UnusedUsingDeclsCheck::onEndOfTranslationUnit() { |
217 | for (const auto &Context : Contexts) { |
218 | if (!Context.IsUsed) { |
219 | diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused" ) |
220 | << Context.FoundUsingDecl; |
221 | // Emit a fix and a fix description of the check; |
222 | diag(Context.FoundUsingDecl->getLocation(), |
223 | /*Description=*/"remove the using" , DiagnosticIDs::Note) |
224 | << FixItHint::CreateRemoval(RemoveRange: Context.UsingDeclRange); |
225 | } |
226 | } |
227 | Contexts.clear(); |
228 | UsingTargetDeclsCache.clear(); |
229 | } |
230 | |
231 | } // namespace clang::tidy::misc |
232 | |