clang-tools 24.0.0git
RvalueReferenceParamNotMovedCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10#include "../utils/Matchers.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13
14using namespace clang::ast_matchers;
15
17
18using matchers::hasUnevaluatedContext;
19
20namespace {
21AST_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}
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 return Ref.matches(Node, Finder, Builder);
35}
36} // namespace
37
39 auto ToParam = hasAnyParameter(parmVarDecl(equalsBoundNode("param")));
40
41 const StatementMatcher MoveCallMatcher =
42 callExpr(
43 argumentCountIs(1),
44 anyOf(callee(functionDecl(hasName(MoveFunction))),
45 callee(unresolvedLookupExpr(hasAnyDeclaration(
46 namedDecl(hasUnderlyingDecl(hasName(MoveFunction))))))),
47 hasArgument(
48 0, argumentOf(
49 AllowPartialMove,
50 declRefExpr(to(equalsBoundNode("param"))).bind("ref"))),
51 unless(hasAncestor(
52 lambdaExpr(valueCapturesVar(equalsBoundNode("param"))))),
53 unless(anyOf(hasAncestor(typeLoc()),
54 hasAncestor(expr(hasUnevaluatedContext())))))
55 .bind("move-call");
56
57 // P1825R0: returning a named rvalue reference parameter by name
58 // performs an implicit move, which is equivalent to ``std::move(param)``
59 const StatementMatcher ImplicitMoveReturnMatcher = traverse(
60 TK_IgnoreUnlessSpelledInSource,
61 returnStmt(hasReturnValue(ignoringParens(
62 declRefExpr(to(equalsBoundNode("param"))).bind("ref"))))
63 .bind("implicit-move-return"));
64
65 const StatementMatcher UsageMatcher = stmt(
66 anyOf(MoveCallMatcher, AllowImplicitMove ? ImplicitMoveReturnMatcher
67 : stmt(unless(anything()))));
68
69 Finder->addMatcher(
70 parmVarDecl(
71 hasType(type(rValueReferenceType())), parmVarDecl().bind("param"),
72 unless(hasType(references(qualType(
73 anyOf(isConstQualified(), substTemplateTypeParmType()))))),
74 optionally(hasType(qualType(references(templateTypeParmType(
75 hasDeclaration(templateTypeParmDecl().bind("template-type"))))))),
76 hasDeclContext(
77 functionDecl(
78 isDefinition(), unless(isDeleted()), unless(isDefaulted()),
79 unless(isImplicit()),
80 unless(cxxConstructorDecl(isMoveConstructor())),
81 unless(cxxMethodDecl(isMoveAssignmentOperator())), ToParam,
82 anyOf(cxxConstructorDecl(
83 optionally(hasDescendant(UsageMatcher))),
84 functionDecl(
85 unless(cxxConstructorDecl()),
86 optionally(hasBody(hasDescendant(UsageMatcher))))))
87 .bind("func"))),
88 this);
89}
90
92 const MatchFinder::MatchResult &Result) {
93 const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
94 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("func");
95 const auto *TemplateType =
96 Result.Nodes.getNodeAs<TemplateTypeParmDecl>("template-type");
97
98 if (!Param || !Function)
99 return;
100
101 if (IgnoreUnnamedParams && Param->getName().empty())
102 return;
103
104 if (!Param->isUsed() && Param->hasAttr<UnusedAttr>())
105 return;
106
107 if (IgnoreNonDeducedTemplateTypes && TemplateType)
108 return;
109
110 if (TemplateType) {
111 if (const FunctionTemplateDecl *FuncTemplate =
112 Function->getDescribedFunctionTemplate()) {
113 const TemplateParameterList *Params =
114 FuncTemplate->getTemplateParameters();
115 if (llvm::is_contained(*Params, TemplateType)) {
116 // Ignore forwarding reference
117 return;
118 }
119 }
120 }
121
122 const auto *MoveCall = Result.Nodes.getNodeAs<CallExpr>("move-call");
123 const auto *ImplicitMoveReturn =
124 Result.Nodes.getNodeAs<ReturnStmt>("implicit-move-return");
125 if (MoveCall || ImplicitMoveReturn)
126 return;
127
128 diag(Param->getLocation(),
129 "rvalue reference parameter %0 is never moved from "
130 "inside the function body")
131 << Param;
132}
133
135 StringRef Name, ClangTidyContext *Context)
136 : ClangTidyCheck(Name, Context),
137 AllowPartialMove(Options.get("AllowPartialMove", false)),
138 IgnoreUnnamedParams(Options.get("IgnoreUnnamedParams", false)),
139 IgnoreNonDeducedTemplateTypes(
140 Options.get("IgnoreNonDeducedTemplateTypes", false)),
141 AllowImplicitMove(Options.get("AllowImplicitMove", false)),
142 MoveFunction(Options.get("MoveFunction", "::std::move")) {}
143
146 Options.store(Opts, "AllowPartialMove", AllowPartialMove);
147 Options.store(Opts, "IgnoreUnnamedParams", IgnoreUnnamedParams);
148 Options.store(Opts, "IgnoreNonDeducedTemplateTypes",
149 IgnoreNonDeducedTemplateTypes);
150 Options.store(Opts, "AllowImplicitMove", AllowImplicitMove);
151 Options.store(Opts, "MoveFunction", MoveFunction);
152}
153
154} // namespace clang::tidy::cppcoreguidelines
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
llvm::StringMap< ClangTidyValue > OptionMap