xref: /llvm-project/clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp (revision 0aaac4fe194ae2249e1a01f9d6f5eac0d75c5bb2)
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 //===----------------------------------------------------------------------===//
8 
9 #include "MoveConstArgCheck.h"
10 
11 #include "clang/Lex/Lexer.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang::tidy::performance {
16 
17 static void replaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag,
18                                const SourceManager &SM,
19                                const LangOptions &LangOpts) {
20   const Expr *Arg = Call->getArg(0);
21 
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);
29 
30   if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) {
31     Diag << FixItHint::CreateRemoval(BeforeArgumentsRange)
32          << FixItHint::CreateRemoval(AfterArgumentsRange);
33   }
34 }
35 
36 void MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
37   Options.store(Opts, "CheckTriviallyCopyableMove", CheckTriviallyCopyableMove);
38   Options.store(Opts, "CheckMoveToConstRef", CheckMoveToConstRef);
39 }
40 
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");
46 
47   Finder->addMatcher(
48       expr(anyOf(
49           castExpr(hasSourceExpression(MoveCallMatcher)),
50           cxxConstructExpr(hasDeclaration(cxxConstructorDecl(anyOf(
51                                isCopyConstructor(), isMoveConstructor()))),
52                            hasArgument(0, MoveCallMatcher)))),
53       this);
54 
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));
68 
69   Finder->addMatcher(
70       invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher))
71           .bind("receiving-expr"),
72       this);
73 }
74 
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;
88       }
89     }
90   }
91   return false;
92 }
93 
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");
101 
102   // Skipping matchers which have been matched.
103   if (!ReceivingExpr && AlreadyCheckedMoves.contains(CallMove))
104     return;
105 
106   if (ReceivingExpr)
107     AlreadyCheckedMoves.insert(CallMove);
108 
109   const Expr *Arg = CallMove->getArg(0);
110   const QualType ArgType = Arg->getType().getCanonicalType();
111   SourceManager &SM = Result.Context->getSourceManager();
112 
113   CharSourceRange MoveRange =
114       CharSourceRange::getCharRange(CallMove->getSourceRange());
115   CharSourceRange FileMoveRange =
116       Lexer::makeFileCharRange(MoveRange, SM, getLangOpts());
117   if (!FileMoveRange.isValid())
118     return;
119 
120   bool IsConstArg = ArgType.isConstQualified();
121   bool IsTriviallyCopyable = ArgType.isTriviallyCopyableType(*Result.Context);
122 
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;
134       }
135     }
136 
137     if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove)
138       return;
139 
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;
147 
148     {
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());
161     }
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;
174 
175       const NamedDecl *FunctionName = nullptr;
176       FunctionName =
177           ReceivingCallExpr
178               ? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl()
179               : ReceivingConstructExpr->getConstructor()->getUnderlyingDecl();
180 
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) + " &";
191       }
192 
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;
198     }
199   } else if (ReceivingExpr && CheckMoveToConstRef) {
200     if ((*InvocationParmType)->isRValueReferenceType())
201       return;
202 
203     {
204       auto Diag = diag(FileMoveRange.getBegin(),
205                        "passing result of std::move() as a const reference "
206                        "argument; no move will actually happen");
207 
208       replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
209     }
210 
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;
218 
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;
225     }
226   }
227 }
228 
229 } // namespace clang::tidy::performance
230