Skip to content

Commit 23d66fd

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

19 files changed

+439
-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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,46 @@ 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+
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+
}
420+
387421
int main(int, char**) {
388422
test_allocation_exceptions();
389423
test_default_ctor_exceptions();
390424
test_copy_ctor_exceptions();
391425
#if TEST_STD_VER >= 11
392426
test_move_ctor_exceptions();
393427
#endif
428+
test_resize_exception_leak();
394429
}

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

Lines changed: 20 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,29 @@ 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+
#if TEST_STD_VER >= 11
98+
{
99+
leak_throw_context ctx(5);
100+
std::vector<leak_throw_t> v;
101+
try {
102+
leak_throw_t e;
103+
v = {e, e};
104+
} catch (int) {
105+
}
106+
CHECK_NEW_DELETE_DIFF(1);
107+
}
108+
check_new_delete_called();
109+
#endif
110+
}
111+
93112
int main(int, char**) {
94113
tests();
95114
#if TEST_STD_VER > 17
96115
static_assert(tests());
97116
#endif
117+
test_assign_initializer_exception_leak();
98118
return 0;
99119
}

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

Lines changed: 20 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,29 @@ 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+
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+
}
72+
5473
int main(int, char**) {
5574
tests();
5675
#if TEST_STD_VER > 17
5776
static_assert(tests());
5877
#endif
78+
test_assign_initializer_exception_leak();
5979
return 0;
6080
}

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

Lines changed: 35 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,44 @@ 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+
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+
}
211+
178212
int main(int, char**) {
179213
test();
180214
#if TEST_STD_VER > 17
181215
static_assert(test());
182216
#endif
217+
test_assign_exception_leak();
183218
return 0;
184219
}

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

Lines changed: 17 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,20 @@ 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+
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+
}
63+
4964
int main(int, char**) {
5065
static_assert(test_constraints<std::vector, int, double>());
5166
test();
@@ -59,5 +74,7 @@ int main(int, char**) {
5974
test_counted_istream_view();
6075
#endif
6176

77+
test_cons_leak();
78+
6279
return 0;
6380
}

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

Lines changed: 17 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,27 @@ 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+
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+
}
252+
238253
int main(int, char**) {
239254
tests();
240255
test_ctor_under_alloc();
241256
#if TEST_STD_VER > 17
242257
static_assert(tests());
243258
#endif
259+
260+
test_cons_leak();
244261
return 0;
245262
}

0 commit comments

Comments
 (0)