https://github.com/voyager-jhk updated 
https://github.com/llvm/llvm-project/pull/199905

>From 215ae67adc0771903e6571b8797d6d85413ec3ff Mon Sep 17 00:00:00 2001
From: voyager-jhk <[email protected]>
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 |  8 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |  2 +
 .../checkers/bugprone/use-after-move.cpp      | 42 +++++++++++++++++++
 3 files changed, 52 insertions(+)

diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
index 361be321185df..1788c9a955322 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
@@ -650,6 +650,14 @@ 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/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index fa53072be1eac..88e3795e1aa15 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -457,6 +457,8 @@ Changes in existing checks
   `CheckedReturnTypes` option from :doc:`bugprone-unused-return-value
   <clang-tidy/checks/bugprone/unused-return-value>`, which caused false
   positives on functions returning ``std::error_code`` or similar types.
+  - Avoid false positives when forwarding derived objects to base class
+    initializers.
 
 - Improved :doc:`cppcoreguidelines-avoid-capturing-lambda-coroutines
   <clang-tidy/checks/cppcoreguidelines/avoid-capturing-lambda-coroutines>`
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 0844aa32825f6..c9b307fd668b1 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
@@ -1941,3 +1941,45 @@ void stdTieParenthesizedPartialReinit() {
   // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved
   // CHECK-NOTES: [[@LINE-5]]:3: note: move occurred here
 }
+namespace GH63202 {
+
+struct Person {
+  std::string name;
+  Person() = default;
+  Person(Person&&) = default;
+  Person& operator=(Person&&) = default;
+};
+
+struct SpecialPerson : Person {
+  std::string surname;
+
+  // Valid: partial move
+  SpecialPerson(SpecialPerson&& sp)
+    : Person(std::move(sp)), surname(std::move(sp.surname)) {}
+
+  // Valid: partial forward — case fixed by this patch
+  SpecialPerson(SpecialPerson&& sp, int)
+    : Person(std::forward<Person>(sp)), surname(std::move(sp.surname)) {}
+
+  // Invalid: access base class member after base move
+  SpecialPerson(SpecialPerson&& sp, char)
+    : Person(std::move(sp)), surname(std::move(sp.name))
+    // CHECK-NOTES: [[@LINE-1]]:48: warning: 'sp' used after it was moved 
[bugprone-use-after-move]
+    // CHECK-NOTES: [[@LINE-2]]:7: note: move occurred here
+  {}
+};
+
+void invalidFullMove(SpecialPerson&& sp) {
+  SpecialPerson other(std::move(sp));
+  sp.surname = "1";
+  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'sp' used after it was moved 
[bugprone-use-after-move]
+  // CHECK-NOTES: [[@LINE-3]]:17: note: move occurred here
+}
+
+void invalidFullForward(SpecialPerson&& sp) {
+  SpecialPerson other(std::forward<SpecialPerson>(sp));
+  sp.surname = "1";
+  // CHECK-NOTES: [[@LINE-1]]:3: warning: 'sp' used after it was forwarded 
[bugprone-use-after-move]
+  // CHECK-NOTES: [[@LINE-3]]:17: note: forward occurred here
+}
+} // namespace GH63202

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to