| //===--- 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 <algorithm> |
| |
| 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->isImplicit()) |
| ? 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) { |
| // 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<CallExpr>("call-move"); |
| const auto *UnresolvedLookup = |
| Result.Nodes.getNodeAs<UnresolvedLookupExpr>("lookup"); |
| const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var"); |
| const auto *TypeParmDecl = |
| Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl"); |
| |
| // Get the FunctionDecl and FunctionTemplateDecl containing the function |
| // parameter. |
| const auto *FuncForParam = dyn_cast<FunctionDecl>(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 |