Skip to content

Commit f9369cc

Browse files
committed
Enhance safety for vector assignment operations
1 parent 6f16a8b commit f9369cc

File tree

5 files changed

+427
-4
lines changed

5 files changed

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

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)