Skip to content

Commit 1e62f7b

Browse files
committed
Enhance safety for vector assignment operations
1 parent 4a7a27c commit 1e62f7b

File tree

5 files changed

+431
-4
lines changed

5 files changed

+431
-4
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+
// <vector>
10+
11+
// Check that vector assignments don't change the rhs vector when an operation throws an exception during rallocations triggered by assignments
12+
13+
#include <cassert>
14+
#include <ranges>
15+
#include <vector>
16+
17+
#include "allocators.h"
18+
#include "exception_test_helpers.h"
19+
#include "test_allocator.h"
20+
#include "test_macros.h"
21+
22+
void test_allocation_exception() {
23+
#if TEST_STD_VER >= 14
24+
{
25+
limited_alloc_wrapper<int> alloc1 = limited_allocator<int, 100>();
26+
limited_alloc_wrapper<int> alloc2 = limited_allocator<int, 200>();
27+
std::vector<int, limited_alloc_wrapper<int> > v(100, alloc1);
28+
std::vector<int, limited_alloc_wrapper<int> > in(200, alloc2);
29+
try { // Throw in copy-assignment operator during allocation
30+
v = in;
31+
} catch (const std::exception&) {
32+
}
33+
assert(v.size() == 100);
34+
}
35+
36+
{
37+
limited_alloc_wrapper<int> alloc1 = limited_allocator<int, 100>();
38+
limited_alloc_wrapper<int> alloc2 = limited_allocator<int, 200>();
39+
std::vector<int, limited_alloc_wrapper<int> > v(100, alloc1);
40+
std::vector<int, limited_alloc_wrapper<int> > in(200, alloc2);
41+
try { // Throw in move-assignment operator during allocation
42+
v = std::move(in);
43+
} catch (const std::exception&) {
44+
}
45+
assert(v.size() == 100);
46+
}
47+
#endif
48+
49+
#if TEST_STD_VER >= 11
50+
{
51+
std::vector<int, limited_allocator<int, 5> > v(5);
52+
std::initializer_list<int> in{1, 2, 3, 4, 5, 6};
53+
try { // Throw in operator=(initializer_list<value_type>) during allocation
54+
v = in;
55+
} catch (const std::exception&) {
56+
}
57+
assert(v.size() == 5);
58+
}
59+
60+
{
61+
std::vector<int, limited_allocator<int, 5> > v(5);
62+
std::initializer_list<int> in{1, 2, 3, 4, 5, 6};
63+
try { // Throw in assign(initializer_list<value_type>) during allocation
64+
v.assign(in);
65+
} catch (const std::exception&) {
66+
}
67+
assert(v.size() == 5);
68+
}
69+
#endif
70+
71+
{
72+
std::vector<int, limited_allocator<int, 100> > v(100);
73+
std::vector<int> in(101, 1);
74+
try { // Throw in assign(_ForwardIterator, _ForwardIterator) during allocation
75+
v.assign(in.begin(), in.end());
76+
} catch (const std::exception&) {
77+
}
78+
assert(v.size() == 100);
79+
}
80+
81+
{
82+
std::vector<int, limited_allocator<int, 100> > v(100);
83+
try { // Throw in assign(size_type, const_reference) during allocation
84+
v.assign(101, 1);
85+
} catch (const std::exception&) {
86+
}
87+
assert(v.size() == 100);
88+
}
89+
90+
#if TEST_STD_VER >= 23
91+
{
92+
std::vector<int, limited_allocator<int, 100> > v(100);
93+
std::vector<int> in(101, 1);
94+
try { // Throw in assign(_ForwardIterator, _ForwardIterator) during allocation
95+
v.assign_range(in);
96+
} catch (const std::exception&) {
97+
}
98+
assert(v.size() == 100);
99+
}
100+
#endif
101+
}
102+
103+
void test_construction_exception() {
104+
{
105+
int throw_after = 10;
106+
throwing_t t = throw_after;
107+
std::vector<throwing_t> in(6, t);
108+
std::vector<throwing_t> v(3, t);
109+
try { // Throw in copy-assignment operator from element type during construction
110+
v = in;
111+
} catch (int) {
112+
}
113+
assert(v.size() == 3);
114+
}
115+
116+
#if TEST_STD_VER >= 11
117+
{
118+
int throw_after = 10;
119+
throwing_t t = throw_after;
120+
NONPOCMAAllocator<throwing_t> alloc1(1);
121+
NONPOCMAAllocator<throwing_t> alloc2(2);
122+
std::vector<throwing_t, NONPOCMAAllocator<throwing_t> > in(6, t, alloc1);
123+
std::vector<throwing_t, NONPOCMAAllocator<throwing_t> > v(3, t, alloc2);
124+
try { // Throw in move-assignment operator from element type during construction
125+
v = std::move(in);
126+
} catch (int) {
127+
}
128+
assert(v.size() == 3);
129+
}
130+
#endif
131+
132+
#if TEST_STD_VER >= 11
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+
constexpr MaybePOCMAAllocator(int id) : id_(id) {}
269+
270+
template <class U>
271+
constexpr MaybePOCMAAllocator(const MaybePOCMAAllocator<U, POCMAValue>& other) : id_(other.id_) {}
272+
273+
constexpr T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
274+
275+
constexpr void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
276+
277+
constexpr int id() const { return id_; }
278+
279+
template <class U>
280+
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+
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)