Skip to content

Commit 8c44dcb

Browse files
committed
Enhance safety for vector assignment operations
1 parent 7317a6e commit 8c44dcb

File tree

5 files changed

+421
-4
lines changed

5 files changed

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

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)