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(*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>(TargetDecl) || isa<ClassTemplateDecl>(TargetDecl) || 43 isa<FunctionDecl>(TargetDecl) || isa<VarDecl>(TargetDecl) || 44 isa<FunctionTemplateDecl>(TargetDecl) || isa<EnumDecl>(TargetDecl) || 45 isa<EnumConstantDecl>(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(getCurrentMainFile(), HeaderFileExtensions)) 57 return; 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 Finder->addMatcher( 75 loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())), 76 hasUnqualifiedDesugaredType( 77 type(asTagDecl(tagDecl().bind("used")))))), 78 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(namedDecl().bind("usedShadow")); 83 Finder->addMatcher(declRefExpr(ThroughShadowMatcher), this); 84 Finder->addMatcher(loc(usingType(ThroughShadowMatcher)), 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>("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 Using->getEndLoc(), tok::semi, *Result.SourceManager, 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(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>(Used)) { 129 removeFromFoundDecls(FD->getPrimaryTemplate()); 130 return; 131 } 132 if (const auto *Specialization = 133 dyn_cast<ClassTemplateSpecializationDecl>(Used)) { 134 removeFromFoundDecls(Specialization->getSpecializedTemplate()); 135 return; 136 } 137 if (const auto *ECD = dyn_cast<EnumConstantDecl>(Used)) { 138 if (const auto *ET = ECD->getType()->getAs<EnumType>()) 139 removeFromFoundDecls(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>("used")) { 145 RemoveNamedDecl(Used); 146 return; 147 } 148 149 if (const auto *UsedShadow = 150 Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) { 151 removeFromFoundDecls(UsedShadow->getTargetDecl()); 152 return; 153 } 154 155 if (const auto *Used = Result.Nodes.getNodeAs<TemplateArgument>("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>("used")) { 175 RemoveNamedDecl(DRE->getDecl()); 176 return; 177 } 178 // Check the uninstantiated template function usage. 179 if (const auto *ULE = Result.Nodes.getNodeAs<UnresolvedLookupExpr>("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>("used")) 188 removeFromFoundDecls(UDL->getCalleeDecl()); 189 } 190 191 void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) { 192 if (!D) 193 return; 194 const Decl *CanonicalDecl = D->getCanonicalDecl(); 195 if (!UsingTargetDeclsCache.contains(CanonicalDecl)) 196 return; 197 // FIXME: Currently, we don't handle the using-decls being used in different 198 // scopes (such as different namespaces, different functions). Instead of 199 // giving an incorrect message, we mark all of them as used. 200 for (auto &Context : Contexts) { 201 if (Context.IsUsed) 202 continue; 203 if (Context.UsingTargetDecls.contains(CanonicalDecl)) 204 Context.IsUsed = true; 205 } 206 } 207 208 void UnusedUsingDeclsCheck::onEndOfTranslationUnit() { 209 for (const auto &Context : Contexts) { 210 if (!Context.IsUsed) { 211 diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused") 212 << Context.FoundUsingDecl; 213 // Emit a fix and a fix description of the check; 214 diag(Context.FoundUsingDecl->getLocation(), 215 /*Description=*/"remove the using", DiagnosticIDs::Note) 216 << FixItHint::CreateRemoval(Context.UsingDeclRange); 217 } 218 } 219 Contexts.clear(); 220 UsingTargetDeclsCache.clear(); 221 } 222 223 } // namespace clang::tidy::misc 224