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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp