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