Skip to content

Commit 74eff9d

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

File tree

3 files changed

+347
-2
lines changed

3 files changed

+347
-2
lines changed

libcxx/include/__vector/vector.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,19 +749,21 @@ class vector {
749749

750750
struct _ConstructTransaction {
751751
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit _ConstructTransaction(vector& __v, size_type __n)
752-
: __v_(__v), __pos_(__v.__end_), __new_end_(__v.__end_ + __n) {
752+
: __v_(__v), __pos_(__v.__end_), __old_end_(__v.__end_), __new_end_(__v.__end_ + __n) {
753753
__v_.__annotate_increase(__n);
754754
}
755755

756756
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~_ConstructTransaction() {
757757
__v_.__end_ = __pos_;
758758
if (__pos_ != __new_end_) {
759+
__v_.__destruct_at_end(__old_end_);
759760
__v_.__annotate_shrink(__new_end_ - __v_.__begin_);
760761
}
761762
}
762763

763764
vector& __v_;
764765
pointer __pos_;
766+
pointer const __old_end_;
765767
const_pointer const __new_end_;
766768

767769
_ConstructTransaction(_ConstructTransaction const&) = delete;

libcxx/test/libcxx/containers/sequences/vector/asan_throw.pass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ void test_insert_n2() {
182182
v.insert(v.cbegin(), 5, ThrowOnCopy());
183183
assert(0);
184184
} catch (int e) {
185-
assert(v.size() == 11);
185+
assert(v.size() == 10);
186186
assert(is_contiguous_container_asan_correct(v));
187187
return;
188188
}
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: no-exceptions
10+
11+
// <vector>
12+
13+
// Make sure elements are destroyed when exceptions thrown in __construct_at_end
14+
15+
#include <cassert>
16+
#include <cstddef>
17+
#include <memory>
18+
#include <vector>
19+
20+
#include "test_macros.h"
21+
#if TEST_STD_VER >= 20
22+
# include <ranges>
23+
#endif
24+
25+
#include "common.h"
26+
#include "count_new.h"
27+
28+
struct throw_context {
29+
static int num;
30+
static int limit;
31+
32+
throw_context(int lim = 2) {
33+
num = 0;
34+
limit = lim;
35+
}
36+
37+
static void inc() {
38+
++num;
39+
if (num >= limit) {
40+
--num;
41+
throw 1;
42+
}
43+
}
44+
45+
static void dec() { --num; }
46+
};
47+
48+
int throw_context::num = 0;
49+
int throw_context::limit = 0;
50+
51+
int debug = 0;
52+
53+
class throw_element {
54+
public:
55+
throw_element() : data(new int(1)) {
56+
try {
57+
throw_context::inc();
58+
} catch (int) {
59+
delete data;
60+
throw;
61+
}
62+
}
63+
64+
throw_element(throw_element const& other) : data(new int(1)) {
65+
(void)other;
66+
try {
67+
throw_context::inc();
68+
} catch (int) {
69+
delete data;
70+
throw;
71+
}
72+
}
73+
74+
~throw_element() {
75+
if (data) {
76+
delete data;
77+
throw_context::dec();
78+
if (debug)
79+
printf("dctor\n");
80+
}
81+
}
82+
83+
throw_element& operator=(throw_element const& other) {
84+
(void)other;
85+
// nothing to do
86+
return *this;
87+
}
88+
89+
private:
90+
int* data;
91+
};
92+
93+
int main(int, char*[]) {
94+
using AllocType = std::allocator<throw_element>;
95+
96+
// vector(size_type __n)
97+
{
98+
throw_context ctx;
99+
try {
100+
std::vector<throw_element> v(3);
101+
} catch (int) {
102+
}
103+
check_new_delete_called();
104+
}
105+
106+
#if TEST_STD_VER >= 14
107+
// vector(size_type __n, const allocator_type& __a)
108+
{
109+
throw_context ctx;
110+
AllocType alloc;
111+
try {
112+
std::vector<throw_element> v(3, alloc);
113+
} catch (int) {
114+
}
115+
check_new_delete_called();
116+
}
117+
#endif
118+
119+
// vector(size_type __n, const value_type& __x)
120+
{
121+
throw_context ctx(3);
122+
try {
123+
throw_element e;
124+
std::vector<throw_element> v(3, e);
125+
} catch (int) {
126+
}
127+
check_new_delete_called();
128+
}
129+
130+
// vector(size_type __n, const value_type& __x, const allocator_type& __a)
131+
{
132+
throw_context ctx(3);
133+
try {
134+
throw_element e;
135+
AllocType alloc;
136+
std::vector<throw_element> v(4, e, alloc);
137+
} catch (int) {
138+
}
139+
check_new_delete_called();
140+
}
141+
142+
// vector(_ForwardIterator __first, _ForwardIterator __last)
143+
{
144+
throw_context ctx(4);
145+
try {
146+
std::vector<throw_element> v1(2);
147+
std::vector<throw_element> v2(v1.begin(), v1.end());
148+
} catch (int) {
149+
}
150+
check_new_delete_called();
151+
}
152+
153+
// vector(_ForwardIterator __first, _ForwardIterator __last, const allocator_type& __a)
154+
{
155+
throw_context ctx(4);
156+
AllocType alloc;
157+
try {
158+
std::vector<throw_element> v1(2);
159+
std::vector<throw_element> v2(v1.begin(), v1.end(), alloc);
160+
} catch (int) {
161+
}
162+
check_new_delete_called();
163+
}
164+
165+
#if TEST_STD_VER >= 23
166+
// vector(from_range_t, _Range&& __range, const allocator_type& __alloc = allocator_type())
167+
{
168+
throw_context ctx(4);
169+
try {
170+
std::vector<throw_element> r(2);
171+
std::vector<throw_element> v(std::from_range, std::views::counted(r.begin(), 2));
172+
} catch (int) {
173+
}
174+
check_new_delete_called();
175+
}
176+
#endif
177+
178+
// vector(const vector& __x)
179+
{
180+
throw_context ctx(4);
181+
try {
182+
std::vector<throw_element> v1(2);
183+
std::vector<throw_element> v2(v1);
184+
} catch (int) {
185+
}
186+
check_new_delete_called();
187+
}
188+
189+
#if TEST_STD_VER > 3
190+
// vector(initializer_list<value_type> __il)
191+
{
192+
throw_context ctx(6);
193+
try {
194+
throw_element e;
195+
std::vector<throw_element> v({e, e, e});
196+
} catch (int) {
197+
}
198+
check_new_delete_called();
199+
}
200+
201+
// vector(initializer_list<value_type> __il, const allocator_type& __a)
202+
{
203+
throw_context ctx(6);
204+
AllocType alloc;
205+
try {
206+
throw_element e;
207+
std::vector<throw_element> v({e, e, e}, alloc);
208+
} catch (int) {
209+
}
210+
check_new_delete_called();
211+
}
212+
#endif
213+
214+
// void resize(size_type __sz)
215+
{
216+
// cap < size
217+
throw_context ctx;
218+
std::vector<throw_element> v;
219+
v.reserve(5);
220+
try {
221+
v.resize(4);
222+
} catch (int) {
223+
}
224+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
225+
}
226+
check_new_delete_called();
227+
228+
// void resize(size_type __sz, const_reference __x)
229+
{
230+
// cap < size
231+
throw_context ctx(3);
232+
std::vector<throw_element> v;
233+
v.reserve(5);
234+
try {
235+
throw_element e;
236+
v.resize(4, e);
237+
} catch (int) {
238+
}
239+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
240+
}
241+
check_new_delete_called();
242+
243+
// void assign(_ForwardIterator __first, _ForwardIterator __last)
244+
{
245+
// new size <= cap && new size > size
246+
throw_context ctx(4);
247+
std::vector<throw_element> v;
248+
v.reserve(3);
249+
try {
250+
std::vector<throw_element> data(2);
251+
v.assign(data.begin(), data.end());
252+
} catch (int) {
253+
}
254+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
255+
}
256+
check_new_delete_called();
257+
258+
{
259+
// new size > cap
260+
throw_context ctx(4);
261+
std::vector<throw_element> v;
262+
try {
263+
std::vector<throw_element> data(2);
264+
v.assign(data.begin(), data.end());
265+
} catch (int) {
266+
}
267+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
268+
}
269+
check_new_delete_called();
270+
271+
#if TEST_STD_VER >= 23
272+
// void assign_range(_Range&& __range)
273+
{
274+
throw_context ctx(5);
275+
std::vector<throw_element> v;
276+
try {
277+
std::vector<throw_element> r(3);
278+
v.assign_range(r);
279+
} catch (int) {
280+
}
281+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
282+
}
283+
check_new_delete_called();
284+
#endif
285+
286+
#if TEST_STD_VER > 3
287+
// vector& operator=(initializer_list<value_type> __il)
288+
{
289+
throw_context ctx(5);
290+
std::vector<throw_element> v;
291+
try {
292+
throw_element e;
293+
v = {e, e};
294+
} catch (int) {
295+
}
296+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
297+
}
298+
check_new_delete_called();
299+
#endif
300+
301+
// vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(const vector& __x)
302+
{
303+
throw_context ctx(4);
304+
std::vector<throw_element> v;
305+
try {
306+
std::vector<throw_element> data(2);
307+
v = data;
308+
} catch (int) {
309+
}
310+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
311+
}
312+
check_new_delete_called();
313+
314+
// iterator insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last)
315+
{
316+
throw_context ctx(6);
317+
std::vector<throw_element> v;
318+
v.reserve(10);
319+
try {
320+
std::vector<throw_element> data(3);
321+
v.insert(v.begin(), data.begin(), data.end());
322+
} catch (int) {
323+
}
324+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
325+
}
326+
check_new_delete_called();
327+
328+
#if TEST_STD_VER >= 23
329+
// iterator insert_range(const_iterator __position, _Range&& __range)
330+
{
331+
throw_context ctx(3);
332+
std::vector<throw_element> v;
333+
try {
334+
std::vector<throw_element> data(2);
335+
v.insert_range(v.begin(), data);
336+
} catch (int) {
337+
}
338+
check_new_delete_called();
339+
}
340+
#endif
341+
342+
return 0;
343+
}

0 commit comments

Comments
 (0)