Skip to content

Commit 1f467f2

Browse files
committed
Enhance safety for vector assignment operations
1 parent 90df664 commit 1f467f2

File tree

5 files changed

+480
-41
lines changed

5 files changed

+480
-41
lines changed

libcxx/include/__vector/vector.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,9 +1025,14 @@ vector<_Tp, _Allocator>::__assign_with_size(_ForwardIterator __first, _Sentinel
10251025
this->__destruct_at_end(__m);
10261026
}
10271027
} else {
1028+
__split_buffer<value_type, allocator_type&> __v(__recommend(__new_size), 0, __alloc_);
1029+
__v.__construct_at_end_with_size(__first, __new_size);
10281030
__vdeallocate();
1029-
__vallocate(__recommend(__new_size));
1030-
__construct_at_end(__first, __last, __new_size);
1031+
this->__begin_ = __v.__begin_;
1032+
this->__end_ = __v.__end_;
1033+
this->__cap_ = __v.__cap_;
1034+
__v.__first_ = __v.__begin_ = __v.__end_ = __v.__cap_ = nullptr;
1035+
__annotate_new(__new_size);
10311036
}
10321037
}
10331038

@@ -1041,9 +1046,14 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::assign(size_type __n
10411046
else
10421047
this->__destruct_at_end(this->__begin_ + __n);
10431048
} else {
1049+
__split_buffer<value_type, allocator_type&> __v(__recommend(__n), 0, __alloc_);
1050+
__v.__construct_at_end(__n, __u);
10441051
__vdeallocate();
1045-
__vallocate(__recommend(static_cast<size_type>(__n)));
1046-
__construct_at_end(__n, __u);
1052+
this->__begin_ = __v.__begin_;
1053+
this->__end_ = __v.__end_;
1054+
this->__cap_ = __v.__cap_;
1055+
__v.__first_ = __v.__begin_ = __v.__end_ = __v.__cap_ = nullptr;
1056+
__annotate_new(__n);
10471057
}
10481058
}
10491059

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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+
// Check that vector assignments do not alter the RHS vector when an exception is thrown during reallocations triggered by assignments.
14+
15+
#include <cassert>
16+
#include <ranges>
17+
#include <vector>
18+
19+
#include "allocators.h"
20+
#include "exception_test_helpers.h"
21+
#include "test_allocator.h"
22+
#include "test_macros.h"
23+
24+
void test_allocation_exception() {
25+
#if TEST_STD_VER >= 14
26+
{
27+
limited_alloc_wrapper<int> alloc1 = limited_allocator<int, 100>();
28+
limited_alloc_wrapper<int> alloc2 = limited_allocator<int, 200>();
29+
std::vector<int, limited_alloc_wrapper<int> > v(100, alloc1);
30+
std::vector<int, limited_alloc_wrapper<int> > in(200, alloc2);
31+
try { // Throw in copy-assignment operator during allocation
32+
v = in;
33+
} catch (const std::exception&) {
34+
}
35+
assert(v.size() == 100);
36+
}
37+
38+
{
39+
limited_alloc_wrapper<int> alloc1 = limited_allocator<int, 100>();
40+
limited_alloc_wrapper<int> alloc2 = limited_allocator<int, 200>();
41+
std::vector<int, limited_alloc_wrapper<int> > v(100, alloc1);
42+
std::vector<int, limited_alloc_wrapper<int> > in(200, alloc2);
43+
try { // Throw in move-assignment operator during allocation
44+
v = std::move(in);
45+
} catch (const std::exception&) {
46+
}
47+
assert(v.size() == 100);
48+
}
49+
#endif
50+
51+
#if TEST_STD_VER >= 11
52+
{
53+
std::vector<int, limited_allocator<int, 5> > v(5);
54+
std::initializer_list<int> in{1, 2, 3, 4, 5, 6};
55+
try { // Throw in operator=(initializer_list<value_type>) during allocation
56+
v = in;
57+
} catch (const std::exception&) {
58+
}
59+
assert(v.size() == 5);
60+
}
61+
62+
{
63+
std::vector<int, limited_allocator<int, 5> > v(5);
64+
std::initializer_list<int> in{1, 2, 3, 4, 5, 6};
65+
try { // Throw in assign(initializer_list<value_type>) during allocation
66+
v.assign(in);
67+
} catch (const std::exception&) {
68+
}
69+
assert(v.size() == 5);
70+
}
71+
#endif
72+
73+
{
74+
std::vector<int, limited_allocator<int, 100> > v(100);
75+
std::vector<int> in(101, 1);
76+
try { // Throw in assign(_ForwardIterator, _ForwardIterator) during allocation
77+
v.assign(in.begin(), in.end());
78+
} catch (const std::exception&) {
79+
}
80+
assert(v.size() == 100);
81+
}
82+
83+
{
84+
std::vector<int, limited_allocator<int, 100> > v(100);
85+
try { // Throw in assign(size_type, const_reference) during allocation
86+
v.assign(101, 1);
87+
} catch (const std::exception&) {
88+
}
89+
assert(v.size() == 100);
90+
}
91+
92+
#if TEST_STD_VER >= 23
93+
{
94+
std::vector<int, limited_allocator<int, 100> > v(100);
95+
std::vector<int> in(101, 1);
96+
try { // Throw in assign(_ForwardIterator, _ForwardIterator) during allocation
97+
v.assign_range(in);
98+
} catch (const std::exception&) {
99+
}
100+
assert(v.size() == 100);
101+
}
102+
#endif
103+
}
104+
105+
void test_construction_exception() {
106+
{
107+
int throw_after = 10;
108+
throwing_t t = throw_after;
109+
std::vector<throwing_t> in(6, t);
110+
std::vector<throwing_t> v(3, t);
111+
try { // Throw in copy-assignment operator from element type during construction
112+
v = in;
113+
} catch (int) {
114+
}
115+
assert(v.size() == 3);
116+
}
117+
118+
#if TEST_STD_VER >= 11
119+
{
120+
int throw_after = 10;
121+
throwing_t t = throw_after;
122+
NONPOCMAAllocator<throwing_t> alloc1(1);
123+
NONPOCMAAllocator<throwing_t> alloc2(2);
124+
std::vector<throwing_t, NONPOCMAAllocator<throwing_t> > in(6, t, alloc1);
125+
std::vector<throwing_t, NONPOCMAAllocator<throwing_t> > v(3, t, alloc2);
126+
try { // Throw in move-assignment operator from element type during construction
127+
v = std::move(in);
128+
} catch (int) {
129+
}
130+
assert(v.size() == 3);
131+
}
132+
133+
{
134+
int throw_after = 10;
135+
throwing_t t = throw_after;
136+
std::initializer_list<throwing_t> in{t, t, t, t, t, t};
137+
std::vector<throwing_t> v(3, t);
138+
try { // Throw in operator=(initializer_list<value_type>) from element type during construction
139+
v = in;
140+
} catch (int) {
141+
}
142+
assert(v.size() == 3);
143+
}
144+
145+
{
146+
int throw_after = 10;
147+
throwing_t t = throw_after;
148+
std::initializer_list<throwing_t> in{t, t, t, t, t, t};
149+
std::vector<throwing_t> v(3, t);
150+
try { // Throw in assign(initializer_list<value_type>) from element type during construction
151+
v.assign(in);
152+
} catch (int) {
153+
}
154+
assert(v.size() == 3);
155+
}
156+
#endif
157+
158+
{
159+
std::vector<int> v(3);
160+
try { // Throw in assign(_ForwardIterator, _ForwardIterator) from forward iterator during construction
161+
v.assign(throwing_iterator<int, std::forward_iterator_tag>(),
162+
throwing_iterator<int, std::forward_iterator_tag>(6));
163+
} catch (int) {
164+
}
165+
assert(v.size() == 3);
166+
}
167+
168+
{
169+
int throw_after = 10;
170+
throwing_t t = throw_after;
171+
std::vector<throwing_t> in(6, t);
172+
std::vector<throwing_t> v(3, t);
173+
try { // Throw in assign(_ForwardIterator, _ForwardIterator) from element type during construction
174+
v.assign(in.begin(), in.end());
175+
} catch (int) {
176+
}
177+
assert(v.size() == 3);
178+
}
179+
180+
#if TEST_STD_VER >= 23
181+
{
182+
int throw_after = 10;
183+
throwing_t t = throw_after;
184+
std::vector<throwing_t> in(6, t);
185+
std::vector<throwing_t> v(3, t);
186+
try { // Throw in assign_range(_Range&&) from element type during construction
187+
v.assign_range(in);
188+
} catch (int) {
189+
}
190+
assert(v.size() == 3);
191+
}
192+
#endif
193+
194+
{
195+
int throw_after = 4;
196+
throwing_t t = throw_after;
197+
std::vector<throwing_t> v(3, t);
198+
try { // Throw in assign(size_type, const_reference) from element type during construction
199+
v.assign(6, t);
200+
} catch (int) {
201+
}
202+
assert(v.size() == 3);
203+
}
204+
}
205+
206+
int main() {
207+
test_allocation_exception();
208+
test_construction_exception();
209+
}

libcxx/test/support/allocators.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,50 @@ using POCCAAllocator = MaybePOCCAAllocator<T, /*POCCAValue = */true>;
251251
template <class T>
252252
using NonPOCCAAllocator = MaybePOCCAAllocator<T, /*POCCAValue = */false>;
253253

254+
template <class T, bool POCMAValue>
255+
class MaybePOCMAAllocator {
256+
template <class, bool>
257+
friend class MaybePOCMAAllocator;
258+
259+
public:
260+
using propagate_on_container_move_assignment = std::integral_constant<bool, POCMAValue>;
261+
using value_type = T;
262+
263+
template <class U>
264+
struct rebind {
265+
using other = MaybePOCMAAllocator<U, POCMAValue>;
266+
};
267+
268+
TEST_CONSTEXPR MaybePOCMAAllocator(int id) : id_(id) {}
269+
270+
template <class U>
271+
TEST_CONSTEXPR MaybePOCMAAllocator(const MaybePOCMAAllocator<U, POCMAValue>& other) : id_(other.id_) {}
272+
273+
TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
274+
275+
TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
276+
277+
TEST_CONSTEXPR int id() const { return id_; }
278+
279+
template <class U>
280+
TEST_CONSTEXPR friend bool operator==(const MaybePOCMAAllocator& lhs, const MaybePOCMAAllocator<U, POCMAValue>& rhs) {
281+
return lhs.id() == rhs.id();
282+
}
283+
284+
template <class U>
285+
TEST_CONSTEXPR friend bool operator!=(const MaybePOCMAAllocator& lhs, const MaybePOCMAAllocator<U, POCMAValue>& rhs) {
286+
return !(lhs == rhs);
287+
}
288+
289+
private:
290+
int id_;
291+
};
292+
293+
template <class T>
294+
using POCMAAllocator = MaybePOCMAAllocator<T, /*POCMAValue = */ true>;
295+
template <class T>
296+
using NONPOCMAAllocator = MaybePOCMAAllocator<T, /*POCMAValue = */ false>;
297+
254298
#endif // TEST_STD_VER >= 11
255299

256300
#endif // ALLOCATORS_H

0 commit comments

Comments
 (0)