[clang] [clang][analyzer] Detect use-after-move for 3-arg std::move (PR #196602)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 7 12:28:28 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/196602
>From 8be2a279f3d3b1458b573df72baa5a3fbcfef258 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 4 May 2026 19:23:51 -0400
Subject: [PATCH 1/6] [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 67f0f416dd4da950f0d4453ef443f9f968bf7bd7 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 8 May 2026 13:59:37 -0400
Subject: [PATCH 2/6] 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 at -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 at -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
+}
+
>From 20925aa35177bdd30a6595e064c6739f59e5e8d6 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 26 May 2026 14:21:40 +0200
Subject: [PATCH 3/6] [StaticAnalyzer] Addressed maintainers' review.
---
.../StaticAnalyzer/Checkers/MoveChecker.cpp | 37 ++++++++++++++++++-
.../test/Analysis/use-after-move-iterator.cpp | 29 +++++++++++++++
clang/test/Analysis/use-after-move.cpp | 17 ---------
3 files changed, 64 insertions(+), 19 deletions(-)
create mode 100644 clang/test/Analysis/use-after-move-iterator.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index f874b257523e8..47e9c585054ae 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -210,6 +210,7 @@ class MoveChecker
private:
BugType BT{this, "Use-after-move", categories::CXXMoveSemantics};
+ // Modelling the 3 argument std::move calls
const CallDescription StdMoveCall{CDM::SimpleFunc, {"std", "move"}, 3};
// Check if the given form of potential misuse of a given object
@@ -236,6 +237,9 @@ class MoveChecker
} // end anonymous namespace
REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState)
+
+// Custom map designed to track containers whose contents were moved by 3-arg
+// std::move
REGISTER_MAP_WITH_PROGRAMSTATE(TrackedContentsMap, const MemRegion *, RegionState)
// Define the inter-checker API.
@@ -539,9 +543,38 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
ObjectKind OK = classifyObject(State, Region, RD);
- if (shouldBeTracked(OK)) {
+ const auto *BackInsCall = dyn_cast<CallExpr>(CE->getArg(2)->IgnoreImpCasts());
+ if (!BackInsCall)
+ return false;
+
+ const Expr *DestExpr = BackInsCall->getArg(0)->IgnoreImpCasts();
+ if (!DestExpr)
+ return false;
+
+ const auto *DestDRE = dyn_cast<DeclRefExpr>(DestExpr);
+ if (!DestDRE)
+ return false;
+
+ const auto *DestVD = dyn_cast<VarDecl>(DestDRE->getDecl());
+ if (!DestVD)
+ return false;
+
+ const MemRegion *DestRegion =
+ State->getLValue(DestVD, C.getLocationContext()).getAsRegion();
+ if (!DestRegion)
+ return false;
+
+ SValBuilder &SVB = State->getStateManager().getSValBuilder();
+ SVal ReturnVal = SVB.conjureSymbolVal(Call, C.blockCount());
+ State = State->BindExpr(CE, C.getLocationContext(), ReturnVal);
+
+ State = State->invalidateRegions({DestRegion}, Call.getCFGElementRef(),
+ C.blockCount(), C.getLocationContext(),
+ /*CausesPointerEscape=*/false);
+
+ if (shouldBeTracked(OK))
State = State->set<TrackedContentsMap>(Region, RegionState::getMoved());
- }
+
C.addTransition(State);
return true;
}
diff --git a/clang/test/Analysis/use-after-move-iterator.cpp b/clang/test/Analysis/use-after-move-iterator.cpp
new file mode 100644
index 0000000000000..50dd7e57b42e4
--- /dev/null
+++ b/clang/test/Analysis/use-after-move-iterator.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_analyze_cc1 -std=c++17 -analyzer-checker=core,cplusplus.Move,alpha.cplusplus.IteratorModeling -analyzer-config aggressive-binary-operation-simplification=true -analyzer-config c++-container-inlining=false %s -verify -analyzer-config display-checker-name=false
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+
+//===----------------------------------------------------------------------===//
+// Test suite for test functions that require both MoveChecker.cpp and
+// IteratorModeling.cpp to be enabled.
+// NOTE: Currently the iterator dereference detection is only working when
+// IteratorModeling is enabled.
+//===----------------------------------------------------------------------===//
+
+void iteratorDerefSource() {
+ std::list<std::string> l1;
+ l1.push_back("l1");
+ std::list<std::string> l2;
+
+ std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+ *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}}
+}
+
+void iteratorDerefDest() {
+ 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
+}
diff --git a/clang/test/Analysis/use-after-move.cpp b/clang/test/Analysis/use-after-move.cpp
index 82f0509c0053b..b26f0320f014b 100644
--- a/clang/test/Analysis/use-after-move.cpp
+++ b/clang/test/Analysis/use-after-move.cpp
@@ -1016,22 +1016,6 @@ struct OtherMoveSafeClasses {
}
};
-// 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 at -1 {{Method called on moved-from object 'l1'}}
-}
-*/
-
void safeOperatorAfterMove() {
std::list<std::string> l1;
l1.push_back("l1");
@@ -1050,4 +1034,3 @@ void sizeAfterMove() {
std::move(l1.begin(), l1.end(), std::back_inserter(l2));
l1.size(); // no-warning
}
-
>From 00c5cf9e103a88ad9af285104f878c5f932ad8ff Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 2 Jun 2026 13:13:46 +0200
Subject: [PATCH 4/6] [analyzer] Removed AST matching to get containers' region
and fixed small issues.
---
.../StaticAnalyzer/Checkers/MoveChecker.cpp | 40 +++++++++++--------
.../test/Analysis/use-after-move-iterator.cpp | 24 +++++------
2 files changed, 36 insertions(+), 28 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index 47e9c585054ae..9ff2e90b618aa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -518,31 +518,29 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
if (!StdMoveCall.matches(Call))
return false;
- const auto *BeginCall =
- dyn_cast<CXXMemberCallExpr>(CE->getArg(0)->IgnoreImpCasts());
- if (!BeginCall)
+ const auto *POS = getIteratorPosition(State, Call.getArgSVal(0));
+ if (!POS)
return false;
- const Expr *ContainerExpr = BeginCall->getImplicitObjectArgument();
- const auto *DRE = dyn_cast<DeclRefExpr>(ContainerExpr->IgnoreImpCasts());
- if (!DRE)
+ const MemRegion *ContainerRegion = POS->getContainer();
+ if (!ContainerRegion)
return false;
- const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
- if (!VD)
+ const auto *TypedRegion =
+ dyn_cast_if_present<TypedValueRegion>(ContainerRegion);
+ if (!TypedRegion)
return false;
- const MemRegion *Region =
- State->getLValue(VD, C.getLocationContext()).getAsRegion();
- if (!Region)
- return false;
+ QualType ObjTy = TypedRegion->getValueType();
- const CXXRecordDecl *RD = ContainerExpr->getType()->getAsCXXRecordDecl();
+ const auto *RD = ObjTy->getAsCXXRecordDecl();
if (!RD)
return false;
- ObjectKind OK = classifyObject(State, Region, RD);
+ ObjectKind OK = classifyObject(State, ContainerRegion, RD);
+ // FIXME: Also apply getIteratorPosition from IteratorModeling to recover the
+ // destination region instead of doing AST pattern matching.
const auto *BackInsCall = dyn_cast<CallExpr>(CE->getArg(2)->IgnoreImpCasts());
if (!BackInsCall)
return false;
@@ -573,7 +571,8 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
/*CausesPointerEscape=*/false);
if (shouldBeTracked(OK))
- State = State->set<TrackedContentsMap>(Region, RegionState::getMoved());
+ State = State->set<TrackedContentsMap>(ContainerRegion,
+ RegionState::getMoved());
C.addTransition(State);
return true;
@@ -736,10 +735,19 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
if (const auto *POS = getIteratorPosition(State, Val)) {
const MemRegion *ContainerRegion = POS->getContainer();
+ if (!ContainerRegion)
+ return;
+
+ const auto *TypedRegion =
+ dyn_cast_if_present<TypedValueRegion>(ContainerRegion);
+ if (!TypedRegion)
+ return;
- const auto *TypedRegion = cast<TypedValueRegion>(ContainerRegion);
QualType ObjTy = TypedRegion->getValueType();
const auto *R = ObjTy->getAsCXXRecordDecl();
+ if (!R)
+ return;
+
if (State->get<TrackedContentsMap>(ContainerRegion)) {
ExplodedNode *N = tryToReportBug(ContainerRegion, R, C, MK_FunCall);
if (!N || N->isSink())
diff --git a/clang/test/Analysis/use-after-move-iterator.cpp b/clang/test/Analysis/use-after-move-iterator.cpp
index 50dd7e57b42e4..2357be3a6bb37 100644
--- a/clang/test/Analysis/use-after-move-iterator.cpp
+++ b/clang/test/Analysis/use-after-move-iterator.cpp
@@ -10,20 +10,20 @@
// IteratorModeling is enabled.
//===----------------------------------------------------------------------===//
-void iteratorDerefSource() {
+std::string iteratorDeref(int rng) {
std::list<std::string> l1;
l1.push_back("l1");
std::list<std::string> l2;
- std::move(l1.begin(), l1.end(), std::back_inserter(l2));
- *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}}
-}
-
-void iteratorDerefDest() {
- 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
+ switch (rng) {
+ case 10: {
+ std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+ return *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}}
+ }
+ case 20: {
+ std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+ return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2!
+ }
+ }
+ return 0;
}
>From f273bc1813c6f60c40de5db62352c6afbf334117 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sun, 7 Jun 2026 21:02:37 +0200
Subject: [PATCH 5/6] Addressed requested changes.
---
clang/test/Analysis/use-after-move.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Analysis/use-after-move.cpp b/clang/test/Analysis/use-after-move.cpp
index b26f0320f014b..697785e157b65 100644
--- a/clang/test/Analysis/use-after-move.cpp
+++ b/clang/test/Analysis/use-after-move.cpp
@@ -1033,4 +1033,4 @@ void sizeAfterMove() {
std::move(l1.begin(), l1.end(), std::back_inserter(l2));
l1.size(); // no-warning
-}
+}
\ No newline at end of file
>From 3625597b8d283b6ce4452ca0b7997286c51afa85 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sun, 7 Jun 2026 20:58:20 +0200
Subject: [PATCH 6/6] Polished test suite.
---
.../test/Analysis/use-after-move-iterator.cpp | 37 +++++++++++++++----
clang/test/Analysis/use-after-move.cpp | 26 +++++--------
2 files changed, 40 insertions(+), 23 deletions(-)
diff --git a/clang/test/Analysis/use-after-move-iterator.cpp b/clang/test/Analysis/use-after-move-iterator.cpp
index 2357be3a6bb37..7b63f8415ddf7 100644
--- a/clang/test/Analysis/use-after-move-iterator.cpp
+++ b/clang/test/Analysis/use-after-move-iterator.cpp
@@ -1,8 +1,11 @@
-// RUN: %clang_analyze_cc1 -std=c++17 -analyzer-checker=core,cplusplus.Move,alpha.cplusplus.IteratorModeling -analyzer-config aggressive-binary-operation-simplification=true -analyzer-config c++-container-inlining=false %s -verify -analyzer-config display-checker-name=false
+// RUN: %clang_analyze_cc1 -std=c++17 \
+// RUN: -analyzer-checker=core,cplusplus.Move,alpha.cplusplus.IteratorModeling \
+// RUN: -analyzer-config aggressive-binary-operation-simplification=true \
+// RUN: -analyzer-config c++-container-inlining=false %s \
+// RUN: -verify -analyzer-config display-checker-name=false
#include "Inputs/system-header-simulator-cxx.h"
-
//===----------------------------------------------------------------------===//
// Test suite for test functions that require both MoveChecker.cpp and
// IteratorModeling.cpp to be enabled.
@@ -10,20 +13,40 @@
// IteratorModeling is enabled.
//===----------------------------------------------------------------------===//
-std::string iteratorDeref(int rng) {
+enum Target {MovedFromSource, Destination};
+
+std::string gh137157_iteratorDerefList(Target trg) {
std::list<std::string> l1;
l1.push_back("l1");
std::list<std::string> l2;
- switch (rng) {
- case 10: {
+ switch (trg) {
+ case MovedFromSource: {
std::move(l1.begin(), l1.end(), std::back_inserter(l2));
return *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}}
}
- case 20: {
+ case Destination: {
std::move(l1.begin(), l1.end(), std::back_inserter(l2));
return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2!
}
}
- return 0;
+ return {};
}
+
+std::string gh137157_iteratorDerefVector(Target trg) {
+ std::vector<std::string> l1;
+ l1.push_back("l1");
+ std::vector<std::string> l2;
+
+ switch (trg) {
+ case MovedFromSource: {
+ std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+ return *l1.cbegin(); // expected-warning {{Method called on moved-from object 'l1'}}
+ }
+ case Destination: {
+ std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+ return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2!
+ }
+ }
+ return {};
+}
\ No newline at end of file
diff --git a/clang/test/Analysis/use-after-move.cpp b/clang/test/Analysis/use-after-move.cpp
index 697785e157b65..3973f22414513 100644
--- a/clang/test/Analysis/use-after-move.cpp
+++ b/clang/test/Analysis/use-after-move.cpp
@@ -1016,21 +1016,15 @@ struct OtherMoveSafeClasses {
}
};
-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
+template<class Container>
+void safeOperatorAfterMove(Container lst) {
+ Container dest;
+ lst.push_back(typename Container::value_type {});
+ std::move(lst.begin(), lst.end(), std::back_inserter(dest)); // no-warning
+ lst.size();
}
-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
-}
\ No newline at end of file
+template void safeOperatorAfterMove<std::list<int>>(std::list<int>);
+template void safeOperatorAfterMove<std::list<std::string>>(std::list<std::string>);
+template void safeOperatorAfterMove<std::vector<int>>(std::vector<int>);
+template void safeOperatorAfterMove<std::vector<std::string>>(std::vector<std::string>);
\ No newline at end of file
More information about the cfe-commits
mailing list