Skip to content

Commit f20062d

Browse files
committed
[libc++] Destroy elements when exceptions are thrown in __construct_at_end
1 parent 0246f33 commit f20062d

19 files changed

+489
-10
lines changed

libcxx/include/__memory/uninitialized_algorithms.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ uninitialized_fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp&
124124

125125
// uninitialized_fill_n
126126

127+
template <class _Alloc, class _ForwardIterator, class _Size, class _Tp>
128+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
129+
__uninitialized_allocator_fill_n(_Alloc& __alloc, _ForwardIterator __first, _Size __n, const _Tp& __x) {
130+
_ForwardIterator __idx = __first;
131+
auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); });
132+
for (; __n > 0; ++__idx, (void)--__n)
133+
allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__idx), __x);
134+
__guard.__complete();
135+
136+
return __idx;
137+
}
138+
127139
template <class _ValueType, class _ForwardIterator, class _Size, class _Tp>
128140
inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator
129141
__uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) {
@@ -143,6 +155,20 @@ uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) {
143155
return std::__uninitialized_fill_n<_ValueType>(__first, __n, __x);
144156
}
145157

158+
// __uninitialized_allocator_value_construct_n
159+
160+
template <class _Alloc, class _ForwardIterator, class _Size>
161+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
162+
__uninitialized_allocator_value_construct_n(_Alloc& __alloc, _ForwardIterator __first, _Size __n) {
163+
auto __idx = __first;
164+
auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); });
165+
for (; __n > 0; ++__idx, (void)--__n)
166+
allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__idx));
167+
__guard.__complete();
168+
169+
return __idx;
170+
}
171+
146172
#if _LIBCPP_STD_VER >= 17
147173

148174
// uninitialized_default_construct

libcxx/include/__vector/vector.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -941,10 +941,7 @@ vector<_Tp, _Allocator>::__recommend(size_type __new_size) const {
941941
template <class _Tp, class _Allocator>
942942
_LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::__construct_at_end(size_type __n) {
943943
_ConstructTransaction __tx(*this, __n);
944-
const_pointer __new_end = __tx.__new_end_;
945-
for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) {
946-
__alloc_traits::construct(this->__alloc_, std::__to_address(__pos));
947-
}
944+
__tx.__pos_ = std::__uninitialized_allocator_value_construct_n(this->__alloc_, this->__end_, __n);
948945
}
949946

950947
// Copy constructs __n objects starting at __end_ from __x
@@ -957,10 +954,7 @@ template <class _Tp, class _Allocator>
957954
_LIBCPP_CONSTEXPR_SINCE_CXX20 inline void
958955
vector<_Tp, _Allocator>::__construct_at_end(size_type __n, const_reference __x) {
959956
_ConstructTransaction __tx(*this, __n);
960-
const_pointer __new_end = __tx.__new_end_;
961-
for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) {
962-
__alloc_traits::construct(this->__alloc_, std::__to_address(__pos), __x);
963-
}
957+
__tx.__pos_ = std::__uninitialized_allocator_fill_n(this->__alloc_, this->__end_, __n, __x);
964958
}
965959

966960
template <class _Tp, class _Allocator>

libcxx/test/std/containers/sequences/vector/common.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,70 @@ inline std::vector<std::string> getStringInputsWithLength(std::size_t n, std::si
260260
return v;
261261
}
262262

263+
struct leak_throw_context {
264+
leak_throw_context(int lim = 2) {
265+
num() = 0;
266+
limit() = lim;
267+
}
268+
269+
static int& num() {
270+
static int n = 0;
271+
return n;
272+
}
273+
274+
static int& limit() {
275+
static int lim = 0;
276+
return lim;
277+
}
278+
279+
static void inc() {
280+
++num();
281+
if (num() >= limit()) {
282+
--num();
283+
throw 1;
284+
}
285+
}
286+
287+
static void dec() { --num(); }
288+
};
289+
290+
class leak_throw_t {
291+
public:
292+
leak_throw_t() : data(new int(1)) {
293+
try {
294+
leak_throw_context::inc();
295+
} catch (int) {
296+
delete data;
297+
throw;
298+
}
299+
}
300+
301+
leak_throw_t(leak_throw_t const&) : data(new int(1)) {
302+
try {
303+
leak_throw_context::inc();
304+
} catch (int) {
305+
delete data;
306+
throw;
307+
}
308+
}
309+
310+
~leak_throw_t() {
311+
if (data) {
312+
delete data;
313+
leak_throw_context::dec();
314+
}
315+
}
316+
317+
leak_throw_t& operator=(leak_throw_t const&) { return *this; }
318+
319+
private:
320+
int* data;
321+
};
322+
323+
#ifdef DISABLE_NEW_COUNT
324+
# define CHECK_NEW_DELETE_DIFF(...)
325+
#else
326+
# define CHECK_NEW_DELETE_DIFF(__n) assert(globalMemCounter.new_called == globalMemCounter.delete_called + __n)
327+
#endif
328+
263329
#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H

libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
#include <type_traits>
1818
#include <vector>
1919

20-
#include "../common.h"
20+
#ifndef TEST_HAS_NO_EXCEPTIONS
21+
# include "../common.h"
22+
#endif
2123
#include "MoveOnly.h"
2224
#include "count_new.h"
2325
#include "increasing_allocator.h"
@@ -384,11 +386,47 @@ void test_move_ctor_exceptions() {
384386

385387
#endif
386388

389+
void test_resize_exception_leak() {
390+
// Test whether __construct_at_end leaks when exception
391+
#ifndef TEST_HAS_NO_EXCEPTIONS
392+
# if TEST_STD_VER >= 11
393+
{
394+
// cap < size
395+
leak_throw_context ctx;
396+
std::vector<leak_throw_t> v;
397+
v.reserve(5);
398+
try {
399+
v.resize(4);
400+
} catch (int) {
401+
}
402+
CHECK_NEW_DELETE_DIFF(1);
403+
}
404+
check_new_delete_called();
405+
406+
// void resize(size_type __sz, const_reference __x)
407+
{
408+
// cap < size
409+
leak_throw_context ctx(3);
410+
std::vector<leak_throw_t> v;
411+
v.reserve(5);
412+
try {
413+
leak_throw_t e;
414+
v.resize(4, e);
415+
} catch (int) {
416+
}
417+
CHECK_NEW_DELETE_DIFF(1);
418+
}
419+
check_new_delete_called();
420+
# endif
421+
#endif
422+
}
423+
387424
int main(int, char**) {
388425
test_allocation_exceptions();
389426
test_default_ctor_exceptions();
390427
test_copy_ctor_exceptions();
391428
#if TEST_STD_VER >= 11
392429
test_move_ctor_exceptions();
393430
#endif
431+
test_resize_exception_leak();
394432
}

libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.pass.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include "test_allocator.h"
1717
#include "min_allocator.h"
1818
#include "allocators.h"
19+
#ifndef TEST_HAS_NO_EXCEPTIONS
20+
# include "../common.h"
21+
#endif
1922

2023
TEST_CONSTEXPR_CXX20 bool tests() {
2124
{
@@ -90,10 +93,31 @@ TEST_CONSTEXPR_CXX20 bool tests() {
9093
return true;
9194
}
9295

96+
void test_assign_initializer_exception_leak() {
97+
// Test whether __construct_at_end leaks when exception
98+
99+
#ifndef TEST_HAS_NO_EXCEPTIONS
100+
# if TEST_STD_VER >= 11
101+
{
102+
leak_throw_context ctx(5);
103+
std::vector<leak_throw_t> v;
104+
try {
105+
leak_throw_t e;
106+
v = {e, e};
107+
} catch (int) {
108+
}
109+
CHECK_NEW_DELETE_DIFF(1);
110+
}
111+
check_new_delete_called();
112+
# endif
113+
#endif
114+
}
115+
93116
int main(int, char**) {
94117
tests();
95118
#if TEST_STD_VER > 17
96119
static_assert(tests());
97120
#endif
121+
test_assign_initializer_exception_leak();
98122
return 0;
99123
}

libcxx/test/std/containers/sequences/vector/vector.cons/assign_initializer_list.pass.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#include "test_macros.h"
1919
#include "min_allocator.h"
2020
#include "asan_testing.h"
21+
#ifndef TEST_HAS_NO_EXCEPTIONS
22+
# include "../common.h"
23+
#endif
2124

2225
template <typename Vec>
2326
TEST_CONSTEXPR_CXX20 void test(Vec& v) {
@@ -51,10 +54,30 @@ TEST_CONSTEXPR_CXX20 bool tests() {
5154
return true;
5255
}
5356

57+
void test_assign_initializer_exception_leak() {
58+
// Test whether __construct_at_end leaks when exception
59+
#ifndef TEST_HAS_NO_EXCEPTIONS
60+
# if TEST_STD_VER >= 11
61+
{
62+
leak_throw_context ctx(5);
63+
std::vector<leak_throw_t> v;
64+
try {
65+
leak_throw_t e;
66+
v = {e, e};
67+
} catch (int) {
68+
}
69+
CHECK_NEW_DELETE_DIFF(1);
70+
}
71+
check_new_delete_called();
72+
# endif
73+
#endif
74+
}
75+
5476
int main(int, char**) {
5577
tests();
5678
#if TEST_STD_VER > 17
5779
static_assert(tests());
5880
#endif
81+
test_assign_initializer_exception_leak();
5982
return 0;
6083
}

libcxx/test/std/containers/sequences/vector/vector.cons/assign_iter_iter.pass.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
# include "emplace_constructible.h"
2323
# include "container_test_types.h"
2424
#endif
25+
#ifndef TEST_HAS_NO_EXCEPTIONS
26+
# include "../common.h"
27+
#endif
2528

2629
TEST_CONSTEXPR_CXX20 bool test() {
2730
#if TEST_STD_VER >= 11
@@ -175,10 +178,45 @@ TEST_CONSTEXPR_CXX20 bool test() {
175178
return true;
176179
}
177180

181+
void test_assign_exception_leak() {
182+
// Test whether __construct_at_end leaks when exception
183+
#ifndef TEST_HAS_NO_EXCEPTIONS
184+
# if TEST_STD_VER >= 11
185+
{
186+
// new size <= cap && new size > size
187+
leak_throw_context ctx(4);
188+
std::vector<leak_throw_t> v;
189+
v.reserve(3);
190+
try {
191+
std::vector<leak_throw_t> data(2);
192+
v.assign(data.begin(), data.end());
193+
} catch (int) {
194+
}
195+
CHECK_NEW_DELETE_DIFF(1);
196+
}
197+
check_new_delete_called();
198+
199+
{
200+
// new size > cap
201+
leak_throw_context ctx(4);
202+
std::vector<leak_throw_t> v;
203+
try {
204+
std::vector<leak_throw_t> data(2);
205+
v.assign(data.begin(), data.end());
206+
} catch (int) {
207+
}
208+
CHECK_NEW_DELETE_DIFF(1);
209+
}
210+
check_new_delete_called();
211+
# endif
212+
#endif
213+
}
214+
178215
int main(int, char**) {
179216
test();
180217
#if TEST_STD_VER > 17
181218
static_assert(test());
182219
#endif
220+
test_assign_exception_leak();
183221
return 0;
184222
}

libcxx/test/std/containers/sequences/vector/vector.cons/construct_from_range.pass.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#include "../../from_range_sequence_containers.h"
1818
#include "asan_testing.h"
1919
#include "test_macros.h"
20+
#ifndef TEST_HAS_NO_EXCEPTIONS
21+
# include "../common.h"
22+
#endif
2023

2124
constexpr bool test() {
2225
for_all_iterators_and_allocators<int>([]<class Iter, class Sent, class Alloc>() {
@@ -46,6 +49,21 @@ void test_counted_istream_view() {
4649
}
4750
#endif
4851

52+
void test_cons_leak() {
53+
// Test whether __construct_at_end leaks when exception
54+
#ifndef TEST_HAS_NO_EXCEPTIONS
55+
# if TEST_STD_VER >= 11
56+
leak_throw_context ctx(4);
57+
try {
58+
std::vector<leak_throw_t> r(2);
59+
std::vector<leak_throw_t> v(std::from_range, std::views::counted(r.begin(), 2));
60+
} catch (int) {
61+
}
62+
check_new_delete_called();
63+
# endif
64+
#endif
65+
}
66+
4967
int main(int, char**) {
5068
static_assert(test_constraints<std::vector, int, double>());
5169
test();
@@ -59,5 +77,7 @@ int main(int, char**) {
5977
test_counted_istream_view();
6078
#endif
6179

80+
test_cons_leak();
81+
6282
return 0;
6383
}

0 commit comments

Comments
 (0)