1 //===--- MoveForwardingReferenceCheck.cpp - clang-tidy --------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "MoveForwardingReferenceCheck.h"
11 #include "clang/Lex/Lexer.h"
12 #include "llvm/Support/raw_ostream.h"
13 
14 #include <algorithm>
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace bugprone {
21 
22 static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee,
23                                    const ParmVarDecl *ParmVar,
24                                    const TemplateTypeParmDecl *TypeParmDecl,
25                                    DiagnosticBuilder &Diag,
26                                    const ASTContext &Context) {
27   const SourceManager &SM = Context.getSourceManager();
28   const LangOptions &LangOpts = Context.getLangOpts();
29 
30   CharSourceRange CallRange =
31       Lexer::makeFileCharRange(CharSourceRange::getTokenRange(
32                                    Callee->getLocStart(), Callee->getLocEnd()),
33                                SM, LangOpts);
34 
35   if (CallRange.isValid()) {
36     const std::string TypeName =
37         TypeParmDecl->getIdentifier()
38             ? TypeParmDecl->getName().str()
39             : (llvm::Twine("decltype(") + ParmVar->getName() + ")").str();
40 
41     const std::string ForwardName =
42         (llvm::Twine("forward<") + TypeName + ">").str();
43 
44     // Create a replacement only if we see a "standard" way of calling
45     // std::move(). This will hopefully prevent erroneous replacements if the
46     // code does unusual things (e.g. create an alias for std::move() in
47     // another namespace).
48     NestedNameSpecifier *NNS = Callee->getQualifier();
49     if (!NNS) {
50       // Called as "move" (i.e. presumably the code had a "using std::move;").
51       // We still conservatively put a "std::" in front of the forward because
52       // we don't know whether the code also had a "using std::forward;".
53       Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName);
54     } else if (const NamespaceDecl *Namespace = NNS->getAsNamespace()) {
55       if (Namespace->getName() == "std") {
56         if (!NNS->getPrefix()) {
57           // Called as "std::move".
58           Diag << FixItHint::CreateReplacement(CallRange,
59                                                "std::" + ForwardName);
60         } else if (NNS->getPrefix()->getKind() == NestedNameSpecifier::Global) {
61           // Called as "::std::move".
62           Diag << FixItHint::CreateReplacement(CallRange,
63                                                "::std::" + ForwardName);
64         }
65       }
66     }
67   }
68 }
69 
70 void MoveForwardingReferenceCheck::registerMatchers(MatchFinder *Finder) {
71   if (!getLangOpts().CPlusPlus11)
72     return;
73 
74   // Matches a ParmVarDecl for a forwarding reference, i.e. a non-const rvalue
75   // reference of a function template parameter type.
76   auto ForwardingReferenceParmMatcher =
77       parmVarDecl(
78           hasType(qualType(rValueReferenceType(),
79                            references(templateTypeParmType(hasDeclaration(
80                                templateTypeParmDecl().bind("type-parm-decl")))),
81                            unless(references(qualType(isConstQualified()))))))
82           .bind("parm-var");
83 
84   Finder->addMatcher(
85       callExpr(callee(unresolvedLookupExpr(
86                           hasAnyDeclaration(namedDecl(
87                               hasUnderlyingDecl(hasName("::std::move")))))
88                           .bind("lookup")),
89                argumentCountIs(1),
90                hasArgument(0, ignoringParenImpCasts(declRefExpr(
91                                   to(ForwardingReferenceParmMatcher)))))
92           .bind("call-move"),
93       this);
94 }
95 
96 void MoveForwardingReferenceCheck::check(
97     const MatchFinder::MatchResult &Result) {
98   const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
99   const auto *UnresolvedLookup =
100       Result.Nodes.getNodeAs<UnresolvedLookupExpr>("lookup");
101   const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var");
102   const auto *TypeParmDecl =
103       Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl");
104 
105   // Get the FunctionDecl and FunctionTemplateDecl containing the function
106   // parameter.
107   const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext());
108   if (!FuncForParam)
109     return;
110   const FunctionTemplateDecl *FuncTemplate =
111       FuncForParam->getDescribedFunctionTemplate();
112   if (!FuncTemplate)
113     return;
114 
115   // Check that the template type parameter belongs to the same function
116   // template as the function parameter of that type. (This implies that type
117   // deduction will happen on the type.)
118   const TemplateParameterList *Params = FuncTemplate->getTemplateParameters();
119   if (!std::count(Params->begin(), Params->end(), TypeParmDecl))
120     return;
121 
122   auto Diag = diag(CallMove->getExprLoc(),
123                    "forwarding reference passed to std::move(), which may "
124                    "unexpectedly cause lvalues to be moved; use "
125                    "std::forward() instead");
126 
127   replaceMoveWithForward(UnresolvedLookup, ParmVar, TypeParmDecl, Diag,
128                          *Result.Context);
129 }
130 
131 } // namespace bugprone
132 } // namespace tidy
133 } // namespace clang
134