1 //===--- MoveForwardingReferenceCheck.cpp - clang-tidy --------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "MoveForwardingReferenceCheck.h"
10 #include "clang/Lex/Lexer.h"
11 #include "llvm/Support/raw_ostream.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::bugprone
{
19 static void replaceMoveWithForward(const UnresolvedLookupExpr
*Callee
,
20 const ParmVarDecl
*ParmVar
,
21 const TemplateTypeParmDecl
*TypeParmDecl
,
22 DiagnosticBuilder
&Diag
,
23 const ASTContext
&Context
) {
24 const SourceManager
&SM
= Context
.getSourceManager();
25 const LangOptions
&LangOpts
= Context
.getLangOpts();
27 CharSourceRange CallRange
=
28 Lexer::makeFileCharRange(CharSourceRange::getTokenRange(
29 Callee
->getBeginLoc(), Callee
->getEndLoc()),
32 if (CallRange
.isValid()) {
33 const std::string TypeName
=
34 (TypeParmDecl
->getIdentifier() && !TypeParmDecl
->isImplicit())
35 ? TypeParmDecl
->getName().str()
36 : (llvm::Twine("decltype(") + ParmVar
->getName() + ")").str();
38 const std::string ForwardName
=
39 (llvm::Twine("forward<") + TypeName
+ ">").str();
41 // Create a replacement only if we see a "standard" way of calling
42 // std::move(). This will hopefully prevent erroneous replacements if the
43 // code does unusual things (e.g. create an alias for std::move() in
44 // another namespace).
45 NestedNameSpecifier
*NNS
= Callee
->getQualifier();
47 // Called as "move" (i.e. presumably the code had a "using std::move;").
48 // We still conservatively put a "std::" in front of the forward because
49 // we don't know whether the code also had a "using std::forward;".
50 Diag
<< FixItHint::CreateReplacement(CallRange
, "std::" + ForwardName
);
51 } else if (const NamespaceDecl
*Namespace
= NNS
->getAsNamespace()) {
52 if (Namespace
->getName() == "std") {
53 if (!NNS
->getPrefix()) {
54 // Called as "std::move".
55 Diag
<< FixItHint::CreateReplacement(CallRange
,
56 "std::" + ForwardName
);
57 } else if (NNS
->getPrefix()->getKind() == NestedNameSpecifier::Global
) {
58 // Called as "::std::move".
59 Diag
<< FixItHint::CreateReplacement(CallRange
,
60 "::std::" + ForwardName
);
67 void MoveForwardingReferenceCheck::registerMatchers(MatchFinder
*Finder
) {
68 // Matches a ParmVarDecl for a forwarding reference, i.e. a non-const rvalue
69 // reference of a function template parameter type.
70 auto ForwardingReferenceParmMatcher
=
72 hasType(qualType(rValueReferenceType(),
73 references(templateTypeParmType(hasDeclaration(
74 templateTypeParmDecl().bind("type-parm-decl")))),
75 unless(references(qualType(isConstQualified()))))))
79 callExpr(callee(unresolvedLookupExpr(
80 hasAnyDeclaration(namedDecl(
81 hasUnderlyingDecl(hasName("::std::move")))))
84 hasArgument(0, ignoringParenImpCasts(declRefExpr(
85 to(ForwardingReferenceParmMatcher
)))))
90 void MoveForwardingReferenceCheck::check(
91 const MatchFinder::MatchResult
&Result
) {
92 const auto *CallMove
= Result
.Nodes
.getNodeAs
<CallExpr
>("call-move");
93 const auto *UnresolvedLookup
=
94 Result
.Nodes
.getNodeAs
<UnresolvedLookupExpr
>("lookup");
95 const auto *ParmVar
= Result
.Nodes
.getNodeAs
<ParmVarDecl
>("parm-var");
96 const auto *TypeParmDecl
=
97 Result
.Nodes
.getNodeAs
<TemplateTypeParmDecl
>("type-parm-decl");
99 // Get the FunctionDecl and FunctionTemplateDecl containing the function
101 const auto *FuncForParam
= dyn_cast
<FunctionDecl
>(ParmVar
->getDeclContext());
104 const FunctionTemplateDecl
*FuncTemplate
=
105 FuncForParam
->getDescribedFunctionTemplate();
109 // Check that the template type parameter belongs to the same function
110 // template as the function parameter of that type. (This implies that type
111 // deduction will happen on the type.)
112 const TemplateParameterList
*Params
= FuncTemplate
->getTemplateParameters();
113 if (!llvm::is_contained(*Params
, TypeParmDecl
))
116 auto Diag
= diag(CallMove
->getExprLoc(),
117 "forwarding reference passed to std::move(), which may "
118 "unexpectedly cause lvalues to be moved; use "
119 "std::forward() instead");
121 replaceMoveWithForward(UnresolvedLookup
, ParmVar
, TypeParmDecl
, Diag
,
125 } // namespace clang::tidy::bugprone