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/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace misc {
19 
20 namespace {
21 // FIXME: Move ASTMatcher library.
22 AST_POLYMORPHIC_MATCHER_P(
23     forEachTemplateArgument,
24     AST_POLYMORPHIC_SUPPORTED_TYPES(ClassTemplateSpecializationDecl,
25                                     TemplateSpecializationType, FunctionDecl),
26     clang::ast_matchers::internal::Matcher<TemplateArgument>, InnerMatcher) {
27   ArrayRef<TemplateArgument> TemplateArgs =
28       clang::ast_matchers::internal::getTemplateSpecializationArgs(Node);
29   clang::ast_matchers::internal::BoundNodesTreeBuilder Result;
30   bool Matched = false;
31   for (const auto &Arg : TemplateArgs) {
32     clang::ast_matchers::internal::BoundNodesTreeBuilder ArgBuilder(*Builder);
33     if (InnerMatcher.matches(Arg, Finder, &ArgBuilder)) {
34       Matched = true;
35       Result.addMatch(ArgBuilder);
36     }
37   }
38   *Builder = std::move(Result);
39   return Matched;
40 }
41 
42 AST_MATCHER_P(DeducedTemplateSpecializationType, refsToTemplatedDecl,
43               clang::ast_matchers::internal::Matcher<NamedDecl>, DeclMatcher) {
44   if (const auto *TD = Node.getTemplateName().getAsTemplateDecl())
45     return DeclMatcher.matches(*TD, Finder, Builder);
46   return false;
47 }
48 
49 } // namespace
50 
51 // A function that helps to tell whether a TargetDecl in a UsingDecl will be
52 // checked. Only variable, function, function template, class template, class,
53 // enum declaration and enum constant declaration are considered.
54 static bool shouldCheckDecl(const Decl *TargetDecl) {
55   return isa<RecordDecl>(TargetDecl) || isa<ClassTemplateDecl>(TargetDecl) ||
56          isa<FunctionDecl>(TargetDecl) || isa<VarDecl>(TargetDecl) ||
57          isa<FunctionTemplateDecl>(TargetDecl) || isa<EnumDecl>(TargetDecl) ||
58          isa<EnumConstantDecl>(TargetDecl);
59 }
60 
61 void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) {
62   Finder->addMatcher(usingDecl(isExpansionInMainFile()).bind("using"), this);
63   auto DeclMatcher = hasDeclaration(namedDecl().bind("used"));
64   Finder->addMatcher(loc(templateSpecializationType(DeclMatcher)), this);
65   Finder->addMatcher(loc(deducedTemplateSpecializationType(
66                          refsToTemplatedDecl(namedDecl().bind("used")))),
67                      this);
68   Finder->addMatcher(callExpr(callee(unresolvedLookupExpr().bind("used"))),
69                      this);
70   Finder->addMatcher(
71       callExpr(hasDeclaration(functionDecl(
72           forEachTemplateArgument(templateArgument().bind("used"))))),
73       this);
74   Finder->addMatcher(loc(templateSpecializationType(forEachTemplateArgument(
75                          templateArgument().bind("used")))),
76                      this);
77   // Cases where we can identify the UsingShadowDecl directly, rather than
78   // just its target.
79   // FIXME: cover more cases in this way, as the AST supports it.
80   auto ThroughShadowMatcher = throughUsingDecl(namedDecl().bind("usedShadow"));
81   Finder->addMatcher(declRefExpr(ThroughShadowMatcher), this);
82   Finder->addMatcher(loc(usingType(ThroughShadowMatcher)), this);
83 }
84 
85 void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
86   if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
87     return;
88 
89   if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
90     // Ignores using-declarations defined in macros.
91     if (Using->getLocation().isMacroID())
92       return;
93 
94     // Ignores using-declarations defined in class definition.
95     if (isa<CXXRecordDecl>(Using->getDeclContext()))
96       return;
97 
98     // FIXME: We ignore using-decls defined in function definitions at the
99     // moment because of false positives caused by ADL and different function
100     // scopes.
101     if (isa<FunctionDecl>(Using->getDeclContext()))
102       return;
103 
104     UsingDeclContext Context(Using);
105     Context.UsingDeclRange = CharSourceRange::getCharRange(
106         Using->getBeginLoc(),
107         Lexer::findLocationAfterToken(
108             Using->getEndLoc(), tok::semi, *Result.SourceManager, getLangOpts(),
109             /*SkipTrailingWhitespaceAndNewLine=*/true));
110     for (const auto *UsingShadow : Using->shadows()) {
111       const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl();
112       if (shouldCheckDecl(TargetDecl))
113         Context.UsingTargetDecls.insert(TargetDecl);
114     }
115     if (!Context.UsingTargetDecls.empty())
116       Contexts.push_back(Context);
117     return;
118   }
119 
120   // Mark a corresponding using declaration as used.
121   auto RemoveNamedDecl = [&](const NamedDecl *Used) {
122     removeFromFoundDecls(Used);
123     // Also remove variants of Used.
124     if (const auto *FD = dyn_cast<FunctionDecl>(Used)) {
125       removeFromFoundDecls(FD->getPrimaryTemplate());
126     } else if (const auto *Specialization =
127                    dyn_cast<ClassTemplateSpecializationDecl>(Used)) {
128       removeFromFoundDecls(Specialization->getSpecializedTemplate());
129     } else if (const auto *FD = dyn_cast<FunctionDecl>(Used)) {
130       if (const auto *FDT = FD->getPrimaryTemplate())
131         removeFromFoundDecls(FDT);
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   }
174 }
175 
176 void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) {
177   if (!D)
178     return;
179   // FIXME: Currently, we don't handle the using-decls being used in different
180   // scopes (such as different namespaces, different functions). Instead of
181   // giving an incorrect message, we mark all of them as used.
182   //
183   // FIXME: Use a more efficient way to find a matching context.
184   for (auto &Context : Contexts) {
185     if (Context.UsingTargetDecls.contains(D->getCanonicalDecl()))
186       Context.IsUsed = true;
187   }
188 }
189 
190 void UnusedUsingDeclsCheck::onEndOfTranslationUnit() {
191   for (const auto &Context : Contexts) {
192     if (!Context.IsUsed) {
193       diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused")
194           << Context.FoundUsingDecl;
195       // Emit a fix and a fix description of the check;
196       diag(Context.FoundUsingDecl->getLocation(),
197            /*Description=*/"remove the using", DiagnosticIDs::Note)
198           << FixItHint::CreateRemoval(Context.UsingDeclRange);
199     }
200   }
201   Contexts.clear();
202 }
203 
204 } // namespace misc
205 } // namespace tidy
206 } // namespace clang
207