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   const auto *Function = dyn_cast<FunctionDecl>(Param->getDeclContext());
88   if (!Function)
89     return;
90 
91   if (IgnoreNonDeducedTemplateTypes && TemplateType)
92     return;
93 
94   if (TemplateType) {
95     if (const FunctionTemplateDecl *FuncTemplate =
96             Function->getDescribedFunctionTemplate()) {
97       const TemplateParameterList *Params =
98           FuncTemplate->getTemplateParameters();
99       if (llvm::is_contained(*Params, TemplateType)) {
100         // Ignore forwarding reference
101         return;
102       }
103     }
104   }
105 
106   const auto *MoveCall = Result.Nodes.getNodeAs<CallExpr>("move-call");
107   if (!MoveCall) {
108     diag(Param->getLocation(),
109          "rvalue reference parameter %0 is never moved from "
110          "inside the function body")
111         << Param;
112   }
113 }
114 
115 RvalueReferenceParamNotMovedCheck::RvalueReferenceParamNotMovedCheck(
116     StringRef Name, ClangTidyContext *Context)
117     : ClangTidyCheck(Name, Context),
118       AllowPartialMove(Options.getLocalOrGlobal("AllowPartialMove", false)),
119       IgnoreUnnamedParams(
120           Options.getLocalOrGlobal("IgnoreUnnamedParams", false)),
121       IgnoreNonDeducedTemplateTypes(
122           Options.getLocalOrGlobal("IgnoreNonDeducedTemplateTypes", false)) {}
123 
124 void RvalueReferenceParamNotMovedCheck::storeOptions(
125     ClangTidyOptions::OptionMap &Opts) {
126   Options.store(Opts, "AllowPartialMove", AllowPartialMove);
127   Options.store(Opts, "IgnoreUnnamedParams", IgnoreUnnamedParams);
128   Options.store(Opts, "IgnoreNonDeducedTemplateTypes",
129                 IgnoreNonDeducedTemplateTypes);
130 }
131 
132 } // namespace clang::tidy::cppcoreguidelines
133