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