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