//===--- MoveForwardingReferenceCheck.cpp - clang-tidy --------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "MoveForwardingReferenceCheck.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/raw_ostream.h" #include using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace bugprone { static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee, const ParmVarDecl *ParmVar, const TemplateTypeParmDecl *TypeParmDecl, DiagnosticBuilder &Diag, const ASTContext &Context) { const SourceManager &SM = Context.getSourceManager(); const LangOptions &LangOpts = Context.getLangOpts(); CharSourceRange CallRange = Lexer::makeFileCharRange(CharSourceRange::getTokenRange( Callee->getBeginLoc(), Callee->getEndLoc()), SM, LangOpts); if (CallRange.isValid()) { const std::string TypeName = TypeParmDecl->getIdentifier() ? TypeParmDecl->getName().str() : (llvm::Twine("decltype(") + ParmVar->getName() + ")").str(); const std::string ForwardName = (llvm::Twine("forward<") + TypeName + ">").str(); // Create a replacement only if we see a "standard" way of calling // std::move(). This will hopefully prevent erroneous replacements if the // code does unusual things (e.g. create an alias for std::move() in // another namespace). NestedNameSpecifier *NNS = Callee->getQualifier(); if (!NNS) { // Called as "move" (i.e. presumably the code had a "using std::move;"). // We still conservatively put a "std::" in front of the forward because // we don't know whether the code also had a "using std::forward;". Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName); } else if (const NamespaceDecl *Namespace = NNS->getAsNamespace()) { if (Namespace->getName() == "std") { if (!NNS->getPrefix()) { // Called as "std::move". Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName); } else if (NNS->getPrefix()->getKind() == NestedNameSpecifier::Global) { // Called as "::std::move". Diag << FixItHint::CreateReplacement(CallRange, "::std::" + ForwardName); } } } } } void MoveForwardingReferenceCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus11) return; // Matches a ParmVarDecl for a forwarding reference, i.e. a non-const rvalue // reference of a function template parameter type. auto ForwardingReferenceParmMatcher = parmVarDecl( hasType(qualType(rValueReferenceType(), references(templateTypeParmType(hasDeclaration( templateTypeParmDecl().bind("type-parm-decl")))), unless(references(qualType(isConstQualified())))))) .bind("parm-var"); Finder->addMatcher( callExpr(callee(unresolvedLookupExpr( hasAnyDeclaration(namedDecl( hasUnderlyingDecl(hasName("::std::move"))))) .bind("lookup")), argumentCountIs(1), hasArgument(0, ignoringParenImpCasts(declRefExpr( to(ForwardingReferenceParmMatcher))))) .bind("call-move"), this); } void MoveForwardingReferenceCheck::check( const MatchFinder::MatchResult &Result) { const auto *CallMove = Result.Nodes.getNodeAs("call-move"); const auto *UnresolvedLookup = Result.Nodes.getNodeAs("lookup"); const auto *ParmVar = Result.Nodes.getNodeAs("parm-var"); const auto *TypeParmDecl = Result.Nodes.getNodeAs("type-parm-decl"); // Get the FunctionDecl and FunctionTemplateDecl containing the function // parameter. const auto *FuncForParam = dyn_cast(ParmVar->getDeclContext()); if (!FuncForParam) return; const FunctionTemplateDecl *FuncTemplate = FuncForParam->getDescribedFunctionTemplate(); if (!FuncTemplate) return; // Check that the template type parameter belongs to the same function // template as the function parameter of that type. (This implies that type // deduction will happen on the type.) const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); if (!std::count(Params->begin(), Params->end(), TypeParmDecl)) return; auto Diag = diag(CallMove->getExprLoc(), "forwarding reference passed to std::move(), which may " "unexpectedly cause lvalues to be moved; use " "std::forward() instead"); replaceMoveWithForward(UnresolvedLookup, ParmVar, TypeParmDecl, Diag, *Result.Context); } } // namespace bugprone } // namespace tidy } // namespace clang