Skip to content

Commit d2226be

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

19 files changed

+456
-9
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,47 @@ void test_move_ctor_exceptions() {
384384

385385
#endif
386386

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

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "test_allocator.h"
1717
#include "min_allocator.h"
1818
#include "allocators.h"
19+
#include "../common.h"
1920

2021
TEST_CONSTEXPR_CXX20 bool tests() {
2122
{
@@ -90,10 +91,31 @@ TEST_CONSTEXPR_CXX20 bool tests() {
9091
return true;
9192
}
9293

94+
void test_assign_initializer_exception_leak() {
95+
// Test whether __construct_at_end leaks when exception
96+
97+
#ifndef TEST_HAS_NO_EXCEPTIONS
98+
# if TEST_STD_VER >= 11
99+
{
100+
leak_throw_context ctx(5);
101+
std::vector<leak_throw_t> v;
102+
try {
103+
leak_throw_t e;
104+
v = {e, e};
105+
} catch (int) {
106+
}
107+
CHECK_NEW_DELETE_DIFF(1);
108+
}
109+
check_new_delete_called();
110+
# endif
111+
#endif
112+
}
113+
93114
int main(int, char**) {
94115
tests();
95116
#if TEST_STD_VER > 17
96117
static_assert(tests());
97118
#endif
119+
test_assign_initializer_exception_leak();
98120
return 0;
99121
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "test_macros.h"
1919
#include "min_allocator.h"
2020
#include "asan_testing.h"
21+
#include "../common.h"
2122

2223
template <typename Vec>
2324
TEST_CONSTEXPR_CXX20 void test(Vec& v) {
@@ -51,10 +52,30 @@ TEST_CONSTEXPR_CXX20 bool tests() {
5152
return true;
5253
}
5354

55+
void test_assign_initializer_exception_leak() {
56+
// Test whether __construct_at_end leaks when exception
57+
#ifndef TEST_HAS_NO_EXCEPTIONS
58+
# if TEST_STD_VER >= 11
59+
{
60+
leak_throw_context ctx(5);
61+
std::vector<leak_throw_t> v;
62+
try {
63+
leak_throw_t e;
64+
v = {e, e};
65+
} catch (int) {
66+
}
67+
CHECK_NEW_DELETE_DIFF(1);
68+
}
69+
check_new_delete_called();
70+
# endif
71+
#endif
72+
}
73+
5474
int main(int, char**) {
5575
tests();
5676
#if TEST_STD_VER > 17
5777
static_assert(tests());
5878
#endif
79+
test_assign_initializer_exception_leak();
5980
return 0;
6081
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# include "emplace_constructible.h"
2323
# include "container_test_types.h"
2424
#endif
25+
#include "../common.h"
2526

2627
TEST_CONSTEXPR_CXX20 bool test() {
2728
#if TEST_STD_VER >= 11
@@ -175,10 +176,45 @@ TEST_CONSTEXPR_CXX20 bool test() {
175176
return true;
176177
}
177178

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

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "../../from_range_sequence_containers.h"
1818
#include "asan_testing.h"
1919
#include "test_macros.h"
20+
#include "../common.h"
2021

2122
constexpr bool test() {
2223
for_all_iterators_and_allocators<int>([]<class Iter, class Sent, class Alloc>() {
@@ -46,6 +47,21 @@ void test_counted_istream_view() {
4647
}
4748
#endif
4849

50+
TEST_CONSTEXPR_CXX20 void test_cons_leak() {
51+
// Test whether __construct_at_end leaks when exception
52+
#ifndef TEST_HAS_NO_EXCEPTIONS
53+
# if TEST_STD_VER >= 11
54+
leak_throw_context ctx(4);
55+
try {
56+
std::vector<leak_throw_t> r(2);
57+
std::vector<leak_throw_t> v(std::from_range, std::views::counted(r.begin(), 2));
58+
} catch (int) {
59+
}
60+
check_new_delete_called();
61+
# endif
62+
#endif
63+
}
64+
4965
int main(int, char**) {
5066
static_assert(test_constraints<std::vector, int, double>());
5167
test();
@@ -59,5 +75,7 @@ int main(int, char**) {
5975
test_counted_istream_view();
6076
#endif
6177

78+
test_cons_leak();
79+
6280
return 0;
6381
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
# include "emplace_constructible.h"
2424
# include "container_test_types.h"
2525
#endif
26+
#include "../common.h"
2627

2728
template <class C, class Iterator>
2829
TEST_CONSTEXPR_CXX20 void test(Iterator first, Iterator last) {
@@ -235,11 +236,28 @@ TEST_CONSTEXPR_CXX20 bool tests() {
235236
return true;
236237
}
237238

239+
TEST_CONSTEXPR_CXX20 void test_cons_leak() {
240+
// Test whether __construct_at_end leaks when exception
241+
#ifndef TEST_HAS_NO_EXCEPTIONS
242+
# if TEST_STD_VER >= 11
243+
leak_throw_context ctx(4);
244+
try {
245+
std::vector<leak_throw_t> v1(2);
246+
std::vector<leak_throw_t> v2(v1.begin(), v1.end());
247+
} catch (int) {
248+
}
249+
check_new_delete_called();
250+
# endif
251+
#endif
252+
}
253+
238254
int main(int, char**) {
239255
tests();
240256
test_ctor_under_alloc();
241257
#if TEST_STD_VER > 17
242258
static_assert(tests());
243259
#endif
260+
261+
test_cons_leak();
244262
return 0;
245263
}

0 commit comments

Comments
 (0)