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