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
