[libcxx-commits] [libcxx] [libc++][tests] Add missing strong exception guarantee test coverage in forward_list (PR #200666)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jun 2 13:55:10 PDT 2026
https://github.com/ovatonne updated https://github.com/llvm/llvm-project/pull/200666
>From 2e4d1d8995b66185c7d27fc9c963ae66cf441aa2 Mon Sep 17 00:00:00 2001
From: Odilon Vatonne <ovatonne at gmail.com>
Date: Sun, 31 May 2026 17:07:39 +0200
Subject: [PATCH 1/3] Add missing strong exception guarantee test coverage in
forward_list
---
.../sequences/forwardlist/exception_safety.pass.cpp | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
index a6579e144e6d3..a9fe399b6c18a 100644
--- a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -35,6 +35,7 @@
// void assign_range(R&& rg); // C++23
//
// void push_front(const value_type& v);
+// void push_front(value_type&& v);
// template <class... Args> reference emplace_front(Args&&... args); // reference in C++17
// template<container-compatible-range<T> R>
// void prepend_range(R&& rg); // C++23
@@ -74,9 +75,17 @@ int main(int, char**) {
c.push_front(*from);
});
+ // void push_front(value_type&& v);
+ test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_front(std::move(*from));
+ });
+
// template <class... Args> reference emplace_front(Args&&... args);
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
- c.push_front(*from);
+ c.emplace_front(*from);
+ });
+ test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_front(std::move(*from));
});
// iterator insert_after(const_iterator p, const value_type& v);
>From 5bc73cecad00bc1a6466e9cc188cb2cd8f9fc5b6 Mon Sep 17 00:00:00 2001
From: Odilon Vatonne <ovatonne at gmail.com>
Date: Sun, 31 May 2026 17:17:16 +0200
Subject: [PATCH 2/3] Add missing throwing condition in forward_list exception
safety tests
---
.../std/containers/exception_safety_helpers.h | 132 +++++++++++-------
.../forwardlist/exception_safety.pass.cpp | 59 +++++---
2 files changed, 125 insertions(+), 66 deletions(-)
diff --git a/libcxx/test/std/containers/exception_safety_helpers.h b/libcxx/test/std/containers/exception_safety_helpers.h
index 2d26735d9bb4e..6e463304cce4f 100644
--- a/libcxx/test/std/containers/exception_safety_helpers.h
+++ b/libcxx/test/std/containers/exception_safety_helpers.h
@@ -17,74 +17,91 @@
#include "test_macros.h"
#if !defined(TEST_HAS_NO_EXCEPTIONS)
-template <int N>
-struct ThrowingCopy {
+template <int NDefault, int NInplace, int NCopy, int NMove>
+struct ThrowingBase {
static bool throwing_enabled;
+ static int default_constructed;
+ static int created_inplace;
static int created_by_copying;
+ static int created_by_moving;
static int destroyed;
int x = 0; // Allows distinguishing between different instances.
- ThrowingCopy() = default;
- ThrowingCopy(int value) : x(value) {}
- ~ThrowingCopy() { ++destroyed; }
+ ThrowingBase() {
+ ++default_constructed;
+ if (throwing_enabled && default_constructed == NDefault) {
+ throw -1;
+ }
+ }
+ ThrowingBase(int value) : x(value) {
+ ++created_inplace;
+ if (throwing_enabled && created_inplace == NInplace) {
+ throw -1;
+ }
+ }
+ ~ThrowingBase() { ++destroyed; }
- ThrowingCopy(const ThrowingCopy& other) : x(other.x) {
+ ThrowingBase(const ThrowingBase& other) : x(other.x) {
++created_by_copying;
- if (throwing_enabled && created_by_copying == N) {
+ if (throwing_enabled && created_by_copying == NCopy) {
+ throw -1;
+ }
+ }
+
+ ThrowingBase(ThrowingBase&& other) : x(other.x) {
+ ++created_by_moving;
+ if (throwing_enabled && created_by_moving == NMove) {
throw -1;
}
}
// Defined to silence GCC warnings. For test purposes, only copy construction is considered `created_by_copying`.
- ThrowingCopy& operator=(const ThrowingCopy& other) {
+ ThrowingBase& operator=(const ThrowingBase& other) {
+ x = other.x;
+ return *this;
+ }
+ ThrowingBase& operator=(ThrowingBase&& other) {
x = other.x;
return *this;
}
- friend bool operator==(const ThrowingCopy& lhs, const ThrowingCopy& rhs) { return lhs.x == rhs.x; }
- friend bool operator<(const ThrowingCopy& lhs, const ThrowingCopy& rhs) { return lhs.x < rhs.x; }
+ friend bool operator==(const ThrowingBase& lhs, const ThrowingBase& rhs) { return lhs.x == rhs.x; }
+ friend bool operator<(const ThrowingBase& lhs, const ThrowingBase& rhs) { return lhs.x < rhs.x; }
- static void reset() { created_by_copying = destroyed = 0; }
+ static void reset() {
+ default_constructed = created_inplace = created_by_copying = created_by_moving = destroyed = 0;
+ }
};
-template <int N>
-bool ThrowingCopy<N>::throwing_enabled = true;
-template <int N>
-int ThrowingCopy<N>::created_by_copying = 0;
-template <int N>
-int ThrowingCopy<N>::destroyed = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+bool ThrowingBase<NDefault, NInplace, NCopy, NMove>::throwing_enabled = true;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::default_constructed = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::created_inplace = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::created_by_copying = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::created_by_moving = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::destroyed = 0;
+
+template <int NDefault, int NInplace, int NCopy, int NMove>
+struct std::hash<ThrowingBase<NDefault, NInplace, NCopy, NMove>> {
+ std::size_t operator()(const ThrowingBase<NDefault, NInplace, NCopy, NMove>& value) const { return value.x; }
+};
template <int N>
-struct ThrowingDefault {
- static bool throwing_enabled;
- static int default_constructed;
- static int destroyed;
- int x = 0;
-
- ThrowingDefault() {
- ++default_constructed;
- if (throwing_enabled && default_constructed == N) {
- throw -1;
- }
- }
-
- ThrowingDefault(int value) : x(value) {}
- ThrowingDefault(const ThrowingDefault& other) = default;
- friend bool operator==(const ThrowingDefault& lhs, const ThrowingDefault& rhs) { return lhs.x == rhs.x; }
- friend bool operator<(const ThrowingDefault& lhs, const ThrowingDefault& rhs) { return lhs.x < rhs.x; }
-
- static void reset() { default_constructed = destroyed = 0; }
-};
+using ThrowingDefault = ThrowingBase<N, 0, 0, 0>;
template <int N>
-bool ThrowingDefault<N>::throwing_enabled = true;
+using ThrowingInplace = ThrowingBase<0, N, 0, 0>;
+
template <int N>
-int ThrowingDefault<N>::default_constructed = 0;
+using ThrowingCopy = ThrowingBase<0, 0, N, 0>;
template <int N>
-struct std::hash<ThrowingCopy<N>> {
- std::size_t operator()(const ThrowingCopy<N>& value) const { return value.x; }
-};
+using ThrowingMove = ThrowingBase<0, 0, 0, N>;
template <int ThrowOn, int Size, class Func>
void test_exception_safety_throwing_copy(Func&& func) {
@@ -123,9 +140,9 @@ void test_exception_safety_throwing_copy_container(Func&& func) {
}
}
-template <int ThrowOn, int Size, class Func>
-void test_strong_exception_safety_throwing_copy(Func&& func) {
- using T = ThrowingCopy<ThrowOn>;
+template <int ThrowOnDefault, int ThrowOnInplace, int ThrowOnCopy, int ThrowOnMove, int Size, class Func>
+void test_strong_exception_safety(Func&& func) {
+ using T = ThrowingBase<ThrowOnDefault, ThrowOnInplace, ThrowOnCopy, ThrowOnMove>;
T::throwing_enabled = false;
std::forward_list<T> c0(Size);
@@ -140,12 +157,31 @@ void test_strong_exception_safety_throwing_copy(Func&& func) {
assert(false); // The function call above should throw.
} catch (int) {
- assert(T::created_by_copying == ThrowOn);
- assert(T::destroyed == ThrowOn - 1); // No destructor call for the partially-constructed element.
- assert(c == c0); // Strong exception guarantee
+ assert(T::default_constructed == ThrowOnDefault);
+ assert(T::created_inplace == ThrowOnInplace);
+ assert(T::created_by_copying == ThrowOnCopy);
+ assert(T::created_by_moving == ThrowOnMove);
+ assert(T::destroyed == ThrowOnDefault + ThrowOnInplace + ThrowOnCopy + ThrowOnMove -
+ 1); // No destructor call for the partially-constructed element.
+ assert(c == c0); // Strong exception guarantee
}
}
+template <int ThrowOn, int Size, class Func>
+void test_strong_exception_safety_throwing_inplace(Func&& func) {
+ test_strong_exception_safety<0, ThrowOn, 0, 0, Size, Func>(std::forward<Func>(func));
+}
+
+template <int ThrowOn, int Size, class Func>
+void test_strong_exception_safety_throwing_copy(Func&& func) {
+ test_strong_exception_safety<0, 0, ThrowOn, 0, Size, Func>(std::forward<Func>(func));
+}
+
+template <int ThrowOn, int Size, class Func>
+void test_strong_exception_safety_throwing_move(Func&& func) {
+ test_strong_exception_safety<0, 0, 0, ThrowOn, Size, Func>(std::forward<Func>(func));
+}
+
#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
#endif // SUPPORT_EXCEPTION_SAFETY_HELPERS_H
diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
index a9fe399b6c18a..4d6ec2731b719 100644
--- a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -10,11 +10,6 @@
// UNSUPPORTED: c++03, no-exceptions
-// TODO:
-// - throwing upon moving;
-// - initializer lists;
-// - throwing when constructing the element in place.
-
// forward_list(size_type n, const value_type& v);
// forward_list(size_type n, const value_type& v, const allocator_type& a);
// template <class InputIterator>
@@ -75,36 +70,64 @@ int main(int, char**) {
c.push_front(*from);
});
- // void push_front(value_type&& v);
- test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
- c.emplace_front(std::move(*from));
- });
-
// template <class... Args> reference emplace_front(Args&&... args);
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.emplace_front(*from);
});
- test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
- c.emplace_front(std::move(*from));
- });
// iterator insert_after(const_iterator p, const value_type& v);
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.insert_after(c.before_begin(), *from);
});
- // iterator insert_after(const_iterator p, value_type&& v);
+ // template <class... Args>
+ // iterator emplace_after(const_iterator p, Args&&... args);
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_after(c.before_begin(), *from);
+ });
+ }
+
+ {
+ constexpr int ThrowOn = 1;
+ constexpr int Size = 1;
+ using T = ThrowingMove<ThrowOn>;
+
+ // void push_front(value_type&& v);
+ test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.push_front(std::move(*from));
+ });
+
+ // template <class... Args>
+ // iterator emplace_after(const_iterator p, Args&&... args);
+ test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_after(c.before_begin(), std::move(*from));
+ });
+
+ // template <class... Args> reference emplace_front(Args&&... args);
+ test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_front(std::move(*from));
+ });
+
+ // iterator insert_after(const_iterator p, value_type&& v);
+ test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.insert_after(c.before_begin(), std::move(*from));
});
+ }
+
+ {
+ constexpr int ThrowOn = 1;
+ constexpr int Size = 1;
+ using T = ThrowingInplace<ThrowOn>;
// template <class... Args>
// iterator emplace_after(const_iterator p, Args&&... args);
- test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
- c.emplace_after(c.before_begin(), *from);
+ test_strong_exception_safety_throwing_inplace<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_after(c.before_begin(), from->x);
});
- test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
- c.emplace_after(c.before_begin(), std::move(*from));
+
+ // template <class... Args> reference emplace_front(Args&&... args);
+ test_strong_exception_safety_throwing_inplace<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+ c.emplace_front(from->x);
});
}
>From c6f6a55f244191180c8032a486cf4cd1a2307782 Mon Sep 17 00:00:00 2001
From: Odilon Vatonne <ovatonne at gmail.com>
Date: Tue, 2 Jun 2026 22:54:52 +0200
Subject: [PATCH 3/3] Add missing throwing condition in forward_list for
initializer_list overloads
---
.../forwardlist/exception_safety.pass.cpp | 33 +++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
index 4d6ec2731b719..a345665634276 100644
--- a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -18,14 +18,18 @@
// forward_list(InputIterator first, InputIterator last, const allocator_type& a);
// forward_list(const forward_list& x);
// forward_list(const forward_list& x, const allocator_type& a);
+// forward_list(initializer_list<value_type> il);
+// forward_list(initializer_list<value_type> il, const allocator_type& a);
// template<container-compatible-range<T> R>
// forward_list(from_range_t, R&& rg, const Allocator& = Allocator()); // C++23
//
// forward_list& operator=(const forward_list& x);
+// forward_list& operator=(initializer_list<value_type> il);
//
// template <class InputIterator>
// void assign(InputIterator first, InputIterator last);
// void assign(size_type n, const value_type& v);
+// void assign(initializer_list<value_type> il);
// template<container-compatible-range<T> R>
// void assign_range(R&& rg); // C++23
//
@@ -138,6 +142,8 @@ int main(int, char**) {
using C = std::forward_list<T>;
using Alloc = std::allocator<T>;
+ std::initializer_list<T> il{1, 2, 3, 4, 5};
+
// forward_list(size_type n, const value_type& v);
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
std::forward_list<T> c(Size, *from);
@@ -192,12 +198,30 @@ int main(int, char**) {
(void)c;
});
+ // forward_list(initializer_list<value_type> il);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+ std::forward_list<T> c(il);
+ (void)c;
+ });
+
+ // forward_list(initializer_list<value_type> il, const allocator_type& a);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+ std::forward_list<T> c(il, Alloc());
+ (void)c;
+ });
+
// forward_list& operator=(const forward_list& x);
test_exception_safety_throwing_copy_container<C, ThrowOn, Size>([](C&& in) {
std::forward_list<T> c;
c = in;
});
+ // forward_list& operator=(initializer_list<value_type> il);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+ std::forward_list<T> c;
+ c = il;
+ });
+
// template <class InputIterator>
// void assign(InputIterator first, InputIterator last);
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
@@ -205,6 +229,12 @@ int main(int, char**) {
c.assign(from, to);
});
+ // void assign(initializer_list<value_type> il);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+ std::forward_list<T> c;
+ c.assign(il);
+ });
+
#if TEST_STD_VER >= 23
// template<container-compatible-range<T> R>
// void assign_range(R&& rg); // C++23
@@ -241,8 +271,7 @@ int main(int, char**) {
});
// iterator insert_after(const_iterator p, initializer_list<value_type> il);
- std::initializer_list<T> il{1, 2, 3, 4, 5};
- test_strong_exception_safety_throwing_copy<ThrowOn, Size>([&](std::forward_list<T>& c, T*, T*) {
+ test_strong_exception_safety_throwing_copy<ThrowOn, Size>([&il](std::forward_list<T>& c, T*, T*) {
c.insert_after(c.before_begin(), il);
});
More information about the libcxx-commits
mailing list