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