clang-tools  15.0.0git
MoveConstArgCheck.cpp
Go to the documentation of this file.
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  Options.store(Opts, "CheckMoveToConstRef", CheckMoveToConstRef);
41 }
42 
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 
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 
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
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::clangd::check
bool check(llvm::StringRef File, llvm::Optional< Range > LineRange, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:273
clang::tidy::performance::replaceCallWithArg
static void replaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag, const SourceManager &SM, const LangOptions &LangOpts)
Definition: MoveConstArgCheck.cpp:19
clang::ast_matchers
Definition: AbseilMatcher.h:14
MoveConstArgCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
LangOpts
const LangOptions * LangOpts
Definition: ExtractFunction.cpp:366
clang::tidy::performance::IsRValueReferenceParam
bool IsRValueReferenceParam(const Expr *Invocation, const QualType *InvocationParmType, const Expr *Arg)
Definition: MoveConstArgCheck.cpp:71