[clang-tools-extra] [clang-tidy] Let `bugprone-use-after-move` also handle calls to `std::forward` (PR #82673)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Feb 23 06:25:52 PST 2024
https://github.com/AMS21 updated https://github.com/llvm/llvm-project/pull/82673
>From 84b816ae115808c9144f4a0808849844a6fcab00 Mon Sep 17 00:00:00 2001
From: AMS21 <AMS21.github at gmail.com>
Date: Thu, 22 Feb 2024 19:24:43 +0100
Subject: [PATCH] [clang-tidy] Let `bugprone-use-after-move` also handle calls
to `std::forward`
Fixes #82023
---
.../clang-tidy/bugprone/UseAfterMoveCheck.cpp | 89 +++++++++++++------
clang-tools-extra/docs/ReleaseNotes.rst | 4 +
.../checkers/bugprone/use-after-move.cpp | 37 ++++++++
3 files changed, 103 insertions(+), 27 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
index c5b6b541096ca9..760053b1b64983 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
@@ -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();
@@ -359,24 +360,55 @@ 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;
+
+ assert(false && "Invalid move type");
+}
+
static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
const UseAfterMove &Use, ClangTidyCheck *Check,
- ASTContext *Context) {
+ ASTContext *Context, MoveType Type) {
SourceLocation UseLoc = Use.DeclRef->getExprLoc();
SourceLocation MoveLoc = MovingCall->getExprLoc();
- Check->diag(UseLoc, "'%0' used after it was moved")
- << MoveArg->getDecl()->getName();
- Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note);
+ StringRef ActionType;
+ StringRef ActionTypePastTense;
+ switch (Type) {
+ case MoveType::Move:
+ ActionType = "move";
+ ActionTypePastTense = "moved";
+ break;
+ case MoveType::Forward:
+ ActionType = "forward";
+ ActionTypePastTense = "forwarded";
+ break;
+ }
+
+ Check->diag(UseLoc, "'%0' used after it was %1")
+ << MoveArg->getDecl()->getName() << ActionTypePastTense;
+ Check->diag(MoveLoc, "%0 occurred here", DiagnosticIDs::Note)
+ << ActionType;
if (Use.EvaluationOrderUndefined) {
Check->diag(UseLoc,
- "the use and move are unsequenced, i.e. there is no guarantee "
+ "the use and %0 are unsequenced, i.e. there is no guarantee "
"about the order in which they are evaluated",
- DiagnosticIDs::Note);
+ DiagnosticIDs::Note)
+ << ActionType;
} else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
- Check->diag(UseLoc,
- "the use happens in a later loop iteration than the move",
- DiagnosticIDs::Note);
+ Check->diag(UseLoc, "the use happens in a later loop iteration than the %0",
+ DiagnosticIDs::Note)
+ << ActionType;
}
}
@@ -387,22 +419,23 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
// the bool.
auto TryEmplaceMatcher =
cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));
- auto CallMoveMatcher =
- callExpr(argumentCountIs(1), callee(functionDecl(hasName("::std::move"))),
- hasArgument(0, declRefExpr().bind("arg")),
- unless(inDecltypeOrTemplateArg()),
- unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
- anyOf(hasAncestor(compoundStmt(
- hasParent(lambdaExpr().bind("containing-lambda")))),
- hasAncestor(functionDecl(anyOf(
- cxxConstructorDecl(
- hasAnyConstructorInitializer(withInitializer(
- expr(anyOf(equalsBoundNode("call-move"),
- hasDescendant(expr(
- equalsBoundNode("call-move")))))
- .bind("containing-ctor-init"))))
- .bind("containing-ctor"),
- functionDecl().bind("containing-func"))))));
+ auto CallMoveMatcher = 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"),
+ anyOf(hasAncestor(compoundStmt(
+ hasParent(lambdaExpr().bind("containing-lambda")))),
+ hasAncestor(functionDecl(
+ anyOf(cxxConstructorDecl(
+ hasAnyConstructorInitializer(withInitializer(
+ expr(anyOf(equalsBoundNode("call-move"),
+ hasDescendant(expr(
+ equalsBoundNode("call-move")))))
+ .bind("containing-ctor-init"))))
+ .bind("containing-ctor"),
+ functionDecl().bind("containing-func"))))));
Finder->addMatcher(
traverse(
@@ -436,6 +469,8 @@ 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;
@@ -470,7 +505,7 @@ 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));
}
}
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index a0b9fcfe0d7774..55c47c7617ce2a 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -130,6 +130,10 @@ Changes in existing checks
<clang-tidy/checks/bugprone/unused-local-non-trivial-variable>` check by
ignoring local variable with ``[maybe_unused]`` attribute.
+- Improved :doc:`bugprone-use-after-move
+ <clang-tidy/checks/bugprone/use-after-move>` check to also handle
+ calls to ``std::forward``.
+
- Cleaned up :doc:`cppcoreguidelines-prefer-member-initializer
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>`
by removing enforcement of rule `C.48
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
index 00b1da1e727e4f..7d9f63479a1b4e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp
@@ -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 {
@@ -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
More information about the cfe-commits
mailing list