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