1 //===--- UnusedParametersCheck.cpp - clang-tidy----------------------------===//
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
7 //===----------------------------------------------------------------------===//
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>
19 using namespace clang::ast_matchers
;
21 namespace clang::tidy::misc
{
24 bool isOverrideMethod(const FunctionDecl
*Function
) {
25 if (const auto *MD
= dyn_cast
<CXXMethodDecl
>(Function
))
26 return MD
->size_overridden_methods() > 0 || MD
->hasAttr
<OverrideAttr
>();
31 void UnusedParametersCheck::registerMatchers(MatchFinder
*Finder
) {
32 Finder
->addMatcher(functionDecl(isDefinition(), hasBody(stmt()),
33 hasAnyParameter(decl()),
34 unless(hasAttr(attr::Kind::Naked
)))
40 static CharSourceRange
removeNode(const MatchFinder::MatchResult
&Result
,
41 const T
*PrevNode
, const T
*Node
,
44 return CharSourceRange::getCharRange(Node
->getBeginLoc(),
45 NextNode
->getBeginLoc());
48 return CharSourceRange::getTokenRange(
49 Lexer::getLocForEndOfToken(PrevNode
->getEndLoc(), 0,
50 *Result
.SourceManager
,
51 Result
.Context
->getLangOpts()),
54 return CharSourceRange::getTokenRange(Node
->getSourceRange());
57 static FixItHint
removeParameter(const MatchFinder::MatchResult
&Result
,
58 const FunctionDecl
*Function
, unsigned Index
) {
59 return FixItHint::CreateRemoval(removeNode(
60 Result
, Index
> 0 ? Function
->getParamDecl(Index
- 1) : nullptr,
61 Function
->getParamDecl(Index
),
62 Index
+ 1 < Function
->getNumParams() ? Function
->getParamDecl(Index
+ 1)
66 static FixItHint
removeArgument(const MatchFinder::MatchResult
&Result
,
67 const CallExpr
*Call
, unsigned Index
) {
68 return FixItHint::CreateRemoval(removeNode(
69 Result
, Index
> 0 ? Call
->getArg(Index
- 1) : nullptr,
71 Index
+ 1 < Call
->getNumArgs() ? Call
->getArg(Index
+ 1) : nullptr));
74 class UnusedParametersCheck::IndexerVisitor
75 : public RecursiveASTVisitor
<IndexerVisitor
> {
77 IndexerVisitor(ASTContext
&Ctx
) { TraverseAST(Ctx
); }
79 const std::unordered_set
<const CallExpr
*> &
80 getFnCalls(const FunctionDecl
*Fn
) {
81 return Index
[Fn
->getCanonicalDecl()].Calls
;
84 const std::unordered_set
<const DeclRefExpr
*> &
85 getOtherRefs(const FunctionDecl
*Fn
) {
86 return Index
[Fn
->getCanonicalDecl()].OtherRefs
;
89 bool shouldTraversePostOrder() const { return true; }
91 bool WalkUpFromDeclRefExpr(DeclRefExpr
*DeclRef
) {
92 if (const auto *Fn
= dyn_cast
<FunctionDecl
>(DeclRef
->getDecl())) {
93 Fn
= Fn
->getCanonicalDecl();
94 Index
[Fn
].OtherRefs
.insert(DeclRef
);
99 bool WalkUpFromCallExpr(CallExpr
*Call
) {
101 dyn_cast_or_null
<FunctionDecl
>(Call
->getCalleeDecl())) {
102 Fn
= Fn
->getCanonicalDecl();
103 if (const auto *Ref
=
104 dyn_cast
<DeclRefExpr
>(Call
->getCallee()->IgnoreImplicit())) {
105 Index
[Fn
].OtherRefs
.erase(Ref
);
107 Index
[Fn
].Calls
.insert(Call
);
114 std::unordered_set
<const CallExpr
*> Calls
;
115 std::unordered_set
<const DeclRefExpr
*> OtherRefs
;
118 std::unordered_map
<const FunctionDecl
*, IndexEntry
> Index
;
121 UnusedParametersCheck::~UnusedParametersCheck() = default;
123 UnusedParametersCheck::UnusedParametersCheck(StringRef Name
,
124 ClangTidyContext
*Context
)
125 : ClangTidyCheck(Name
, Context
),
126 StrictMode(Options
.getLocalOrGlobal("StrictMode", false)),
127 IgnoreVirtual(Options
.get("IgnoreVirtual", false)) {}
129 void UnusedParametersCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
130 Options
.store(Opts
, "StrictMode", StrictMode
);
131 Options
.store(Opts
, "IgnoreVirtual", IgnoreVirtual
);
134 void UnusedParametersCheck::warnOnUnusedParameter(
135 const MatchFinder::MatchResult
&Result
, const FunctionDecl
*Function
,
136 unsigned ParamIndex
) {
137 const auto *Param
= Function
->getParamDecl(ParamIndex
);
138 // Don't bother to diagnose invalid parameters as being unused.
139 if (Param
->isInvalidDecl())
141 auto MyDiag
= diag(Param
->getLocation(), "parameter %0 is unused") << Param
;
144 Indexer
= std::make_unique
<IndexerVisitor
>(*Result
.Context
);
147 // Cannot remove parameter for non-local functions.
148 if (Function
->isExternallyVisible() ||
149 !Result
.SourceManager
->isInMainFile(Function
->getLocation()) ||
150 !Indexer
->getOtherRefs(Function
).empty() || isOverrideMethod(Function
) ||
151 isLambdaCallOperator(Function
)) {
153 // It is illegal to omit parameter name here in C code, so early-out.
154 if (!Result
.Context
->getLangOpts().CPlusPlus
)
157 SourceRange
RemovalRange(Param
->getLocation());
158 // Note: We always add a space before the '/*' to not accidentally create
159 // a '*/*' for pointer types, which doesn't start a comment. clang-format
160 // will clean this up afterwards.
161 MyDiag
<< FixItHint::CreateReplacement(
162 RemovalRange
, (Twine(" /*") + Param
->getName() + "*/").str());
166 // Fix all redeclarations.
167 for (const FunctionDecl
*FD
: Function
->redecls())
168 if (FD
->param_size())
169 MyDiag
<< removeParameter(Result
, FD
, ParamIndex
);
171 // Fix all call sites.
172 for (const CallExpr
*Call
: Indexer
->getFnCalls(Function
))
173 if (ParamIndex
< Call
->getNumArgs()) // See PR38055 for example.
174 MyDiag
<< removeArgument(Result
, Call
, ParamIndex
);
177 void UnusedParametersCheck::check(const MatchFinder::MatchResult
&Result
) {
178 const auto *Function
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("function");
179 if (!Function
->hasWrittenPrototype() || Function
->isTemplateInstantiation())
181 if (const auto *Method
= dyn_cast
<CXXMethodDecl
>(Function
)) {
182 if (IgnoreVirtual
&& Method
->isVirtual())
184 if (Method
->isLambdaStaticInvoker())
187 for (unsigned I
= 0, E
= Function
->getNumParams(); I
!= E
; ++I
) {
188 const auto *Param
= Function
->getParamDecl(I
);
189 if (Param
->isUsed() || Param
->isReferenced() || !Param
->getDeclName() ||
190 Param
->hasAttr
<UnusedAttr
>())
193 // In non-strict mode ignore function definitions with empty bodies
194 // (constructor initializer counts for non-empty body).
195 if (StrictMode
|| !Function
->getBody()->children().empty() ||
196 (isa
<CXXConstructorDecl
>(Function
) &&
197 cast
<CXXConstructorDecl
>(Function
)->getNumCtorInitializers() > 0))
198 warnOnUnusedParameter(Result
, Function
, I
);
202 } // namespace clang::tidy::misc