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