1 //===--- UnusedParametersCheck.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 "UnusedParametersCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/AST/ASTLambda.h" 12 #include "clang/AST/Attr.h" 13 #include "clang/AST/Decl.h" 14 #include "clang/AST/RecursiveASTVisitor.h" 15 #include "clang/ASTMatchers/ASTMatchFinder.h" 16 #include "clang/Basic/SourceManager.h" 17 #include "clang/Lex/Lexer.h" 18 #include "llvm/ADT/STLExtras.h" 19 #include <unordered_map> 20 #include <unordered_set> 21 22 using namespace clang::ast_matchers; 23 24 namespace clang::tidy::misc { 25 26 namespace { 27 bool isOverrideMethod(const FunctionDecl *Function) { 28 if (const auto *MD = dyn_cast<CXXMethodDecl>(Function)) 29 return MD->size_overridden_methods() > 0 || MD->hasAttr<OverrideAttr>(); 30 return false; 31 } 32 33 bool hasAttrAfterParam(const SourceManager *SourceManager, 34 const ParmVarDecl *Param) { 35 for (const auto *Attr : Param->attrs()) { 36 if (SourceManager->isBeforeInTranslationUnit(Param->getLocation(), 37 Attr->getLocation())) { 38 return true; 39 } 40 } 41 return false; 42 } 43 } // namespace 44 45 void UnusedParametersCheck::registerMatchers(MatchFinder *Finder) { 46 Finder->addMatcher(functionDecl(isDefinition(), hasBody(stmt()), 47 hasAnyParameter(decl()), 48 unless(hasAttr(attr::Kind::Naked))) 49 .bind("function"), 50 this); 51 } 52 53 template <typename T> 54 static CharSourceRange removeNode(const MatchFinder::MatchResult &Result, 55 const T *PrevNode, const T *Node, 56 const T *NextNode) { 57 if (NextNode) 58 return CharSourceRange::getCharRange(Node->getBeginLoc(), 59 NextNode->getBeginLoc()); 60 61 if (PrevNode) 62 return CharSourceRange::getTokenRange( 63 Lexer::getLocForEndOfToken(PrevNode->getEndLoc(), 0, 64 *Result.SourceManager, 65 Result.Context->getLangOpts()), 66 Node->getEndLoc()); 67 68 return CharSourceRange::getTokenRange(Node->getSourceRange()); 69 } 70 71 static FixItHint removeParameter(const MatchFinder::MatchResult &Result, 72 const FunctionDecl *Function, unsigned Index) { 73 return FixItHint::CreateRemoval(removeNode( 74 Result, Index > 0 ? Function->getParamDecl(Index - 1) : nullptr, 75 Function->getParamDecl(Index), 76 Index + 1 < Function->getNumParams() ? Function->getParamDecl(Index + 1) 77 : nullptr)); 78 } 79 80 static FixItHint removeArgument(const MatchFinder::MatchResult &Result, 81 const CallExpr *Call, unsigned Index) { 82 return FixItHint::CreateRemoval(removeNode( 83 Result, Index > 0 ? Call->getArg(Index - 1) : nullptr, 84 Call->getArg(Index), 85 Index + 1 < Call->getNumArgs() ? Call->getArg(Index + 1) : nullptr)); 86 } 87 88 class UnusedParametersCheck::IndexerVisitor 89 : public RecursiveASTVisitor<IndexerVisitor> { 90 public: 91 IndexerVisitor(ASTContext &Ctx) { TraverseAST(Ctx); } 92 93 const std::unordered_set<const CallExpr *> & 94 getFnCalls(const FunctionDecl *Fn) { 95 return Index[Fn->getCanonicalDecl()].Calls; 96 } 97 98 const std::unordered_set<const DeclRefExpr *> & 99 getOtherRefs(const FunctionDecl *Fn) { 100 return Index[Fn->getCanonicalDecl()].OtherRefs; 101 } 102 103 bool shouldTraversePostOrder() const { return true; } 104 105 bool WalkUpFromDeclRefExpr(DeclRefExpr *DeclRef) { 106 if (const auto *Fn = dyn_cast<FunctionDecl>(DeclRef->getDecl())) { 107 Fn = Fn->getCanonicalDecl(); 108 Index[Fn].OtherRefs.insert(DeclRef); 109 } 110 return true; 111 } 112 113 bool WalkUpFromCallExpr(CallExpr *Call) { 114 if (const auto *Fn = 115 dyn_cast_or_null<FunctionDecl>(Call->getCalleeDecl())) { 116 Fn = Fn->getCanonicalDecl(); 117 if (const auto *Ref = 118 dyn_cast<DeclRefExpr>(Call->getCallee()->IgnoreImplicit())) { 119 Index[Fn].OtherRefs.erase(Ref); 120 } 121 Index[Fn].Calls.insert(Call); 122 } 123 return true; 124 } 125 126 private: 127 struct IndexEntry { 128 std::unordered_set<const CallExpr *> Calls; 129 std::unordered_set<const DeclRefExpr *> OtherRefs; 130 }; 131 132 std::unordered_map<const FunctionDecl *, IndexEntry> Index; 133 }; 134 135 UnusedParametersCheck::~UnusedParametersCheck() = default; 136 137 UnusedParametersCheck::UnusedParametersCheck(StringRef Name, 138 ClangTidyContext *Context) 139 : ClangTidyCheck(Name, Context), 140 StrictMode(Options.getLocalOrGlobal("StrictMode", false)), 141 IgnoreVirtual(Options.get("IgnoreVirtual", false)) {} 142 143 void UnusedParametersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 144 Options.store(Opts, "StrictMode", StrictMode); 145 Options.store(Opts, "IgnoreVirtual", IgnoreVirtual); 146 } 147 148 void UnusedParametersCheck::warnOnUnusedParameter( 149 const MatchFinder::MatchResult &Result, const FunctionDecl *Function, 150 unsigned ParamIndex) { 151 const auto *Param = Function->getParamDecl(ParamIndex); 152 // Don't bother to diagnose invalid parameters as being unused. 153 if (Param->isInvalidDecl()) 154 return; 155 auto MyDiag = diag(Param->getLocation(), "parameter %0 is unused") << Param; 156 157 if (!Indexer) { 158 Indexer = std::make_unique<IndexerVisitor>(*Result.Context); 159 } 160 161 // Cannot remove parameter for non-local functions. 162 if (Function->isExternallyVisible() || 163 !Result.SourceManager->isInMainFile(Function->getLocation()) || 164 !Indexer->getOtherRefs(Function).empty() || isOverrideMethod(Function) || 165 isLambdaCallOperator(Function)) { 166 167 // It is illegal to omit parameter name here in C code, so early-out. 168 if (!Result.Context->getLangOpts().CPlusPlus) 169 return; 170 171 SourceRange RemovalRange(Param->getLocation()); 172 // Note: We always add a space before the '/*' to not accidentally create 173 // a '*/*' for pointer types, which doesn't start a comment. clang-format 174 // will clean this up afterwards. 175 MyDiag << FixItHint::CreateReplacement( 176 RemovalRange, (Twine(" /*") + Param->getName() + "*/").str()); 177 return; 178 } 179 180 // Fix all redeclarations. 181 for (const FunctionDecl *FD : Function->redecls()) 182 if (FD->param_size()) 183 MyDiag << removeParameter(Result, FD, ParamIndex); 184 185 // Fix all call sites. 186 for (const CallExpr *Call : Indexer->getFnCalls(Function)) 187 if (ParamIndex < Call->getNumArgs()) // See PR38055 for example. 188 MyDiag << removeArgument(Result, Call, ParamIndex); 189 } 190 191 void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) { 192 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function"); 193 if (!Function->hasWrittenPrototype() || Function->isTemplateInstantiation()) 194 return; 195 if (const auto *Method = dyn_cast<CXXMethodDecl>(Function)) { 196 if (IgnoreVirtual && Method->isVirtual()) 197 return; 198 if (Method->isLambdaStaticInvoker()) 199 return; 200 } 201 for (unsigned I = 0, E = Function->getNumParams(); I != E; ++I) { 202 const auto *Param = Function->getParamDecl(I); 203 if (Param->isUsed() || Param->isReferenced() || !Param->getDeclName() || 204 Param->hasAttr<UnusedAttr>()) 205 continue; 206 if (hasAttrAfterParam(Result.SourceManager, Param)) { 207 // Due to how grammar works, attributes would be wrongly applied to the 208 // type if we remove the preceding parameter name. 209 continue; 210 } 211 212 // In non-strict mode ignore function definitions with empty bodies 213 // (constructor initializer counts for non-empty body). 214 if (StrictMode || !Function->getBody()->children().empty() || 215 (isa<CXXConstructorDecl>(Function) && 216 cast<CXXConstructorDecl>(Function)->getNumCtorInitializers() > 0)) 217 warnOnUnusedParameter(Result, Function, I); 218 } 219 } 220 221 } // namespace clang::tidy::misc 222