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(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 | } |
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(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 | |
79 | void 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 | |
119 | RvalueReferenceParamNotMovedCheck::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 | |
128 | void 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 | |