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 hasDeclContext( 66 functionDecl( 67 isDefinition(), unless(isDeleted()), unless(isDefaulted()), 68 unless(cxxConstructorDecl(isMoveConstructor())), 69 unless(cxxMethodDecl(isMoveAssignmentOperator())), ToParam, 70 anyOf(cxxConstructorDecl( 71 optionally(hasDescendant(MoveCallMatcher))), 72 functionDecl(unless(cxxConstructorDecl()), 73 optionally(hasBody( 74 hasDescendant(MoveCallMatcher)))))) 75 .bind("func"))), 76 this); 77 } 78 79 void RvalueReferenceParamNotMovedCheck::check( 80 const MatchFinder::MatchResult &Result) { 81 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param"); 82 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("func"); 83 const auto *TemplateType = 84 Result.Nodes.getNodeAs<TemplateTypeParmDecl>("template-type"); 85 86 if (!Param || !Function) 87 return; 88 89 if (IgnoreUnnamedParams && Param->getName().empty()) 90 return; 91 92 if (!Param->isUsed() && Param->hasAttr<UnusedAttr>()) 93 return; 94 95 if (IgnoreNonDeducedTemplateTypes && TemplateType) 96 return; 97 98 if (TemplateType) { 99 if (const FunctionTemplateDecl *FuncTemplate = 100 Function->getDescribedFunctionTemplate()) { 101 const TemplateParameterList *Params = 102 FuncTemplate->getTemplateParameters(); 103 if (llvm::is_contained(*Params, TemplateType)) { 104 // Ignore forwarding reference 105 return; 106 } 107 } 108 } 109 110 const auto *MoveCall = Result.Nodes.getNodeAs<CallExpr>("move-call"); 111 if (!MoveCall) { 112 diag(Param->getLocation(), 113 "rvalue reference parameter %0 is never moved from " 114 "inside the function body") 115 << Param; 116 } 117 } 118 119 RvalueReferenceParamNotMovedCheck::RvalueReferenceParamNotMovedCheck( 120 StringRef Name, ClangTidyContext *Context) 121 : ClangTidyCheck(Name, Context), 122 AllowPartialMove(Options.get("AllowPartialMove", false)), 123 IgnoreUnnamedParams(Options.get("IgnoreUnnamedParams", false)), 124 IgnoreNonDeducedTemplateTypes( 125 Options.get("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