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
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::cppcoreguidelines {
17
18using matchers::hasUnevaluatedContext;
19
20namespace {
21AST_MATCHER_P(LambdaExpr, valueCapturesVar, DeclarationMatcher, VarMatcher) {
22 return std::find_if(first: Node.capture_begin(), last: Node.capture_end(),
23 pred: [&](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}
30AST_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
39void RvalueReferenceParamNotMovedCheck::registerMatchers(MatchFinder *Finder) {
40 auto ToParam = hasAnyParameter(InnerMatcher: parmVarDecl(equalsBoundNode(ID: "param")));
41
42 StatementMatcher MoveCallMatcher =
43 callExpr(
44 argumentCountIs(N: 1),
45 anyOf(callee(InnerMatcher: functionDecl(hasName(Name: "::std::move"))),
46 callee(InnerMatcher: unresolvedLookupExpr(hasAnyDeclaration(
47 InnerMatcher: namedDecl(hasUnderlyingDecl(InnerMatcher: hasName(Name: "::std::move"))))))),
48 hasArgument(
49 N: 0, InnerMatcher: argumentOf(
50 AllowPartialMove,
51 Ref: declRefExpr(to(InnerMatcher: equalsBoundNode(ID: "param"))).bind(ID: "ref"))),
52 unless(hasAncestor(
53 lambdaExpr(valueCapturesVar(VarMatcher: equalsBoundNode(ID: "param"))))),
54 unless(anyOf(hasAncestor(typeLoc()),
55 hasAncestor(expr(hasUnevaluatedContext())))))
56 .bind(ID: "move-call");
57
58 Finder->addMatcher(
59 NodeMatch: parmVarDecl(
60 hasType(InnerMatcher: type(rValueReferenceType())), parmVarDecl().bind(ID: "param"),
61 unless(hasType(InnerMatcher: references(InnerMatcher: qualType(
62 anyOf(isConstQualified(), substTemplateTypeParmType()))))),
63 optionally(hasType(InnerMatcher: qualType(references(InnerMatcher: templateTypeParmType(
64 hasDeclaration(InnerMatcher: templateTypeParmDecl().bind(ID: "template-type"))))))),
65 hasDeclContext(
66 InnerMatcher: 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 InnerMatcher: hasDescendant(MoveCallMatcher))))))
75 .bind(ID: "func"))),
76 Action: this);
77}
78
79void RvalueReferenceParamNotMovedCheck::check(
80 const MatchFinder::MatchResult &Result) {
81 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>(ID: "param");
82 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(ID: "func");
83 const auto *TemplateType =
84 Result.Nodes.getNodeAs<TemplateTypeParmDecl>(ID: "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(Range: *Params, Element: TemplateType)) {
104 // Ignore forwarding reference
105 return;
106 }
107 }
108 }
109
110 const auto *MoveCall = Result.Nodes.getNodeAs<CallExpr>(ID: "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
119RvalueReferenceParamNotMovedCheck::RvalueReferenceParamNotMovedCheck(
120 StringRef Name, ClangTidyContext *Context)
121 : ClangTidyCheck(Name, Context),
122 AllowPartialMove(Options.getLocalOrGlobal(LocalName: "AllowPartialMove", Default: false)),
123 IgnoreUnnamedParams(
124 Options.getLocalOrGlobal(LocalName: "IgnoreUnnamedParams", Default: false)),
125 IgnoreNonDeducedTemplateTypes(
126 Options.getLocalOrGlobal(LocalName: "IgnoreNonDeducedTemplateTypes", Default: false)) {}
127
128void RvalueReferenceParamNotMovedCheck::storeOptions(
129 ClangTidyOptions::OptionMap &Opts) {
130 Options.store(Options&: Opts, LocalName: "AllowPartialMove", Value: AllowPartialMove);
131 Options.store(Options&: Opts, LocalName: "IgnoreUnnamedParams", Value: IgnoreUnnamedParams);
132 Options.store(Options&: Opts, LocalName: "IgnoreNonDeducedTemplateTypes",
133 Value: IgnoreNonDeducedTemplateTypes);
134}
135
136} // namespace clang::tidy::cppcoreguidelines
137

source code of clang-tools-extra/clang-tidy/cppcoreguidelines/RvalueReferenceParamNotMovedCheck.cpp