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