Skip to content

Commit b32845c

Browse files
authored
[clang-tidy] Let bugprone-use-after-move also handle calls to std::forward (#82673)
Add support for std::forward. Fixes #82023
1 parent 081882e commit b32845c

File tree

4 files changed

+95
-15
lines changed

4 files changed

+95
-15
lines changed

clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,8 @@ void UseAfterMoveFinder::getReinits(
330330
traverse(TK_AsIs, DeclRefMatcher),
331331
unless(parmVarDecl(hasType(
332332
references(qualType(isConstQualified())))))),
333-
unless(callee(functionDecl(hasName("::std::move")))))))
333+
unless(callee(functionDecl(
334+
hasAnyName("::std::move", "::std::forward")))))))
334335
.bind("reinit");
335336

336337
Stmts->clear();
@@ -359,24 +360,46 @@ void UseAfterMoveFinder::getReinits(
359360
}
360361
}
361362

363+
enum class MoveType {
364+
Move, // std::move
365+
Forward, // std::forward
366+
};
367+
368+
static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
369+
if (FuncDecl->getName() == "move")
370+
return MoveType::Move;
371+
if (FuncDecl->getName() == "forward")
372+
return MoveType::Forward;
373+
374+
llvm_unreachable("Invalid move type");
375+
}
376+
362377
static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
363378
const UseAfterMove &Use, ClangTidyCheck *Check,
364-
ASTContext *Context) {
365-
SourceLocation UseLoc = Use.DeclRef->getExprLoc();
366-
SourceLocation MoveLoc = MovingCall->getExprLoc();
379+
ASTContext *Context, MoveType Type) {
380+
const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
381+
const SourceLocation MoveLoc = MovingCall->getExprLoc();
382+
383+
const bool IsMove = (Type == MoveType::Move);
367384

368-
Check->diag(UseLoc, "'%0' used after it was moved")
369-
<< MoveArg->getDecl()->getName();
370-
Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note);
385+
Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1")
386+
<< MoveArg->getDecl()->getName() << IsMove;
387+
Check->diag(MoveLoc, "%select{forward|move}0 occurred here",
388+
DiagnosticIDs::Note)
389+
<< IsMove;
371390
if (Use.EvaluationOrderUndefined) {
372-
Check->diag(UseLoc,
373-
"the use and move are unsequenced, i.e. there is no guarantee "
374-
"about the order in which they are evaluated",
375-
DiagnosticIDs::Note);
391+
Check->diag(
392+
UseLoc,
393+
"the use and %select{forward|move}0 are unsequenced, i.e. "
394+
"there is no guarantee about the order in which they are evaluated",
395+
DiagnosticIDs::Note)
396+
<< IsMove;
376397
} else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
377398
Check->diag(UseLoc,
378-
"the use happens in a later loop iteration than the move",
379-
DiagnosticIDs::Note);
399+
"the use happens in a later loop iteration than the "
400+
"%select{forward|move}0",
401+
DiagnosticIDs::Note)
402+
<< IsMove;
380403
}
381404
}
382405

@@ -388,7 +411,9 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
388411
auto TryEmplaceMatcher =
389412
cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));
390413
auto CallMoveMatcher =
391-
callExpr(argumentCountIs(1), callee(functionDecl(hasName("::std::move"))),
414+
callExpr(argumentCountIs(1),
415+
callee(functionDecl(hasAnyName("::std::move", "::std::forward"))
416+
.bind("move-decl")),
392417
hasArgument(0, declRefExpr().bind("arg")),
393418
unless(inDecltypeOrTemplateArg()),
394419
unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
@@ -436,6 +461,7 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
436461
const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
437462
const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");
438463
const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
464+
const auto *MoveDecl = Result.Nodes.getNodeAs<FunctionDecl>("move-decl");
439465

440466
if (!MovingCall || !MovingCall->getExprLoc().isValid())
441467
MovingCall = CallMove;
@@ -470,7 +496,8 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
470496
UseAfterMoveFinder Finder(Result.Context);
471497
UseAfterMove Use;
472498
if (Finder.find(CodeBlock, MovingCall, Arg->getDecl(), &Use))
473-
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context);
499+
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context,
500+
determineMoveType(MoveDecl));
474501
}
475502
}
476503

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ Changes in existing checks
144144
<clang-tidy/checks/bugprone/unused-return-value>` check by updating the
145145
parameter `CheckedFunctions` to support regexp.
146146

147+
- Improved :doc:`bugprone-use-after-move
148+
<clang-tidy/checks/bugprone/use-after-move>` check to also handle
149+
calls to ``std::forward``.
150+
147151
- Improved :doc:`cppcoreguidelines-missing-std-forward
148152
<clang-tidy/checks/cppcoreguidelines/missing-std-forward>` check by no longer
149153
giving false positives for deleted functions.

clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,18 @@ When analyzing the order in which moves, uses and reinitializations happen (see
177177
section `Unsequenced moves, uses, and reinitializations`_), the move is assumed
178178
to occur in whichever function the result of the ``std::move`` is passed to.
179179

180+
The check also handles perfect-forwarding with ``std::forward`` so the
181+
following code will also trigger a use-after-move warning.
182+
183+
.. code-block:: c++
184+
185+
void consume(int);
186+
187+
void f(int&& i) {
188+
consume(std::forward<int>(i));
189+
consume(std::forward<int>(i)); // use-after-move
190+
}
191+
180192
Use
181193
---
182194

clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept {
111111
return static_cast<typename remove_reference<_Tp>::type &&>(__t);
112112
}
113113

114+
template <class _Tp>
115+
constexpr _Tp&&
116+
forward(typename std::remove_reference<_Tp>::type& __t) noexcept {
117+
return static_cast<_Tp&&>(__t);
118+
}
119+
120+
template <class _Tp>
121+
constexpr _Tp&&
122+
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {
123+
return static_cast<_Tp&&>(__t);
124+
}
125+
114126
} // namespace std
115127

116128
class A {
@@ -1525,3 +1537,28 @@ class PR38187 {
15251537
private:
15261538
std::string val_;
15271539
};
1540+
1541+
namespace issue82023
1542+
{
1543+
1544+
struct S {
1545+
S();
1546+
S(S&&);
1547+
};
1548+
1549+
void consume(S s);
1550+
1551+
template <typename T>
1552+
void forward(T&& t) {
1553+
consume(std::forward<T>(t));
1554+
consume(std::forward<T>(t));
1555+
// CHECK-NOTES: [[@LINE-1]]:27: warning: 't' used after it was forwarded
1556+
// CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here
1557+
}
1558+
1559+
void create() {
1560+
S s;
1561+
forward(std::move(s));
1562+
}
1563+
1564+
} // namespace issue82023

0 commit comments

Comments
 (0)