Skip to content

Commit e0c08c8

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

File tree

3 files changed

+348
-2
lines changed

3 files changed

+348
-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: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
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(1);
220+
try {
221+
v.resize(4);
222+
} catch (int) {
223+
}
224+
printf("new: %d, del: %d\n", globalMemCounter.new_called, globalMemCounter.delete_called);
225+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
226+
}
227+
check_new_delete_called();
228+
229+
// void resize(size_type __sz, const_reference __x)
230+
{
231+
// cap < size
232+
throw_context ctx(3);
233+
std::vector<throw_element> v;
234+
v.reserve(1);
235+
try {
236+
throw_element e;
237+
v.resize(4, e);
238+
} catch (int) {
239+
}
240+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
241+
}
242+
check_new_delete_called();
243+
244+
// void assign(_ForwardIterator __first, _ForwardIterator __last)
245+
{
246+
// new size <= cap && new size > size
247+
throw_context ctx(4);
248+
std::vector<throw_element> v;
249+
v.reserve(3);
250+
try {
251+
std::vector<throw_element> data(2);
252+
v.assign(data.begin(), data.end());
253+
} catch (int) {
254+
}
255+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
256+
}
257+
check_new_delete_called();
258+
259+
{
260+
// new size > cap
261+
throw_context ctx(4);
262+
std::vector<throw_element> v;
263+
try {
264+
std::vector<throw_element> data(2);
265+
v.assign(data.begin(), data.end());
266+
} catch (int) {
267+
}
268+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
269+
}
270+
check_new_delete_called();
271+
272+
#if TEST_STD_VER >= 23
273+
// void assign_range(_Range&& __range)
274+
{
275+
throw_context ctx(5);
276+
std::vector<throw_element> v;
277+
try {
278+
std::vector<throw_element> r(3);
279+
v.assign_range(r);
280+
} catch (int) {
281+
}
282+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
283+
}
284+
check_new_delete_called();
285+
#endif
286+
287+
#if TEST_STD_VER > 3
288+
// vector& operator=(initializer_list<value_type> __il)
289+
{
290+
throw_context ctx(5);
291+
std::vector<throw_element> v;
292+
try {
293+
throw_element e;
294+
v = {e, e};
295+
} catch (int) {
296+
}
297+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
298+
}
299+
check_new_delete_called();
300+
#endif
301+
302+
// vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(const vector& __x)
303+
{
304+
throw_context ctx(4);
305+
std::vector<throw_element> v;
306+
try {
307+
std::vector<throw_element> data(2);
308+
v = data;
309+
} catch (int) {
310+
}
311+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
312+
}
313+
check_new_delete_called();
314+
315+
// iterator insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last)
316+
{
317+
throw_context ctx(6);
318+
std::vector<throw_element> v;
319+
v.reserve(10);
320+
try {
321+
std::vector<throw_element> data(3);
322+
v.insert(v.begin(), data.begin(), data.end());
323+
} catch (int) {
324+
}
325+
assert(globalMemCounter.new_called == globalMemCounter.delete_called + 1);
326+
}
327+
check_new_delete_called();
328+
329+
#if TEST_STD_VER >= 23
330+
// iterator insert_range(const_iterator __position, _Range&& __range)
331+
{
332+
throw_context ctx(3);
333+
std::vector<throw_element> v;
334+
try {
335+
std::vector<throw_element> data(2);
336+
v.insert_range(v.begin(), data);
337+
} catch (int) {
338+
}
339+
check_new_delete_called();
340+
}
341+
#endif
342+
343+
return 0;
344+
}

0 commit comments

Comments
 (0)