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