Skip to content

[libc++] Ensure strong exception guarantee for forward_list::resize #131025

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cIssues.csv
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"`LWG4153 <https://wg21.link/LWG4153>`__","Fix extra ""-1"" for ``philox_engine::max()``","2024-11 (Wrocław)","","",""
"`LWG4154 <https://wg21.link/LWG4154>`__","The Mandates for ``std::packaged_task``'s constructor from a callable entity should consider decaying","2024-11 (Wrocław)","","",""
"`LWG4157 <https://wg21.link/LWG4157>`__","The resolution of LWG3465 was damaged by P2167R3","2024-11 (Wrocław)","","20",""
"`LWG4164 <https://wg21.link/LWG4164>`__","Missing guarantees for ``forward_list`` modifiers","2024-11 (Wrocław)","","",""
"`LWG4164 <https://wg21.link/LWG4164>`__","Missing guarantees for ``forward_list`` modifiers","2024-11 (Wrocław)","|Complete|","21",""
"`LWG4169 <https://wg21.link/LWG4169>`__","``std::atomic<T>``'s default constructor should be constrained","2024-11 (Wrocław)","","",""
"`LWG4170 <https://wg21.link/LWG4170>`__","``contiguous_iterator`` should require ``to_address(I{})``","2024-11 (Wrocław)","","",""
"","","","","",""
Expand Down
34 changes: 14 additions & 20 deletions libcxx/include/forward_list
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,9 @@ public:
}
# endif // _LIBCPP_CXX03_LANG
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, const value_type& __v);
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, size_type __n, const value_type& __v);
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, size_type __n, const value_type& __v) {
return __insert_after(__p, __n, __v);
}
template <class _InputIterator, __enable_if_t<__has_input_iterator_category<_InputIterator>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, _InputIterator __f, _InputIterator __l);

Expand Down Expand Up @@ -876,6 +878,9 @@ private:
template <class _Iter, class _Sent>
_LIBCPP_HIDE_FROM_ABI void __assign_with_sentinel(_Iter __f, _Sent __l);

template <class... _Args>
_LIBCPP_HIDE_FROM_ABI iterator __insert_after(const_iterator __p, size_type __n, _Args&&... __args);

template <class _Compare>
static _LIBCPP_HIDE_FROM_ABI __node_pointer __merge(__node_pointer __f1, __node_pointer __f2, _Compare& __comp);

Expand Down Expand Up @@ -1129,17 +1134,18 @@ forward_list<_Tp, _Alloc>::insert_after(const_iterator __p, const value_type& __
}

template <class _Tp, class _Alloc>
template <class... _Args>
typename forward_list<_Tp, _Alloc>::iterator
forward_list<_Tp, _Alloc>::insert_after(const_iterator __p, size_type __n, const value_type& __v) {
forward_list<_Tp, _Alloc>::__insert_after(const_iterator __p, size_type __n, _Args&&... __args) {
__begin_node_pointer __r = __p.__get_begin();
if (__n > 0) {
__node_pointer __first = this->__create_node(/* next = */ nullptr, __v);
__node_pointer __first = this->__create_node(/* next = */ nullptr, std::forward<_Args>(__args)...);
__node_pointer __last = __first;
# if _LIBCPP_HAS_EXCEPTIONS
try {
# endif // _LIBCPP_HAS_EXCEPTIONS
for (--__n; __n != 0; --__n, __last = __last->__next_) {
__last->__next_ = this->__create_node(/* next = */ nullptr, __v);
__last->__next_ = this->__create_node(/* next = */ nullptr, std::forward<_Args>(__args)...);
}
# if _LIBCPP_HAS_EXCEPTIONS
} catch (...) {
Expand Down Expand Up @@ -1239,14 +1245,8 @@ void forward_list<_Tp, _Alloc>::resize(size_type __n) {
;
if (__i != __e)
erase_after(__p, __e);
else {
__n -= __sz;
if (__n > 0) {
for (__begin_node_pointer __ptr = __p.__get_begin(); __n > 0; --__n, __ptr = __ptr->__next_as_begin()) {
__ptr->__next_ = this->__create_node(/* next = */ nullptr);
}
}
}
else
__insert_after(__p, __n - __sz);
}

template <class _Tp, class _Alloc>
Expand All @@ -1259,14 +1259,8 @@ void forward_list<_Tp, _Alloc>::resize(size_type __n, const value_type& __v) {
;
if (__i != __e)
erase_after(__p, __e);
else {
__n -= __sz;
if (__n > 0) {
for (__begin_node_pointer __ptr = __p.__get_begin(); __n > 0; --__n, __ptr = __ptr->__next_as_begin()) {
__ptr->__next_ = this->__create_node(/* next = */ nullptr, __v);
}
}
}
else
__insert_after(__p, __n - __sz, __v);
}

template <class _Tp, class _Alloc>
Expand Down
51 changes: 51 additions & 0 deletions libcxx/test/std/containers/exception_safety_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <cassert>
#include <cstddef>
#include <forward_list>
#include <functional>
#include <utility>
#include "test_macros.h"
Expand Down Expand Up @@ -53,6 +54,33 @@ int ThrowingCopy<N>::created_by_copying = 0;
template <int N>
int ThrowingCopy<N>::destroyed = 0;

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; }
};

template <int N>
bool ThrowingDefault<N>::throwing_enabled = true;
template <int N>
int ThrowingDefault<N>::default_constructed = 0;

template <int N>
struct std::hash<ThrowingCopy<N>> {
std::size_t operator()(const ThrowingCopy<N>& value) const { return value.x; }
Expand Down Expand Up @@ -95,6 +123,29 @@ 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>;
T::throwing_enabled = false;

std::forward_list<T> c0(Size);
for (int i = 0; i < Size; ++i)
c0.emplace_front(i);
std::forward_list<T> c = c0;
T in[Size];
T::reset();
T::throwing_enabled = true;
try {
func(c, in, in + Size);
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
}
}

#endif // !defined(TEST_HAS_NO_EXCEPTIONS)

#endif // SUPPORT_EXCEPTION_SAFETY_HELPERS_H
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,23 @@
// void assign_range(R&& rg); // C++23
//
// void push_front(const value_type& v);
// template<container-compatible-range<T> R>
// 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
//
// iterator insert_after(const_iterator p, const value_type& v);
// iterator insert_after(const_iterator p, value_type&& v);
// iterator insert_after(const_iterator p, size_type n, const value_type& v);
// iterator insert_after(const_iterator p, initializer_list<value_type> il);
// template <class InputIterator>
// iterator insert_after(const_iterator p,
// InputIterator first, InputIterator last);
// template<container-compatible-range<T> R>
// template<container-compatible-range<T> R>
// iterator insert_range_after(const_iterator position, R&& rg); // C++23
//
// template <class... Args>
// iterator emplace_after(const_iterator p, Args&&... args);
// void resize(size_type n, const value_type& v);
// void resize(size_type n);

#include <forward_list>

Expand All @@ -65,16 +70,33 @@ int main(int, char**) {
using T = ThrowingCopy<ThrowOn>;

// void push_front(const value_type& v);
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
std::forward_list<T> c;
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.push_front(*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);
});

// iterator insert_after(const_iterator p, const value_type& v);
test_exception_safety_throwing_copy</*ThrowOn=*/1, Size>([](T* from, T*) {
std::forward_list<T> c;
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);
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.insert_after(c.before_begin(), std::move(*from));
});

// 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_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.emplace_after(c.before_begin(), std::move(*from));
});
}

{
Expand Down Expand Up @@ -169,40 +191,53 @@ int main(int, char**) {
#if TEST_STD_VER >= 23
// template<container-compatible-range<T> R>
// void prepend_range(R&& rg); // C++23
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
std::forward_list<T> c;
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T* to) {
c.prepend_range(std::ranges::subrange(from, to));
});
#endif

// iterator insert_after(const_iterator p, size_type n, const value_type& v);
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
std::forward_list<T> c;
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.insert_after(c.before_begin(), Size, *from);
});

// template <class InputIterator>
// iterator insert_after(const_iterator p,
// InputIterator first, InputIterator last);
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
std::forward_list<T> c;
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T* to) {
c.insert_after(c.before_begin(), from, to);
});

// 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*) {
c.insert_after(c.before_begin(), il);
});

#if TEST_STD_VER >= 23
// template<container-compatible-range<T> R>
// iterator insert_range_after(const_iterator position, R&& rg); // C++23
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
std::forward_list<T> c;
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T* to) {
c.insert_range_after(c.before_begin(), std::ranges::subrange(from, to));
});
#endif

// void resize(size_type n, const value_type& v);
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
std::forward_list<T> c;
c.resize(Size, *from);
});
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
c.resize(Size * 3, *from);
});

{ // void resize(size_type n);
using X = ThrowingDefault<ThrowOn>;
std::forward_list<X> c0{X{1}, X{2}, X{3}};
std::forward_list<X> c = c0;
try {
c.resize(3 * ThrowOn);
assert(false);
} catch (int) {
assert(c == c0);
}
}
}

return 0;
Expand Down
Loading