https://github.com/SekaiArendelle updated 
https://github.com/llvm/llvm-project/pull/218303

>From 24dfc21213e8e98d829ee8b73fa141973a8421cc Mon Sep 17 00:00:00 2001
From: Arendelle <[email protected]>
Date: Mon, 24 Aug 2026 09:50:41 +0800
Subject: [PATCH 1/4] [clang][analyzer] Handle explicit-object move assignment

Model the explicit object parameter as the assignment target and the following 
parameter as the moved-from source in the cplusplus.Move checker.
---
 .../StaticAnalyzer/Checkers/MoveChecker.cpp   | 39 +++++++++++++--
 clang/test/Analysis/use-after-move-cxx23.cpp  | 47 +++++++++++++++++++
 2 files changed, 83 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Analysis/use-after-move-cxx23.cpp

diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index 9c616a2d17783..47e808afca3a0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -473,7 +473,12 @@ void MoveChecker::checkPostCall(const CallEvent &Call,
   if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator())
     return;
 
-  const auto ArgRegion = AFC->getArgSVal(0).getAsRegion();
+  // For an explicit-object member function, the object parameter is part of
+  // the function's parameter list.  In that case, the object being moved from
+  // is the second argument rather than the first one.
+  const unsigned MoveArgIndex =
+      MethodDecl->isExplicitObjectMemberFunction() ? 1 : 0;
+  const auto ArgRegion = AFC->getArgSVal(MoveArgIndex).getAsRegion();
   if (!ArgRegion)
     return;
 
@@ -482,14 +487,17 @@ void MoveChecker::checkPostCall(const CallEvent &Call,
   if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion)
     return;
 
-  if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC))
+  if (MethodDecl->isExplicitObjectMemberFunction()) {
+    if (AFC->getArgSVal(0).getAsRegion() == ArgRegion)
+      return;
+  } else if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC))
     if (IC->getCXXThisVal().getAsRegion() == ArgRegion)
       return;
 
   const MemRegion *BaseRegion = ArgRegion->getBaseRegion();
   // Skip temp objects because of their short lifetime.
   if (BaseRegion->getAs<CXXTempObjectRegion>() ||
-      AFC->getArgExpr(0)->isPRValue())
+      AFC->getArgExpr(MoveArgIndex)->isPRValue())
     return;
   // If it has already been reported do not need to modify the state.
 
@@ -705,6 +713,31 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
     }
   }
 
+  // Calls to explicit-object member functions are represented as ordinary
+  // function calls because they have no implicit 'this' argument.  Model an
+  // explicit-object assignment here before handling instance calls below.
+  const auto *ExplicitObjectMethod =
+      dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
+  if (ExplicitObjectMethod &&
+      ExplicitObjectMethod->isExplicitObjectMemberFunction() &&
+      ExplicitObjectMethod->getOverloadedOperator() == OO_Equal) {
+    const MemRegion *ThisRegion = Call.getArgSVal(0).getAsRegion();
+    State = removeFromState(State, ThisRegion);
+
+    if (ExplicitObjectMethod->isCopyAssignmentOperator() ||
+        ExplicitObjectMethod->isMoveAssignmentOperator()) {
+      const MemRegion *ArgRegion = Call.getArgSVal(1).getAsRegion();
+      const CXXRecordDecl *RD = ExplicitObjectMethod->getParent();
+      MisuseKind MK = ExplicitObjectMethod->isMoveAssignmentOperator()
+                          ? MK_Move
+                          : MK_Copy;
+      modelUse(State, ArgRegion, RD, MK, C);
+      return;
+    }
+    C.addTransition(State);
+    return;
+  }
+
   const auto IC = dyn_cast<CXXInstanceCall>(&Call);
   if (!IC)
     return;
diff --git a/clang/test/Analysis/use-after-move-cxx23.cpp 
b/clang/test/Analysis/use-after-move-cxx23.cpp
new file mode 100644
index 0000000000000..ff989f43d17d3
--- /dev/null
+++ b/clang/test/Analysis/use-after-move-cxx23.cpp
@@ -0,0 +1,47 @@
+// RUN: %clang_analyze_cc1 -std=c++23 -analyzer-checker=cplusplus.Move \
+// RUN:   -analyzer-output=text -verify %s
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+struct Owner {
+  Owner() = default;
+  Owner(Owner &&) {}
+
+  Owner &operator=(this Owner &self, Owner &&other) {
+    return self;
+  }
+
+  void use() const {}
+};
+
+void moveAssignmentMarksTheSource() {
+  Owner target;
+  Owner source;
+  target = std::move(source); // expected-note {{Object 'source' is moved}}
+  target.use();
+  source.use(); // expected-warning {{Method called on moved-from object 
'source'}}
+                // expected-note@-1 {{Method called on moved-from object 
'source'}}
+}
+
+void moveAssignmentResetsTheTarget() {
+  Owner movedFrom;
+  Owner target = std::move(movedFrom);
+  Owner source;
+  target = std::move(source);
+  target.use();
+}
+
+void movingFromTheSourceTwiceWarns() {
+  Owner firstTarget;
+  Owner secondTarget;
+  Owner source;
+  firstTarget = std::move(source); // expected-note {{Object 'source' is 
moved}}
+  secondTarget = std::move(source); // expected-warning {{Moved-from object 
'source' is moved}}
+                                    // expected-note@-1 {{Moved-from object 
'source' is moved}}
+}
+
+void selfMoveAssignmentDoesNotMarkTheObject() {
+  Owner object;
+  object = std::move(object);
+  object.use();
+}

>From 9d81aed2f462eb38d29e6e5f0b868be3c7d600a1 Mon Sep 17 00:00:00 2001
From: Arendelle <[email protected]>
Date: Mon, 24 Aug 2026 10:11:33 +0800
Subject: [PATCH 2/4] [clang][analyzer] Fix formatting in MoveChecker

---
 clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index 47e808afca3a0..5614ccf0a2c62 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -728,9 +728,8 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
         ExplicitObjectMethod->isMoveAssignmentOperator()) {
       const MemRegion *ArgRegion = Call.getArgSVal(1).getAsRegion();
       const CXXRecordDecl *RD = ExplicitObjectMethod->getParent();
-      MisuseKind MK = ExplicitObjectMethod->isMoveAssignmentOperator()
-                          ? MK_Move
-                          : MK_Copy;
+      MisuseKind MK =
+          ExplicitObjectMethod->isMoveAssignmentOperator() ? MK_Move : MK_Copy;
       modelUse(State, ArgRegion, RD, MK, C);
       return;
     }

>From bec9a2a0be51883ced247f99cd0d496a4df8e95d Mon Sep 17 00:00:00 2001
From: Arendelle <[email protected]>
Date: Tue, 25 Aug 2026 19:11:41 +0800
Subject: [PATCH 3/4] [clang][analyzer] Reuse MethodDecl in MoveChecker

---
 .../StaticAnalyzer/Checkers/MoveChecker.cpp   | 24 ++++++++-----------
 1 file changed, 10 insertions(+), 14 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index 5614ccf0a2c62..7def45ae2170c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -713,23 +713,24 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
     }
   }
 
+  const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
+  if (!MethodDecl)
+    return;
+
   // Calls to explicit-object member functions are represented as ordinary
   // function calls because they have no implicit 'this' argument.  Model an
   // explicit-object assignment here before handling instance calls below.
-  const auto *ExplicitObjectMethod =
-      dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
-  if (ExplicitObjectMethod &&
-      ExplicitObjectMethod->isExplicitObjectMemberFunction() &&
-      ExplicitObjectMethod->getOverloadedOperator() == OO_Equal) {
+  if (MethodDecl->isExplicitObjectMemberFunction() &&
+      MethodDecl->getOverloadedOperator() == OO_Equal) {
     const MemRegion *ThisRegion = Call.getArgSVal(0).getAsRegion();
     State = removeFromState(State, ThisRegion);
 
-    if (ExplicitObjectMethod->isCopyAssignmentOperator() ||
-        ExplicitObjectMethod->isMoveAssignmentOperator()) {
+    if (MethodDecl->isCopyAssignmentOperator() ||
+        MethodDecl->isMoveAssignmentOperator()) {
       const MemRegion *ArgRegion = Call.getArgSVal(1).getAsRegion();
-      const CXXRecordDecl *RD = ExplicitObjectMethod->getParent();
+      const CXXRecordDecl *RD = MethodDecl->getParent();
       MisuseKind MK =
-          ExplicitObjectMethod->isMoveAssignmentOperator() ? MK_Move : MK_Copy;
+          MethodDecl->isMoveAssignmentOperator() ? MK_Move : MK_Copy;
       modelUse(State, ArgRegion, RD, MK, C);
       return;
     }
@@ -745,11 +746,6 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
   if (!ThisRegion)
     return;
 
-  // The remaining part is check only for method call on a moved-from object.
-  const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl());
-  if (!MethodDecl)
-    return;
-
   // Calling a destructor on a moved object is fine.
   if (isa<CXXDestructorDecl>(MethodDecl))
     return;

>From 2379e538f7fc96177bd26c44cccdf8dd4c1f988f Mon Sep 17 00:00:00 2001
From: Arendelle <[email protected]>
Date: Tue, 25 Aug 2026 19:36:22 +0800
Subject: [PATCH 4/4] [clang][analyzer] Test by-value explicit-object move
 assignment

---
 clang/test/Analysis/use-after-move-cxx23.cpp | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/clang/test/Analysis/use-after-move-cxx23.cpp 
b/clang/test/Analysis/use-after-move-cxx23.cpp
index ff989f43d17d3..4c08b5fd00418 100644
--- a/clang/test/Analysis/use-after-move-cxx23.cpp
+++ b/clang/test/Analysis/use-after-move-cxx23.cpp
@@ -14,6 +14,16 @@ struct Owner {
   void use() const {}
 };
 
+struct ByValueOwner {
+  ByValueOwner() = default;
+  ByValueOwner(const ByValueOwner &) {}
+  ByValueOwner(ByValueOwner &&) {}
+
+  void operator=(this ByValueOwner self, ByValueOwner &&other) {}
+
+  void use() const {}
+};
+
 void moveAssignmentMarksTheSource() {
   Owner target;
   Owner source;
@@ -45,3 +55,11 @@ void selfMoveAssignmentDoesNotMarkTheObject() {
   object = std::move(object);
   object.use();
 }
+
+// The by-value object parameter is a copy, so this is not a self-move.
+void byValueObjectParameterIsNotSelfMove() {
+  ByValueOwner object;
+  object = std::move(object); // expected-note {{Object 'object' is moved}}
+  object.use(); // expected-warning {{Method called on moved-from object 
'object'}}
+                // expected-note@-1 {{Method called on moved-from object 
'object'}}
+}

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

Reply via email to