https://github.com/benedekaibas created 
https://github.com/llvm/llvm-project/pull/196602

This implementation detects a use-after-move for the 3-arguments std::move on 
containers. This PR fixes #137157.

Since my current implementation uses `IteratorModeling` which is in alpha stage 
I mark this PR as draft.

When both the `IteratorModeling` and `MoveChecker` are enabled my 
implementation works to detect the use-after-move for the 3 argument std::move 
case. 

```cpp
std::move(l1.begin(), l1.end(), std::back_inserter(l2));
std::cout << "l1: " << *l1.cbegin() << '\n'; // <--- should have a 
use-after-move
```

```text
move_iterator.cpp:14:28: warning: Method called on moved-from object 'l1' of
      type 'std::list' [cplusplus.Move]
   14 |     std::cout << "l1: " << *l1.cbegin() << '\n'; // <--- should ...
      |                            ^~~~~~~~~~~~
```

`evalCall` models the 3-arg `std::move` pattern  and marks the source container 
in `TrackedContentsMap` to avoid false positives on safe method calls. In 
`checkPreCall` I recover the iterator's container through `getIteratorPosition` 
and check it against `TrackedContentsMap` to emit the warning.

I have been thinking about alternative solutions that do not depend on 
`IteratorModeling`, but I think it would be more time saving to ask maintainers 
about possible solutions before I start my own implementation.

>From c9ad17f36d073b747f9ae52c074a221681885db1 Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Mon, 4 May 2026 19:23:51 -0400
Subject: [PATCH 1/2] [analyzer] 3-arg std::move initial commit for maintainer
 review.

---
 .../StaticAnalyzer/Checkers/MoveChecker.cpp   | 81 ++++++++++++++++++-
 1 file changed, 77 insertions(+), 4 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index ba8281b186c5d..2cc46923c8dc5 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -12,19 +12,23 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "Iterator.h"
 #include "Move.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/ExprCXX.h"
+#include "clang/Basic/OperatorKinds.h"
 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
 #include "clang/StaticAnalyzer/Core/Checker.h"
 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
 #include "llvm/ADT/StringSet.h"
 
 using namespace clang;
 using namespace ento;
+using namespace iterator;
 
 namespace {
 struct RegionState {
@@ -47,11 +51,12 @@ struct RegionState {
 namespace {
 class MoveChecker
     : public Checker<check::PreCall, check::PostCall,
-                     check::DeadSymbols, check::RegionChanges> {
+                     check::DeadSymbols, check::RegionChanges, eval::Call> {
 public:
   void checkPreCall(const CallEvent &MC, CheckerContext &C) const;
   void checkPostCall(const CallEvent &MC, CheckerContext &C) const;
   void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
   ProgramStateRef
   checkRegionChanges(ProgramStateRef State,
                      const InvalidatedSymbols *Invalidated,
@@ -205,6 +210,8 @@ class MoveChecker
 private:
   BugType BT{this, "Use-after-move", categories::CXXMoveSemantics};
 
+  const CallDescription StdMoveCall{CDM::SimpleFunc, {"std", "move"}, 3};
+
   // Check if the given form of potential misuse of a given object
   // should be reported. If so, get it reported. The callback from which
   // this function was called should immediately return after the call
@@ -229,6 +236,7 @@ class MoveChecker
 } // end anonymous namespace
 
 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, 
RegionState)
+REGISTER_MAP_WITH_PROGRAMSTATE(TrackedContentsMap, const MemRegion *, 
RegionState)
 
 // Define the inter-checker API.
 namespace clang {
@@ -495,6 +503,48 @@ void MoveChecker::checkPostCall(const CallEvent &Call,
   assert(!C.isDifferent() && "Should not have made transitions on this path!");
 }
 
+bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
+
+  const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return false;
+  ProgramStateRef State = C.getState();
+
+  if (!StdMoveCall.matches(Call))
+    return false;
+
+  const auto *BeginCall =
+      dyn_cast<CXXMemberCallExpr>(CE->getArg(0)->IgnoreImpCasts());
+  if (!BeginCall)
+    return false;
+
+  const Expr *ContainerExpr = BeginCall->getImplicitObjectArgument();
+  const auto *DRE = dyn_cast<DeclRefExpr>(ContainerExpr->IgnoreImpCasts());
+  if (!DRE)
+    return false;
+
+  const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+  if (!VD)
+    return false;
+
+  const MemRegion *Region =
+      State->getLValue(VD, C.getLocationContext()).getAsRegion();
+  if (!Region)
+    return false;
+
+  const CXXRecordDecl *RD = ContainerExpr->getType()->getAsCXXRecordDecl();
+  if (!RD)
+    return false;
+
+  ObjectKind OK = classifyObject(State, Region, RD);
+
+  if (shouldBeTracked(OK)) {
+    State = State->set<TrackedContentsMap>(Region, RegionState::getMoved());
+  }
+  C.addTransition(State);
+  return true;
+}
+
 bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const {
   // We abandon the cases where bool/void/void* conversion happens.
   if (const auto *ConversionDec =
@@ -643,6 +693,32 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
   // class in which the encountered method defined.
   ThisRegion = ThisRegion->getMostDerivedObjectRegion();
 
+  // Store class declaration as well, for bug reporting purposes.
+  const CXXRecordDecl *RD = MethodDecl->getParent();
+
+  if (MethodDecl->getOverloadedOperator() == OO_Star ||
+      MethodDecl->getOverloadedOperator() == OO_Arrow) {
+    SVal Val = IC->getCXXThisVal();
+
+    if (const auto *POS = getIteratorPosition(State, Val)) {
+      const MemRegion *ContainerRegion = POS->getContainer();
+
+      const auto *TypedRegion = cast<TypedValueRegion>(ContainerRegion);
+      QualType ObjTy = TypedRegion->getValueType();
+      const auto *R = ObjTy->getAsCXXRecordDecl();
+      if (State->get<TrackedContentsMap>(ContainerRegion)) {
+        ExplodedNode *N = tryToReportBug(ContainerRegion, R, C, MK_FunCall);
+        if (!N || N->isSink())
+          return;
+
+        State = State->set<TrackedContentsMap>(ContainerRegion,
+                                               RegionState::getReported());
+        C.addTransition(State, N);
+        return;
+      }
+    }
+  }
+
   if (isStateResetMethod(MethodDecl)) {
     State = removeFromState(State, ThisRegion);
     C.addTransition(State);
@@ -652,9 +728,6 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
   if (isMoveSafeMethod(MethodDecl))
     return;
 
-  // Store class declaration as well, for bug reporting purposes.
-  const CXXRecordDecl *RD = MethodDecl->getParent();
-
   if (MethodDecl->isOverloadedOperator()) {
     OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator();
 

>From a4b82b66e85f22611644429c26033d51c8fdd60e Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 8 May 2026 13:59:37 -0400
Subject: [PATCH 2/2] Implemented the 3-arg std::move analysis.

---
 .../StaticAnalyzer/Checkers/MoveChecker.cpp   |  1 +
 .../Inputs/system-header-simulator-cxx.h      | 24 +++++++++++++
 clang/test/Analysis/use-after-move.cpp        | 36 +++++++++++++++++++
 3 files changed, 61 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index 2cc46923c8dc5..f874b257523e8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -508,6 +508,7 @@ bool MoveChecker::evalCall(const CallEvent &Call, 
CheckerContext &C) const {
   const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
   if (!CE)
     return false;
+
   ProgramStateRef State = C.getState();
 
   if (!StdMoveCall.matches(Call))
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h 
b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
index c5aeb0af9d578..78fe4994b8d44 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
@@ -386,6 +386,8 @@ namespace std {
     void assign(std::initializer_list<T> ilist);
 
     void clear();
+    size_t size() const;
+    bool empty() const;
 
     void push_back(const T &value);
     void push_back(T &&value);
@@ -1002,6 +1004,28 @@ next(ForwardIterator it,
   OutputIterator copy(InputIterator first, InputIterator last,
                       OutputIterator result);
 
+  template<class InputIterator, class OutputIterator>
+  OutputIterator move(InputIterator first, InputIterator last,
+                      OutputIterator result);
+
+  template <typename Container> struct back_insert_iterator {
+    Container *item;
+
+    back_insert_iterator(Container& container) : item(&container) {}
+
+    back_insert_iterator& operator=(const typename Container::value_type& 
value) {
+      item->push_back(value);
+      return *this;
+    }
+    back_insert_iterator& operator*() {return *this;}
+    back_insert_iterator& operator++() {return *this;}
+    back_insert_iterator operator++(int) {return *this;}
+  };
+
+  template <typename Container>
+  back_insert_iterator<Container> back_inserter(Container& c) {
+    return back_insert_iterator<Container>(c);
+  }
 }
 
 #if __cplusplus >= 201103L
diff --git a/clang/test/Analysis/use-after-move.cpp 
b/clang/test/Analysis/use-after-move.cpp
index 24d5dd8a8b3d2..82f0509c0053b 100644
--- a/clang/test/Analysis/use-after-move.cpp
+++ b/clang/test/Analysis/use-after-move.cpp
@@ -1015,3 +1015,39 @@ struct OtherMoveSafeClasses {
     // aggressive-note@-2   {{Moved-from object 'Task' is moved}}
   }
 };
+
+// This test case does not pass because my implementation uses 
IteratorModeling.cpp
+// and the warning can be only emitted if IteratorModeling is enabled.
+// If both Move checker and the IteratorModeling are enabled then the analyzer 
can emit a warning.
+
+/*
+void starOperatorAfterMove() {
+  std::list<std::string> l1;
+  l1.push_back("l1");
+  std::list<std::string> l2;
+
+  std::move(l1.begin(), l1.end(), std::back_inserter(l2)); // peaceful-note 
{{Object 'l1' is moved}}
+  *l1.cbegin(); // peaceful-warning {{Method called on moved-from object 'l1'}}
+                // peaceful-note@-1 {{Method called on moved-from object 'l1'}}
+}
+*/
+
+void safeOperatorAfterMove() {
+  std::list<std::string> l1;
+  l1.push_back("l1");
+  std::list<std::string> l2;
+
+  std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+  *l2.cbegin(); // no-warning
+}
+
+void sizeAfterMove() {
+  std::list<std::string> l1;
+  l1.push_back("l1");
+  l1.push_back("l2");
+  std::list<std::string> l2;
+
+  std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+  l1.size(); // no-warning
+}
+

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

Reply via email to