1 //===--- MoveConstArgCheck.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 "MoveConstArgCheck.h" 10 11 #include "clang/Lex/Lexer.h" 12 13 using namespace clang::ast_matchers; 14 15 namespace clang { 16 namespace tidy { 17 namespace performance { 18 19 static void replaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag, 20 const SourceManager &SM, 21 const LangOptions &LangOpts) { 22 const Expr *Arg = Call->getArg(0); 23 24 CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange( 25 CharSourceRange::getCharRange(Call->getBeginLoc(), Arg->getBeginLoc()), 26 SM, LangOpts); 27 CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange( 28 CharSourceRange::getCharRange(Call->getEndLoc(), 29 Call->getEndLoc().getLocWithOffset(1)), 30 SM, LangOpts); 31 32 if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { 33 Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) 34 << FixItHint::CreateRemoval(AfterArgumentsRange); 35 } 36 } 37 38 void MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 39 Options.store(Opts, "CheckTriviallyCopyableMove", CheckTriviallyCopyableMove); 40 Options.store(Opts, "CheckMoveToConstRef", CheckMoveToConstRef); 41 } 42 43 void MoveConstArgCheck::registerMatchers(MatchFinder *Finder) { 44 auto MoveCallMatcher = 45 callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1), 46 unless(isInTemplateInstantiation())) 47 .bind("call-move"); 48 49 Finder->addMatcher(MoveCallMatcher, this); 50 51 auto ConstTypeParmMatcher = 52 qualType(references(isConstQualified())).bind("invocation-parm-type"); 53 auto RValueTypeParmMatcher = 54 qualType(rValueReferenceType()).bind("invocation-parm-type"); 55 // Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr. 56 auto ArgumentWithParamMatcher = forEachArgumentWithParam( 57 MoveCallMatcher, parmVarDecl(anyOf(hasType(ConstTypeParmMatcher), 58 hasType(RValueTypeParmMatcher))) 59 .bind("invocation-parm")); 60 // Matches respective types of arguments for a CallExpr or CXXConstructExpr 61 // and it works on calls through function pointers as well. 62 auto ArgumentWithParamTypeMatcher = forEachArgumentWithParamType( 63 MoveCallMatcher, anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher)); 64 65 Finder->addMatcher( 66 invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher)) 67 .bind("receiving-expr"), 68 this); 69 } 70 71 bool IsRValueReferenceParam(const Expr *Invocation, 72 const QualType *InvocationParmType, 73 const Expr *Arg) { 74 if (Invocation && (*InvocationParmType)->isRValueReferenceType() && 75 Arg->isLValue()) { 76 if (!Invocation->getType()->isRecordType()) 77 return true; 78 else { 79 if (const auto *ConstructCallExpr = 80 dyn_cast<CXXConstructExpr>(Invocation)) { 81 if (const auto *ConstructorDecl = ConstructCallExpr->getConstructor()) { 82 if (!ConstructorDecl->isCopyOrMoveConstructor() && 83 !ConstructorDecl->isDefaultConstructor()) 84 return true; 85 } 86 } 87 } 88 } 89 return false; 90 } 91 92 void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) { 93 const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move"); 94 const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr"); 95 const auto *InvocationParm = 96 Result.Nodes.getNodeAs<ParmVarDecl>("invocation-parm"); 97 const auto *InvocationParmType = 98 Result.Nodes.getNodeAs<QualType>("invocation-parm-type"); 99 100 // Skipping matchers which have been matched. 101 if (!ReceivingExpr && AlreadyCheckedMoves.contains(CallMove)) 102 return; 103 104 if (ReceivingExpr) 105 AlreadyCheckedMoves.insert(CallMove); 106 107 const Expr *Arg = CallMove->getArg(0); 108 SourceManager &SM = Result.Context->getSourceManager(); 109 110 CharSourceRange MoveRange = 111 CharSourceRange::getCharRange(CallMove->getSourceRange()); 112 CharSourceRange FileMoveRange = 113 Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); 114 if (!FileMoveRange.isValid()) 115 return; 116 117 bool IsConstArg = Arg->getType().isConstQualified(); 118 bool IsTriviallyCopyable = 119 Arg->getType().isTriviallyCopyableType(*Result.Context); 120 121 if (IsConstArg || IsTriviallyCopyable) { 122 if (const CXXRecordDecl *R = Arg->getType()->getAsCXXRecordDecl()) { 123 // According to [expr.prim.lambda]p3, "whether the closure type is 124 // trivially copyable" property can be changed by the implementation of 125 // the language, so we shouldn't rely on it when issuing diagnostics. 126 if (R->isLambda()) 127 return; 128 // Don't warn when the type is not copyable. 129 for (const auto *Ctor : R->ctors()) { 130 if (Ctor->isCopyConstructor() && Ctor->isDeleted()) 131 return; 132 } 133 } 134 135 if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove) 136 return; 137 138 bool IsVariable = isa<DeclRefExpr>(Arg); 139 // std::move shouldn't be removed when an lvalue wrapped by std::move is 140 // passed to the function with an rvalue reference parameter. 141 bool IsRVRefParam = 142 IsRValueReferenceParam(ReceivingExpr, InvocationParmType, Arg); 143 const auto *Var = 144 IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr; 145 146 { 147 auto Diag = diag(FileMoveRange.getBegin(), 148 "std::move of the %select{|const }0" 149 "%select{expression|variable %5}1 " 150 "%select{|of the trivially-copyable type %6 }2" 151 "has no effect%select{; remove std::move()|}3" 152 "%select{| or make the variable non-const}4") 153 << IsConstArg << IsVariable << IsTriviallyCopyable 154 << IsRVRefParam 155 << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var 156 << Arg->getType(); 157 if (!IsRVRefParam) 158 replaceCallWithArg(CallMove, Diag, SM, getLangOpts()); 159 } 160 if (IsRVRefParam) { 161 // Generate notes for an invocation with an rvalue reference parameter. 162 const auto *ReceivingCallExpr = dyn_cast<CallExpr>(ReceivingExpr); 163 const auto *ReceivingConstructExpr = 164 dyn_cast<CXXConstructExpr>(ReceivingExpr); 165 // Skipping the invocation which is a template instantiation. 166 if ((!ReceivingCallExpr || !ReceivingCallExpr->getDirectCallee() || 167 ReceivingCallExpr->getDirectCallee()->isTemplateInstantiation()) && 168 (!ReceivingConstructExpr || 169 !ReceivingConstructExpr->getConstructor() || 170 ReceivingConstructExpr->getConstructor()->isTemplateInstantiation())) 171 return; 172 173 const NamedDecl *FunctionName = nullptr; 174 FunctionName = 175 ReceivingCallExpr 176 ? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl() 177 : ReceivingConstructExpr->getConstructor()->getUnderlyingDecl(); 178 179 QualType NoRefType = (*InvocationParmType)->getPointeeType(); 180 PrintingPolicy PolicyWithSuppressedTag(getLangOpts()); 181 PolicyWithSuppressedTag.SuppressTagKeyword = true; 182 PolicyWithSuppressedTag.SuppressUnwrittenScope = true; 183 std::string ExpectParmTypeName = 184 NoRefType.getAsString(PolicyWithSuppressedTag); 185 if (!NoRefType->isPointerType()) { 186 NoRefType.addConst(); 187 ExpectParmTypeName = 188 NoRefType.getAsString(PolicyWithSuppressedTag) + " &"; 189 } 190 191 diag(InvocationParm->getLocation(), 192 "consider changing the %ordinal0 parameter of %1 from %2 to '%3'", 193 DiagnosticIDs::Note) 194 << (InvocationParm->getFunctionScopeIndex() + 1) << FunctionName 195 << *InvocationParmType << ExpectParmTypeName; 196 } 197 } else if (ReceivingExpr && CheckMoveToConstRef) { 198 if ((*InvocationParmType)->isRValueReferenceType()) 199 return; 200 201 auto Diag = diag(FileMoveRange.getBegin(), 202 "passing result of std::move() as a const reference " 203 "argument; no move will actually happen"); 204 205 replaceCallWithArg(CallMove, Diag, SM, getLangOpts()); 206 } 207 } 208 209 } // namespace performance 210 } // namespace tidy 211 } // namespace clang 212