Skip to content

[clang-tidy] Let bugprone-use-after-move also handle calls to std::forward #82673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ void UseAfterMoveFinder::getReinits(
traverse(TK_AsIs, DeclRefMatcher),
unless(parmVarDecl(hasType(
references(qualType(isConstQualified())))))),
unless(callee(functionDecl(hasName("::std::move")))))))
unless(callee(functionDecl(
hasAnyName("::std::move", "::std::forward")))))))
.bind("reinit");

Stmts->clear();
Expand Down Expand Up @@ -359,24 +360,46 @@ void UseAfterMoveFinder::getReinits(
}
}

enum class MoveType {
Move, // std::move
Forward, // std::forward
};

static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
if (FuncDecl->getName() == "move")
return MoveType::Move;
if (FuncDecl->getName() == "forward")
return MoveType::Forward;

llvm_unreachable("Invalid move type");
}

static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
const UseAfterMove &Use, ClangTidyCheck *Check,
ASTContext *Context) {
SourceLocation UseLoc = Use.DeclRef->getExprLoc();
SourceLocation MoveLoc = MovingCall->getExprLoc();
ASTContext *Context, MoveType Type) {
const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
const SourceLocation MoveLoc = MovingCall->getExprLoc();

const bool IsMove = (Type == MoveType::Move);

Check->diag(UseLoc, "'%0' used after it was moved")
<< MoveArg->getDecl()->getName();
Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note);
Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1")
<< MoveArg->getDecl()->getName() << IsMove;
Check->diag(MoveLoc, "%select{forward|move}0 occurred here",
DiagnosticIDs::Note)
<< IsMove;
if (Use.EvaluationOrderUndefined) {
Check->diag(UseLoc,
"the use and move are unsequenced, i.e. there is no guarantee "
"about the order in which they are evaluated",
DiagnosticIDs::Note);
Check->diag(
UseLoc,
"the use and %select{forward|move}0 are unsequenced, i.e. "
"there is no guarantee about the order in which they are evaluated",
DiagnosticIDs::Note)
<< IsMove;
} else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
Check->diag(UseLoc,
"the use happens in a later loop iteration than the move",
DiagnosticIDs::Note);
"the use happens in a later loop iteration than the "
"%select{forward|move}0",
DiagnosticIDs::Note)
<< IsMove;
}
}

Expand All @@ -388,7 +411,9 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
auto TryEmplaceMatcher =
cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));
auto CallMoveMatcher =
callExpr(argumentCountIs(1), callee(functionDecl(hasName("::std::move"))),
callExpr(argumentCountIs(1),
callee(functionDecl(hasAnyName("::std::move", "::std::forward"))
.bind("move-decl")),
hasArgument(0, declRefExpr().bind("arg")),
unless(inDecltypeOrTemplateArg()),
unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
Expand Down Expand Up @@ -436,6 +461,7 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");
const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
const auto *MoveDecl = Result.Nodes.getNodeAs<FunctionDecl>("move-decl");

if (!MovingCall || !MovingCall->getExprLoc().isValid())
MovingCall = CallMove;
Expand Down Expand Up @@ -470,7 +496,8 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
UseAfterMoveFinder Finder(Result.Context);
UseAfterMove Use;
if (Finder.find(CodeBlock, MovingCall, Arg->getDecl(), &Use))
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context);
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context,
determineMoveType(MoveDecl));
}
}

Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ Changes in existing checks
<clang-tidy/checks/bugprone/unused-return-value>` check by updating the
parameter `CheckedFunctions` to support regexp.

- Improved :doc:`bugprone-use-after-move
<clang-tidy/checks/bugprone/use-after-move>` check to also handle
calls to ``std::forward``.

- Improved :doc:`cppcoreguidelines-missing-std-forward
<clang-tidy/checks/cppcoreguidelines/missing-std-forward>` check by no longer
giving false positives for deleted functions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ When analyzing the order in which moves, uses and reinitializations happen (see
section `Unsequenced moves, uses, and reinitializations`_), the move is assumed
to occur in whichever function the result of the ``std::move`` is passed to.

The check also handles perfect-forwarding with ``std::forward`` so the
following code will also trigger a use-after-move warning.

.. code-block:: c++

void consume(int);

void f(int&& i) {
consume(std::forward<int>(i));
consume(std::forward<int>(i)); // use-after-move
}

Use
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept {
return static_cast<typename remove_reference<_Tp>::type &&>(__t);
}

template <class _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept {
return static_cast<_Tp&&>(__t);
}

template <class _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {
return static_cast<_Tp&&>(__t);
}

} // namespace std

class A {
Expand Down Expand Up @@ -1525,3 +1537,28 @@ class PR38187 {
private:
std::string val_;
};

namespace issue82023
{

struct S {
S();
S(S&&);
};

void consume(S s);

template <typename T>
void forward(T&& t) {
consume(std::forward<T>(t));
consume(std::forward<T>(t));
// CHECK-NOTES: [[@LINE-1]]:27: warning: 't' used after it was forwarded
// CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here
}

void create() {
S s;
forward(std::move(s));
}

} // namespace issue82023