[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clang-tidy / performance / MoveConstArgCheck.cpp
blob421ce003975bc9a5e370d385be6d472fe2f8776d
1 //===--- MoveConstArgCheck.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 //===----------------------------------------------------------------------===//
9 #include "MoveConstArgCheck.h"
11 #include "clang/Lex/Lexer.h"
13 using namespace clang::ast_matchers;
15 namespace clang::tidy::performance {
17 static void replaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag,
18 const SourceManager &SM,
19 const LangOptions &LangOpts) {
20 const Expr *Arg = Call->getArg(0);
22 CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange(
23 CharSourceRange::getCharRange(Call->getBeginLoc(), Arg->getBeginLoc()),
24 SM, LangOpts);
25 CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange(
26 CharSourceRange::getCharRange(Call->getEndLoc(),
27 Call->getEndLoc().getLocWithOffset(1)),
28 SM, LangOpts);
30 if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) {
31 Diag << FixItHint::CreateRemoval(BeforeArgumentsRange)
32 << FixItHint::CreateRemoval(AfterArgumentsRange);
36 void MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
37 Options.store(Opts, "CheckTriviallyCopyableMove", CheckTriviallyCopyableMove);
38 Options.store(Opts, "CheckMoveToConstRef", CheckMoveToConstRef);
41 void MoveConstArgCheck::registerMatchers(MatchFinder *Finder) {
42 auto MoveCallMatcher =
43 callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1),
44 unless(isInTemplateInstantiation()))
45 .bind("call-move");
47 Finder->addMatcher(
48 expr(anyOf(
49 castExpr(hasSourceExpression(MoveCallMatcher)),
50 cxxConstructExpr(hasDeclaration(cxxConstructorDecl(anyOf(
51 isCopyConstructor(), isMoveConstructor()))),
52 hasArgument(0, MoveCallMatcher)))),
53 this);
55 auto ConstTypeParmMatcher =
56 qualType(references(isConstQualified())).bind("invocation-parm-type");
57 auto RValueTypeParmMatcher =
58 qualType(rValueReferenceType()).bind("invocation-parm-type");
59 // Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr.
60 auto ArgumentWithParamMatcher = forEachArgumentWithParam(
61 MoveCallMatcher, parmVarDecl(anyOf(hasType(ConstTypeParmMatcher),
62 hasType(RValueTypeParmMatcher)))
63 .bind("invocation-parm"));
64 // Matches respective types of arguments for a CallExpr or CXXConstructExpr
65 // and it works on calls through function pointers as well.
66 auto ArgumentWithParamTypeMatcher = forEachArgumentWithParamType(
67 MoveCallMatcher, anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher));
69 Finder->addMatcher(
70 invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher))
71 .bind("receiving-expr"),
72 this);
75 bool IsRValueReferenceParam(const Expr *Invocation,
76 const QualType *InvocationParmType,
77 const Expr *Arg) {
78 if (Invocation && (*InvocationParmType)->isRValueReferenceType() &&
79 Arg->isLValue()) {
80 if (!Invocation->getType()->isRecordType())
81 return true;
82 if (const auto *ConstructCallExpr =
83 dyn_cast<CXXConstructExpr>(Invocation)) {
84 if (const auto *ConstructorDecl = ConstructCallExpr->getConstructor()) {
85 if (!ConstructorDecl->isCopyOrMoveConstructor() &&
86 !ConstructorDecl->isDefaultConstructor())
87 return true;
91 return false;
94 void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) {
95 const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
96 const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr");
97 const auto *InvocationParm =
98 Result.Nodes.getNodeAs<ParmVarDecl>("invocation-parm");
99 const auto *InvocationParmType =
100 Result.Nodes.getNodeAs<QualType>("invocation-parm-type");
102 // Skipping matchers which have been matched.
103 if (!ReceivingExpr && AlreadyCheckedMoves.contains(CallMove))
104 return;
106 if (ReceivingExpr)
107 AlreadyCheckedMoves.insert(CallMove);
109 const Expr *Arg = CallMove->getArg(0);
110 const QualType ArgType = Arg->getType().getCanonicalType();
111 SourceManager &SM = Result.Context->getSourceManager();
113 CharSourceRange MoveRange =
114 CharSourceRange::getCharRange(CallMove->getSourceRange());
115 CharSourceRange FileMoveRange =
116 Lexer::makeFileCharRange(MoveRange, SM, getLangOpts());
117 if (!FileMoveRange.isValid())
118 return;
120 bool IsConstArg = ArgType.isConstQualified();
121 bool IsTriviallyCopyable = ArgType.isTriviallyCopyableType(*Result.Context);
123 if (IsConstArg || IsTriviallyCopyable) {
124 if (const CXXRecordDecl *R = ArgType->getAsCXXRecordDecl()) {
125 // According to [expr.prim.lambda]p3, "whether the closure type is
126 // trivially copyable" property can be changed by the implementation of
127 // the language, so we shouldn't rely on it when issuing diagnostics.
128 if (R->isLambda())
129 return;
130 // Don't warn when the type is not copyable.
131 for (const auto *Ctor : R->ctors()) {
132 if (Ctor->isCopyConstructor() && Ctor->isDeleted())
133 return;
137 if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove)
138 return;
140 bool IsVariable = isa<DeclRefExpr>(Arg);
141 // std::move shouldn't be removed when an lvalue wrapped by std::move is
142 // passed to the function with an rvalue reference parameter.
143 bool IsRVRefParam =
144 IsRValueReferenceParam(ReceivingExpr, InvocationParmType, Arg);
145 const auto *Var =
146 IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr;
149 auto Diag = diag(FileMoveRange.getBegin(),
150 "std::move of the %select{|const }0"
151 "%select{expression|variable %5}1 "
152 "%select{|of the trivially-copyable type %6 }2"
153 "has no effect%select{; remove std::move()|}3"
154 "%select{| or make the variable non-const}4")
155 << IsConstArg << IsVariable << IsTriviallyCopyable
156 << IsRVRefParam
157 << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var
158 << Arg->getType();
159 if (!IsRVRefParam)
160 replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
162 if (IsRVRefParam) {
163 // Generate notes for an invocation with an rvalue reference parameter.
164 const auto *ReceivingCallExpr = dyn_cast<CallExpr>(ReceivingExpr);
165 const auto *ReceivingConstructExpr =
166 dyn_cast<CXXConstructExpr>(ReceivingExpr);
167 // Skipping the invocation which is a template instantiation.
168 if ((!ReceivingCallExpr || !ReceivingCallExpr->getDirectCallee() ||
169 ReceivingCallExpr->getDirectCallee()->isTemplateInstantiation()) &&
170 (!ReceivingConstructExpr ||
171 !ReceivingConstructExpr->getConstructor() ||
172 ReceivingConstructExpr->getConstructor()->isTemplateInstantiation()))
173 return;
175 const NamedDecl *FunctionName = nullptr;
176 FunctionName =
177 ReceivingCallExpr
178 ? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl()
179 : ReceivingConstructExpr->getConstructor()->getUnderlyingDecl();
181 QualType NoRefType = (*InvocationParmType)->getPointeeType();
182 PrintingPolicy PolicyWithSuppressedTag(getLangOpts());
183 PolicyWithSuppressedTag.SuppressTagKeyword = true;
184 PolicyWithSuppressedTag.SuppressUnwrittenScope = true;
185 std::string ExpectParmTypeName =
186 NoRefType.getAsString(PolicyWithSuppressedTag);
187 if (!NoRefType->isPointerType()) {
188 NoRefType.addConst();
189 ExpectParmTypeName =
190 NoRefType.getAsString(PolicyWithSuppressedTag) + " &";
193 diag(InvocationParm->getLocation(),
194 "consider changing the %ordinal0 parameter of %1 from %2 to '%3'",
195 DiagnosticIDs::Note)
196 << (InvocationParm->getFunctionScopeIndex() + 1) << FunctionName
197 << *InvocationParmType << ExpectParmTypeName;
199 } else if (ReceivingExpr && CheckMoveToConstRef) {
200 if ((*InvocationParmType)->isRValueReferenceType())
201 return;
204 auto Diag = diag(FileMoveRange.getBegin(),
205 "passing result of std::move() as a const reference "
206 "argument; no move will actually happen");
208 replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
211 if (const CXXRecordDecl *RecordDecl = ArgType->getAsCXXRecordDecl();
212 RecordDecl && RecordDecl->hasDefinition() &&
213 !(RecordDecl->hasMoveConstructor() &&
214 RecordDecl->hasMoveAssignment())) {
215 const bool MissingMoveAssignment = !RecordDecl->hasMoveAssignment();
216 const bool MissingMoveConstructor = !RecordDecl->hasMoveConstructor();
217 const bool MissingBoth = MissingMoveAssignment && MissingMoveConstructor;
219 diag(RecordDecl->getLocation(),
220 "%0 is not move "
221 "%select{|assignable}1%select{|/}2%select{|constructible}3",
222 DiagnosticIDs::Note)
223 << RecordDecl << MissingMoveAssignment << MissingBoth
224 << MissingMoveConstructor;
229 } // namespace clang::tidy::performance