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