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