1 //===--- UnnecessaryValueParamCheck.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 "UnnecessaryValueParamCheck.h" 10 11 #include "../utils/DeclRefExprUtils.h" 12 #include "../utils/FixItHintUtils.h" 13 #include "../utils/Matchers.h" 14 #include "../utils/OptionsUtils.h" 15 #include "../utils/TypeTraits.h" 16 #include "clang/Frontend/CompilerInstance.h" 17 #include "clang/Lex/Lexer.h" 18 #include "clang/Lex/Preprocessor.h" 19 #include <optional> 20 21 using namespace clang::ast_matchers; 22 23 namespace clang::tidy::performance { 24 25 namespace { 26 27 std::string paramNameOrIndex(StringRef Name, size_t Index) { 28 return (Name.empty() ? llvm::Twine('#') + llvm::Twine(Index + 1) 29 : llvm::Twine('\'') + Name + llvm::Twine('\'')) 30 .str(); 31 } 32 33 bool isReferencedOutsideOfCallExpr(const FunctionDecl &Function, 34 ASTContext &Context) { 35 auto Matches = match(declRefExpr(to(functionDecl(equalsNode(&Function))), 36 unless(hasAncestor(callExpr()))), 37 Context); 38 return !Matches.empty(); 39 } 40 41 bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, 42 ASTContext &Context) { 43 auto Matches = match( 44 traverse(TK_AsIs, 45 decl(forEachDescendant(declRefExpr( 46 equalsNode(&DeclRef), 47 unless(hasAncestor(stmt(anyOf(forStmt(), cxxForRangeStmt(), 48 whileStmt(), doStmt())))))))), 49 Decl, Context); 50 return Matches.empty(); 51 } 52 53 } // namespace 54 55 UnnecessaryValueParamCheck::UnnecessaryValueParamCheck( 56 StringRef Name, ClangTidyContext *Context) 57 : ClangTidyCheck(Name, Context), 58 Inserter(Options.getLocalOrGlobal("IncludeStyle", 59 utils::IncludeSorter::IS_LLVM), 60 areDiagsSelfContained()), 61 AllowedTypes( 62 utils::options::parseStringList(Options.get("AllowedTypes", ""))) {} 63 64 void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) { 65 const auto ExpensiveValueParamDecl = parmVarDecl( 66 hasType(qualType( 67 hasCanonicalType(matchers::isExpensiveToCopy()), 68 unless(anyOf(hasCanonicalType(referenceType()), 69 hasDeclaration(namedDecl( 70 matchers::matchesAnyListedName(AllowedTypes))))))), 71 decl().bind("param")); 72 Finder->addMatcher( 73 traverse( 74 TK_AsIs, 75 functionDecl(hasBody(stmt()), isDefinition(), unless(isImplicit()), 76 unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))), 77 has(typeLoc(forEach(ExpensiveValueParamDecl))), 78 decl().bind("functionDecl"))), 79 this); 80 } 81 82 void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) { 83 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param"); 84 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("functionDecl"); 85 86 TraversalKindScope RAII(*Result.Context, TK_AsIs); 87 88 FunctionParmMutationAnalyzer *Analyzer = 89 FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer( 90 *Function, *Result.Context, MutationAnalyzerCache); 91 if (Analyzer->isMutated(Param)) 92 return; 93 94 const bool IsConstQualified = 95 Param->getType().getCanonicalType().isConstQualified(); 96 97 // If the parameter is non-const, check if it has a move constructor and is 98 // only referenced once to copy-construct another object or whether it has a 99 // move assignment operator and is only referenced once when copy-assigned. 100 // In this case wrap DeclRefExpr with std::move() to avoid the unnecessary 101 // copy. 102 if (!IsConstQualified) { 103 auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs( 104 *Param, *Function, *Result.Context); 105 if (AllDeclRefExprs.size() == 1) { 106 auto CanonicalType = Param->getType().getCanonicalType(); 107 const auto &DeclRefExpr = **AllDeclRefExprs.begin(); 108 109 if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) && 110 ((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) && 111 utils::decl_ref_expr::isCopyConstructorArgument( 112 DeclRefExpr, *Function, *Result.Context)) || 113 (utils::type_traits::hasNonTrivialMoveAssignment(CanonicalType) && 114 utils::decl_ref_expr::isCopyAssignmentArgument( 115 DeclRefExpr, *Function, *Result.Context)))) { 116 handleMoveFix(*Param, DeclRefExpr, *Result.Context); 117 return; 118 } 119 } 120 } 121 122 handleConstRefFix(*Function, *Param, *Result.Context); 123 } 124 125 void UnnecessaryValueParamCheck::registerPPCallbacks( 126 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { 127 Inserter.registerPreprocessor(PP); 128 } 129 130 void UnnecessaryValueParamCheck::storeOptions( 131 ClangTidyOptions::OptionMap &Opts) { 132 Options.store(Opts, "IncludeStyle", Inserter.getStyle()); 133 Options.store(Opts, "AllowedTypes", 134 utils::options::serializeStringList(AllowedTypes)); 135 } 136 137 void UnnecessaryValueParamCheck::onEndOfTranslationUnit() { 138 MutationAnalyzerCache.clear(); 139 } 140 141 void UnnecessaryValueParamCheck::handleConstRefFix(const FunctionDecl &Function, 142 const ParmVarDecl &Param, 143 ASTContext &Context) { 144 const size_t Index = 145 llvm::find(Function.parameters(), &Param) - Function.parameters().begin(); 146 const bool IsConstQualified = 147 Param.getType().getCanonicalType().isConstQualified(); 148 149 auto Diag = 150 diag(Param.getLocation(), 151 "the %select{|const qualified }0parameter %1 is copied for each " 152 "invocation%select{ but only used as a const reference|}0; consider " 153 "making it a %select{const |}0reference") 154 << IsConstQualified << paramNameOrIndex(Param.getName(), Index); 155 // Do not propose fixes when: 156 // 1. the ParmVarDecl is in a macro, since we cannot place them correctly 157 // 2. the function is virtual as it might break overrides 158 // 3. the function is referenced outside of a call expression within the 159 // compilation unit as the signature change could introduce build errors. 160 // 4. the function is an explicit template/ specialization. 161 const auto *Method = llvm::dyn_cast<CXXMethodDecl>(&Function); 162 if (Param.getBeginLoc().isMacroID() || (Method && Method->isVirtual()) || 163 isReferencedOutsideOfCallExpr(Function, Context) || 164 Function.getTemplateSpecializationKind() == TSK_ExplicitSpecialization) 165 return; 166 for (const auto *FunctionDecl = &Function; FunctionDecl != nullptr; 167 FunctionDecl = FunctionDecl->getPreviousDecl()) { 168 const auto &CurrentParam = *FunctionDecl->getParamDecl(Index); 169 Diag << utils::fixit::changeVarDeclToReference(CurrentParam, Context); 170 // The parameter of each declaration needs to be checked individually as to 171 // whether it is const or not as constness can differ between definition and 172 // declaration. 173 if (!CurrentParam.getType().getCanonicalType().isConstQualified()) { 174 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl( 175 CurrentParam, Context, Qualifiers::Const)) 176 Diag << *Fix; 177 } 178 } 179 } 180 181 void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Param, 182 const DeclRefExpr &CopyArgument, 183 ASTContext &Context) { 184 auto Diag = diag(CopyArgument.getBeginLoc(), 185 "parameter %0 is passed by value and only copied once; " 186 "consider moving it to avoid unnecessary copies") 187 << &Param; 188 // Do not propose fixes in macros since we cannot place them correctly. 189 if (CopyArgument.getBeginLoc().isMacroID()) 190 return; 191 const auto &SM = Context.getSourceManager(); 192 auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM, 193 Context.getLangOpts()); 194 Diag << FixItHint::CreateInsertion(CopyArgument.getBeginLoc(), "std::move(") 195 << FixItHint::CreateInsertion(EndLoc, ")") 196 << Inserter.createIncludeInsertion( 197 SM.getFileID(CopyArgument.getBeginLoc()), "<utility>"); 198 } 199 200 } // namespace clang::tidy::performance 201