1 //===--- RvalueReferenceParamNotMovedCheck.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 "RvalueReferenceParamNotMovedCheck.h" 10 #include "../utils/Matchers.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 14 using namespace clang::ast_matchers; 15 16 namespace clang::tidy::cppcoreguidelines { 17 18 using matchers::hasUnevaluatedContext; 19 20 namespace { 21 AST_MATCHER_P(LambdaExpr, valueCapturesVar, DeclarationMatcher, VarMatcher) { 22 return std::find_if(Node.capture_begin(), Node.capture_end(), 23 [&](const LambdaCapture &Capture) { 24 return Capture.capturesVariable() && 25 VarMatcher.matches(*Capture.getCapturedVar(), 26 Finder, Builder) && 27 Capture.getCaptureKind() == LCK_ByCopy; 28 }) != Node.capture_end(); 29 } 30 AST_MATCHER_P2(Stmt, argumentOf, bool, AllowPartialMove, StatementMatcher, 31 Ref) { 32 if (AllowPartialMove) { 33 return stmt(anyOf(Ref, hasDescendant(Ref))).matches(Node, Finder, Builder); 34 } else { 35 return Ref.matches(Node, Finder, Builder); 36 } 37 } 38 } // namespace 39 40 void RvalueReferenceParamNotMovedCheck::registerMatchers(MatchFinder *Finder) { 41 auto ToParam = hasAnyParameter(parmVarDecl(equalsBoundNode("param"))); 42 43 StatementMatcher MoveCallMatcher = 44 callExpr( 45 argumentCountIs(1), 46 anyOf(callee(functionDecl(hasName("::std::move"))), 47 callee(unresolvedLookupExpr(hasAnyDeclaration( 48 namedDecl(hasUnderlyingDecl(hasName("::std::move"))))))), 49 hasArgument( 50 0, argumentOf( 51 AllowPartialMove, 52 declRefExpr(to(equalsBoundNode("param"))).bind("ref"))), 53 unless(hasAncestor( 54 lambdaExpr(valueCapturesVar(equalsBoundNode("param"))))), 55 unless(anyOf(hasAncestor(typeLoc()), 56 hasAncestor(expr(hasUnevaluatedContext()))))) 57 .bind("move-call"); 58 59 Finder->addMatcher( 60 parmVarDecl( 61 hasType(type(rValueReferenceType())), parmVarDecl().bind("param"), 62 unless(hasType(references(qualType( 63 anyOf(isConstQualified(), substTemplateTypeParmType()))))), 64 optionally(hasType(qualType(references(templateTypeParmType( 65 hasDeclaration(templateTypeParmDecl().bind("template-type"))))))), 66 anyOf(hasAncestor(cxxConstructorDecl( 67 ToParam, isDefinition(), unless(isMoveConstructor()), 68 optionally(hasDescendant(MoveCallMatcher)))), 69 hasAncestor(functionDecl( 70 unless(cxxConstructorDecl()), ToParam, 71 unless(cxxMethodDecl(isMoveAssignmentOperator())), 72 hasBody(optionally(hasDescendant(MoveCallMatcher))))))), 73 this); 74 } 75 76 void RvalueReferenceParamNotMovedCheck::check( 77 const MatchFinder::MatchResult &Result) { 78 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param"); 79 const auto *TemplateType = 80 Result.Nodes.getNodeAs<TemplateTypeParmDecl>("template-type"); 81 82 if (!Param) 83 return; 84 85 if (IgnoreUnnamedParams && Param->getName().empty()) 86 return; 87 88 const auto *Function = dyn_cast<FunctionDecl>(Param->getDeclContext()); 89 if (!Function) 90 return; 91 92 if (IgnoreNonDeducedTemplateTypes && TemplateType) 93 return; 94 95 if (TemplateType) { 96 if (const FunctionTemplateDecl *FuncTemplate = 97 Function->getDescribedFunctionTemplate()) { 98 const TemplateParameterList *Params = 99 FuncTemplate->getTemplateParameters(); 100 if (llvm::is_contained(*Params, TemplateType)) { 101 // Ignore forwarding reference 102 return; 103 } 104 } 105 } 106 107 const auto *MoveCall = Result.Nodes.getNodeAs<CallExpr>("move-call"); 108 if (!MoveCall) { 109 diag(Param->getLocation(), 110 "rvalue reference parameter %0 is never moved from " 111 "inside the function body") 112 << Param; 113 } 114 } 115 116 RvalueReferenceParamNotMovedCheck::RvalueReferenceParamNotMovedCheck( 117 StringRef Name, ClangTidyContext *Context) 118 : ClangTidyCheck(Name, Context), 119 AllowPartialMove(Options.getLocalOrGlobal("AllowPartialMove", false)), 120 IgnoreUnnamedParams( 121 Options.getLocalOrGlobal("IgnoreUnnamedParams", false)), 122 IgnoreNonDeducedTemplateTypes( 123 Options.getLocalOrGlobal("IgnoreNonDeducedTemplateTypes", false)) {} 124 125 void RvalueReferenceParamNotMovedCheck::storeOptions( 126 ClangTidyOptions::OptionMap &Opts) { 127 Options.store(Opts, "AllowPartialMove", AllowPartialMove); 128 Options.store(Opts, "IgnoreUnnamedParams", IgnoreUnnamedParams); 129 Options.store(Opts, "IgnoreNonDeducedTemplateTypes", 130 IgnoreNonDeducedTemplateTypes); 131 } 132 133 } // namespace clang::tidy::cppcoreguidelines 134