[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
Sun May 31 08:43:22 PDT 2026


https://github.com/ovatonne created https://github.com/llvm/llvm-project/pull/200666

Adds missing coverage of strong exception guarantee for forward_list::push_front and forward_list::emplace_front.

The goal is to add the missing coverage to close https://github.com/microsoft/STL/issues/5126. In this thread, I thought there were other missing functions, but I was wrong.

I also added other throwing functions in the helper and refactored it to remove the TODOs from the test.
In retrospect, I think it's probably a bad idea and probably even wrong (testing things that shouldn't be tested). I left it because it may be useful to have more functions to test other container's exception guarantees. If it is wrong, I'd be happy to stick to the first commit only.

Related to https://github.com/llvm/llvm-project/issues/118366

>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/2] 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/2] 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);
     });
   }
 



More information about the libcxx-commits mailing list