[clang] [clang][analyzer] Handle explicit-object move assignment (PR #218303)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 25 05:10:33 PDT 2026
https://github.com/SekaiArendelle updated https://github.com/llvm/llvm-project/pull/218303
>From 24dfc21213e8e98d829ee8b73fa141973a8421cc Mon Sep 17 00:00:00 2001
From: Arendelle <Arendelle_ at outlook.com>
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 at -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 at -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 <Arendelle_ at outlook.com>
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 <Arendelle_ at outlook.com>
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 <Arendelle_ at outlook.com>
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 at -1 {{Method called on moved-from object 'object'}}
+}
More information about the cfe-commits
mailing list