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