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