Skip to content

Commit 98121d6

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

19 files changed

+408
-8
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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,44 @@ 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+
{
391+
// cap < size
392+
leak_throw_context ctx;
393+
std::vector<leak_throw_t> v;
394+
v.reserve(5);
395+
try {
396+
v.resize(4);
397+
} catch (int) {
398+
}
399+
CHECK_NEW_DELETE_DIFF(1);
400+
}
401+
check_new_delete_called();
402+
403+
// void resize(size_type __sz, const_reference __x)
404+
{
405+
// cap < size
406+
leak_throw_context ctx(3);
407+
std::vector<leak_throw_t> v;
408+
v.reserve(5);
409+
try {
410+
leak_throw_t e;
411+
v.resize(4, e);
412+
} catch (int) {
413+
}
414+
CHECK_NEW_DELETE_DIFF(1);
415+
}
416+
check_new_delete_called();
417+
}
418+
387419
int main(int, char**) {
388420
test_allocation_exceptions();
389421
test_default_ctor_exceptions();
390422
test_copy_ctor_exceptions();
391423
#if TEST_STD_VER >= 11
392424
test_move_ctor_exceptions();
393425
#endif
426+
test_resize_exception_leak();
394427
}

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

Lines changed: 18 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,27 @@ 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+
{
98+
leak_throw_context ctx(5);
99+
std::vector<leak_throw_t> v;
100+
try {
101+
leak_throw_t e;
102+
v = {e, e};
103+
} catch (int) {
104+
}
105+
CHECK_NEW_DELETE_DIFF(1);
106+
}
107+
check_new_delete_called();
108+
}
109+
93110
int main(int, char**) {
94111
tests();
95112
#if TEST_STD_VER > 17
96113
static_assert(tests());
97114
#endif
115+
test_assign_initializer_exception_leak();
98116
return 0;
99117
}

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

Lines changed: 18 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,27 @@ 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+
{
59+
leak_throw_context ctx(5);
60+
std::vector<leak_throw_t> v;
61+
try {
62+
leak_throw_t e;
63+
v = {e, e};
64+
} catch (int) {
65+
}
66+
CHECK_NEW_DELETE_DIFF(1);
67+
}
68+
check_new_delete_called();
69+
}
70+
5471
int main(int, char**) {
5572
tests();
5673
#if TEST_STD_VER > 17
5774
static_assert(tests());
5875
#endif
76+
test_assign_initializer_exception_leak();
5977
return 0;
6078
}

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

Lines changed: 33 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,42 @@ 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+
{
183+
// new size <= cap && new size > size
184+
leak_throw_context ctx(4);
185+
std::vector<leak_throw_t> v;
186+
v.reserve(3);
187+
try {
188+
std::vector<leak_throw_t> data(2);
189+
v.assign(data.begin(), data.end());
190+
} catch (int) {
191+
}
192+
CHECK_NEW_DELETE_DIFF(1);
193+
}
194+
check_new_delete_called();
195+
196+
{
197+
// new size > cap
198+
leak_throw_context ctx(4);
199+
std::vector<leak_throw_t> v;
200+
try {
201+
std::vector<leak_throw_t> data(2);
202+
v.assign(data.begin(), data.end());
203+
} catch (int) {
204+
}
205+
CHECK_NEW_DELETE_DIFF(1);
206+
}
207+
check_new_delete_called();
208+
}
209+
178210
int main(int, char**) {
179211
test();
180212
#if TEST_STD_VER > 17
181213
static_assert(test());
182214
#endif
215+
test_assign_exception_leak();
183216
return 0;
184217
}

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

Lines changed: 15 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,18 @@ 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+
leak_throw_context ctx(4);
54+
try {
55+
std::vector<leak_throw_t> r(2);
56+
std::vector<leak_throw_t> v(std::from_range, std::views::counted(r.begin(), 2));
57+
} catch (int) {
58+
}
59+
check_new_delete_called();
60+
}
61+
4962
int main(int, char**) {
5063
static_assert(test_constraints<std::vector, int, double>());
5164
test();
@@ -59,5 +72,7 @@ int main(int, char**) {
5972
test_counted_istream_view();
6073
#endif
6174

75+
test_cons_leak();
76+
6277
return 0;
6378
}

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

Lines changed: 15 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,25 @@ 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+
leak_throw_context ctx(4);
243+
try {
244+
std::vector<leak_throw_t> v1(2);
245+
std::vector<leak_throw_t> v2(v1.begin(), v1.end());
246+
} catch (int) {
247+
}
248+
check_new_delete_called();
249+
}
250+
238251
int main(int, char**) {
239252
tests();
240253
test_ctor_under_alloc();
241254
#if TEST_STD_VER > 17
242255
static_assert(tests());
243256
#endif
257+
258+
test_cons_leak();
244259
return 0;
245260
}

0 commit comments

Comments
 (0)