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