[clang-tools-extra] [clang-tidy] Fix false positive in bugprone-use-after-move with std::forward on derived classes (PR #199905)
peiqi li via cfe-commits
cfe-commits at lists.llvm.org
Wed May 27 01:55:55 PDT 2026
https://github.com/voyager-jhk created https://github.com/llvm/llvm-project/pull/199905
The `bugprone-use-after-move` check correctly identified partial moves when using `std::move` by matching the `ImplicitCastExpr` (DerivedToBase) as the parent of the call. However, when using `std::forward<Base>`, the cast occurs inside the argument, causing the matcher to miss the cast and falsely report a use-after-move.
This patch manually checks the first argument of the moving call for a `DerivedToBase` cast if the parent matcher fails, ensuring both `move` and `forward` are correctly identified as partial moves.
Fixes #63202
>From e289410b28a28b182864481a66dc523d09425d71 Mon Sep 17 00:00:00 2001
From: voyager-jhk <voyager.lpq at gmail.com>
Date: Wed, 27 May 2026 16:54:08 +0800
Subject: [PATCH] [clang-tidy] Fix false positive in bugprone-use-after-move
with std::forward on derived classes
The `bugprone-use-after-move` check correctly identified partial moves when using `std::move` by matching the `ImplicitCastExpr` (DerivedToBase) as the parent of the call. However, when using `std::forward<Base>`, the cast occurs inside the argument, causing the matcher to miss the cast and falsely report a use-after-move.
This patch manually checks the first argument of the moving call for a `DerivedToBase` cast if the parent matcher fails, ensuring both `move` and `forward` are correctly identified as partial moves.
Fixes #63202
---
.../clang-tidy/bugprone/UseAfterMoveCheck.cpp | 9 ++++
.../use-after-move-forward-derived.cpp | 42 +++++++++++++++++++
2 files changed, 51 insertions(+)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-forward-derived.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
index 399442f52bd33..301b943a8ecd3 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
@@ -641,6 +641,15 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
const CXXRecordDecl *MovedAs =
ParentCast ? ParentCast->getType()->getAsCXXRecordDecl() : nullptr;
+ if (!MovedAs && CallMove && CallMove->getNumArgs() > 0) {
+ if (const auto *ArgCast =
+ dyn_cast<ImplicitCastExpr>(CallMove->getArg(0)->IgnoreParens())) {
+ if (ArgCast->getCastKind() == CK_DerivedToBase) {
+ MovedAs = ArgCast->getType()->getAsCXXRecordDecl();
+ }
+ }
+ }
+
for (Stmt *CodeBlock : CodeBlocks) {
UseAfterMoveFinder Finder(Result.Context, InvalidationFunctions,
ReinitializationFunctions, MovedAs);
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-forward-derived.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-forward-derived.cpp
new file mode 100644
index 0000000000000..4322b94bdf00d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-forward-derived.cpp
@@ -0,0 +1,42 @@
+// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t
+
+#include <utility>
+
+struct Person {
+ Person() = default;
+ Person(Person&&) {}
+};
+
+struct SpecialPerson : Person {
+ int surname;
+
+ // Valid partial move
+ SpecialPerson(SpecialPerson&& sp)
+ : Person(std::move(sp)),
+ surname(std::move(sp.surname))
+ // CHECK-NOTES-NOT: [[@LINE-1]]:{{[0-9]+}}: warning: 'sp' used after it was moved
+ {}
+
+ // Valid partial forward (The original false positive bug)
+ SpecialPerson(SpecialPerson&& sp, int)
+ : Person(std::forward<Person>(sp)),
+ surname(std::move(sp.surname))
+ // CHECK-NOTES-NOT: [[@LINE-1]]:{{[0-9]+}}: warning: 'sp' used after it was forwarded
+ {}
+
+ // Invalid full move (Must warn)
+ SpecialPerson(SpecialPerson&& sp, float) {
+ SpecialPerson other(std::move(sp));
+ sp.surname = 1;
+ // CHECK-NOTES: [[@LINE-1]]:{{[0-9]+}}: warning: 'sp' used after it was moved [bugprone-use-after-move]
+ // CHECK-NOTES: [[@LINE-3]]:{{[0-9]+}}: note: move occurred here
+ }
+
+ // Invalid full forward (Must warn)
+ SpecialPerson(SpecialPerson&& sp, double) {
+ SpecialPerson other(std::forward<SpecialPerson>(sp));
+ sp.surname = 1;
+ // CHECK-NOTES: [[@LINE-1]]:{{[0-9]+}}: warning: 'sp' used after it was forwarded [bugprone-use-after-move]
+ // CHECK-NOTES: [[@LINE-3]]:{{[0-9]+}}: note: forward occurred here
+ }
+};
More information about the cfe-commits
mailing list