//===--- UnusedParametersCheck.cpp - clang-tidy----------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "UnusedParametersCheck.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/STLExtras.h" #include #include using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace misc { namespace { bool isOverrideMethod(const FunctionDecl *Function) { if (const auto *MD = dyn_cast(Function)) return MD->size_overridden_methods() > 0 || MD->hasAttr(); return false; } } // namespace void UnusedParametersCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( functionDecl(isDefinition(), hasBody(stmt()), hasAnyParameter(decl())) .bind("function"), this); } template static CharSourceRange removeNode(const MatchFinder::MatchResult &Result, const T *PrevNode, const T *Node, const T *NextNode) { if (NextNode) return CharSourceRange::getCharRange(Node->getBeginLoc(), NextNode->getBeginLoc()); if (PrevNode) return CharSourceRange::getTokenRange( Lexer::getLocForEndOfToken(PrevNode->getEndLoc(), 0, *Result.SourceManager, Result.Context->getLangOpts()), Node->getEndLoc()); return CharSourceRange::getTokenRange(Node->getSourceRange()); } static FixItHint removeParameter(const MatchFinder::MatchResult &Result, const FunctionDecl *Function, unsigned Index) { return FixItHint::CreateRemoval(removeNode( Result, Index > 0 ? Function->getParamDecl(Index - 1) : nullptr, Function->getParamDecl(Index), Index + 1 < Function->getNumParams() ? Function->getParamDecl(Index + 1) : nullptr)); } static FixItHint removeArgument(const MatchFinder::MatchResult &Result, const CallExpr *Call, unsigned Index) { return FixItHint::CreateRemoval(removeNode( Result, Index > 0 ? Call->getArg(Index - 1) : nullptr, Call->getArg(Index), Index + 1 < Call->getNumArgs() ? Call->getArg(Index + 1) : nullptr)); } class UnusedParametersCheck::IndexerVisitor : public RecursiveASTVisitor { public: IndexerVisitor(ASTContext &Ctx) { TraverseAST(Ctx); } const std::unordered_set & getFnCalls(const FunctionDecl *Fn) { return Index[Fn->getCanonicalDecl()].Calls; } const std::unordered_set & getOtherRefs(const FunctionDecl *Fn) { return Index[Fn->getCanonicalDecl()].OtherRefs; } bool shouldTraversePostOrder() const { return true; } bool WalkUpFromDeclRefExpr(DeclRefExpr *DeclRef) { if (const auto *Fn = dyn_cast(DeclRef->getDecl())) { Fn = Fn->getCanonicalDecl(); Index[Fn].OtherRefs.insert(DeclRef); } return true; } bool WalkUpFromCallExpr(CallExpr *Call) { if (const auto *Fn = dyn_cast_or_null(Call->getCalleeDecl())) { Fn = Fn->getCanonicalDecl(); if (const auto *Ref = dyn_cast(Call->getCallee()->IgnoreImplicit())) { Index[Fn].OtherRefs.erase(Ref); } Index[Fn].Calls.insert(Call); } return true; } private: struct IndexEntry { std::unordered_set Calls; std::unordered_set OtherRefs; }; std::unordered_map Index; }; UnusedParametersCheck::~UnusedParametersCheck() = default; UnusedParametersCheck::UnusedParametersCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), StrictMode(Options.getLocalOrGlobal("StrictMode", false)) {} void UnusedParametersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "StrictMode", StrictMode); } void UnusedParametersCheck::warnOnUnusedParameter( const MatchFinder::MatchResult &Result, const FunctionDecl *Function, unsigned ParamIndex) { const auto *Param = Function->getParamDecl(ParamIndex); auto MyDiag = diag(Param->getLocation(), "parameter %0 is unused") << Param; if (!Indexer) { Indexer = std::make_unique(*Result.Context); } // Cannot remove parameter for non-local functions. if (Function->isExternallyVisible() || !Result.SourceManager->isInMainFile(Function->getLocation()) || !Indexer->getOtherRefs(Function).empty() || isOverrideMethod(Function) || isLambdaCallOperator(Function)) { // It is illegal to omit parameter name here in C code, so early-out. if (!Result.Context->getLangOpts().CPlusPlus) return; SourceRange RemovalRange(Param->getLocation()); // Note: We always add a space before the '/*' to not accidentally create // a '*/*' for pointer types, which doesn't start a comment. clang-format // will clean this up afterwards. MyDiag << FixItHint::CreateReplacement( RemovalRange, (Twine(" /*") + Param->getName() + "*/").str()); return; } // Fix all redeclarations. for (const FunctionDecl *FD : Function->redecls()) if (FD->param_size()) MyDiag << removeParameter(Result, FD, ParamIndex); // Fix all call sites. for (const CallExpr *Call : Indexer->getFnCalls(Function)) if (ParamIndex < Call->getNumArgs()) // See PR38055 for example. MyDiag << removeArgument(Result, Call, ParamIndex); } void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) { const auto *Function = Result.Nodes.getNodeAs("function"); if (!Function->hasWrittenPrototype() || Function->isTemplateInstantiation()) return; if (const auto *Method = dyn_cast(Function)) if (Method->isLambdaStaticInvoker()) return; for (unsigned i = 0, e = Function->getNumParams(); i != e; ++i) { const auto *Param = Function->getParamDecl(i); if (Param->isUsed() || Param->isReferenced() || !Param->getDeclName() || Param->hasAttr()) continue; // In non-strict mode ignore function definitions with empty bodies // (constructor initializer counts for non-empty body). if (StrictMode || (Function->getBody()->child_begin() != Function->getBody()->child_end()) || (isa(Function) && cast(Function)->getNumCtorInitializers() > 0)) warnOnUnusedParameter(Result, Function, i); } } } // namespace misc } // namespace tidy } // namespace clang