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