xref: /llvm-project/clang-tools-extra/clang-tidy/misc/UnusedParametersCheck.cpp (revision 1b897f737df2097f8fee1b203676ea7f01dff06d)
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