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 | // This test file validates that std::vector<T>::shrink_to_fit provides the strong exception guarantee when |
12 | // T is Cpp17MoveInsertable and its move constructor does not throw exceptions during the shrink_to_fit |
13 | // call. Additionally, it checks that for move-only types where T's move constructor is not noexcept, only |
14 | // the basic exception guarantee is ensured. |
15 | |
16 | #include <cstddef> |
17 | #include <memory> |
18 | #include <type_traits> |
19 | #include <vector> |
20 | |
21 | #include "../common.h" |
22 | #include "MoveOnly.h" |
23 | #include "count_new.h" |
24 | #include "increasing_allocator.h" |
25 | #include "min_allocator.h" |
26 | #include "test_allocator.h" |
27 | #include "test_iterators.h" |
28 | #include "test_macros.h" |
29 | |
30 | template <typename T, typename Alloc> |
31 | void test_allocation_exception_for_strong_guarantee(std::vector<T, Alloc>& v, const std::vector<T>& values) { |
32 | assert(v.size() == values.size()); |
33 | T* old_data = v.data(); |
34 | std::size_t old_size = v.size(); |
35 | std::size_t old_cap = v.capacity(); |
36 | |
37 | try { |
38 | v.shrink_to_fit(); |
39 | } catch (...) { |
40 | } |
41 | |
42 | // As shrink_to_fit may swallow any exceptions, we place the checks outisde the catch block. |
43 | assert(v.data() == old_data); |
44 | assert(v.size() == old_size); |
45 | assert(v.capacity() == old_cap); |
46 | for (std::size_t i = 0; i < v.size(); ++i) |
47 | assert(v[i] == values[i]); |
48 | } |
49 | |
50 | template <typename T, typename Alloc> |
51 | void test_copy_ctor_exception_for_strong_guarantee(std::vector<throwing_data<T>, Alloc>& v, |
52 | const std::vector<T>& values) { |
53 | assert(v.empty() && !values.empty()); |
54 | v.reserve(values.size() * 2); |
55 | int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation |
56 | for (std::size_t i = 0; i < values.size(); ++i) |
57 | v.emplace_back(values[i], throw_after); |
58 | |
59 | throwing_data<T>* old_data = v.data(); |
60 | std::size_t old_size = v.size(); |
61 | std::size_t old_cap = v.capacity(); |
62 | |
63 | try { |
64 | v.shrink_to_fit(); |
65 | } catch (...) { |
66 | } |
67 | |
68 | assert(v.data() == old_data); |
69 | assert(v.size() == old_size); |
70 | assert(v.capacity() == old_cap); |
71 | for (std::size_t i = 0; i < v.size(); ++i) |
72 | assert(v[i].data_ == values[i]); |
73 | } |
74 | |
75 | #if TEST_STD_VER >= 11 |
76 | |
77 | template <typename T, typename Alloc> |
78 | void test_move_ctor_exception_for_basic_guarantee(std::vector<move_only_throwing_t<T>, Alloc>& v, |
79 | const std::vector<T>& values) { |
80 | assert(v.empty() && !values.empty()); |
81 | v.reserve(values.size() * 2); |
82 | int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation |
83 | for (std::size_t i = 0; i < values.size(); ++i) |
84 | v.emplace_back(values[i], throw_after); |
85 | |
86 | try { |
87 | v.shrink_to_fit(); |
88 | } catch (...) { |
89 | } |
90 | use_unspecified_but_valid_state_vector(v); |
91 | } |
92 | |
93 | #endif |
94 | |
95 | // Check the strong exception guarantee during reallocation failures |
96 | void test_allocation_exceptions() { |
97 | { |
98 | int a[] = {1, 2, 3, 4, 5}; |
99 | std::vector<int> in(a, a + sizeof(a) / sizeof(a[0])); |
100 | std::vector<int, limited_allocator<int, 100> > v; |
101 | v.reserve(100); |
102 | for (std::size_t i = 0; i < in.size(); ++i) |
103 | v.push_back(in[i]); |
104 | test_allocation_exception_for_strong_guarantee(v, in); |
105 | } |
106 | check_new_delete_called(); |
107 | |
108 | { |
109 | std::vector<int> in(50, 42); |
110 | std::vector<int, limited_allocator<int, 100> > v; |
111 | v.reserve(100); |
112 | for (std::size_t i = 0; i < in.size(); ++i) |
113 | v.push_back(in[i]); |
114 | test_allocation_exception_for_strong_guarantee(v, in); |
115 | } |
116 | check_new_delete_called(); |
117 | |
118 | { |
119 | std::vector<int> in(10, 42); |
120 | std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end()); |
121 | v.reserve(90); |
122 | test_allocation_exception_for_strong_guarantee(v, in); |
123 | } |
124 | check_new_delete_called(); |
125 | |
126 | #if TEST_STD_VER >= 11 |
127 | { |
128 | std::vector<MoveOnly> in(10); |
129 | std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10); |
130 | v.reserve(90); |
131 | test_allocation_exception_for_strong_guarantee(v, in); |
132 | } |
133 | check_new_delete_called(); |
134 | |
135 | { |
136 | std::vector<MoveOnly> in(10); |
137 | std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10); |
138 | v.reserve(90); |
139 | in.insert(in.cbegin() + 5, MoveOnly(42)); |
140 | v.insert(v.cbegin() + 5, MoveOnly(42)); |
141 | test_allocation_exception_for_strong_guarantee(v, in); |
142 | } |
143 | check_new_delete_called(); |
144 | #endif |
145 | |
146 | { // Practical example: Testing with 100 integers. |
147 | auto in = getIntegerInputs(n: 100); |
148 | std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end()); |
149 | in.erase(first: in.end() - 10, last: in.end()); |
150 | v.erase(v.end() - 10, v.end()); |
151 | test_allocation_exception_for_strong_guarantee(v, in); |
152 | } |
153 | check_new_delete_called(); |
154 | |
155 | { // Practical example: Testing with 100 strings, each 256 characters long. |
156 | std::vector<std::string> in = getStringInputsWithLength(n: 100, len: 256); |
157 | std::vector<std::string, limited_allocator<std::string, 300> > v(in.begin(), in.end()); |
158 | v.reserve(200); |
159 | test_allocation_exception_for_strong_guarantee(v, in); |
160 | } |
161 | check_new_delete_called(); |
162 | } |
163 | |
164 | // Check the strong exception guarantee during copy-constructor failures |
165 | void test_copy_ctor_exceptions() { |
166 | { |
167 | int a[] = {1, 2, 3, 4, 5}; |
168 | std::vector<int> in(a, a + sizeof(a) / sizeof(a[0])); |
169 | std::vector<throwing_data<int> > v; |
170 | test_copy_ctor_exception_for_strong_guarantee(v, values: in); |
171 | } |
172 | check_new_delete_called(); |
173 | |
174 | { |
175 | int a[] = {1, 2, 3, 4, 5}; |
176 | std::vector<int> in(a, a + sizeof(a) / sizeof(a[0])); |
177 | std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v; |
178 | test_copy_ctor_exception_for_strong_guarantee(v, in); |
179 | } |
180 | check_new_delete_called(); |
181 | |
182 | { |
183 | std::vector<int> in(10, 42); |
184 | std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v; |
185 | test_copy_ctor_exception_for_strong_guarantee(v, in); |
186 | } |
187 | check_new_delete_called(); |
188 | |
189 | { |
190 | std::vector<int> in(10, 42); |
191 | std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v; |
192 | test_copy_ctor_exception_for_strong_guarantee(v, in); |
193 | } |
194 | check_new_delete_called(); |
195 | |
196 | { |
197 | std::vector<int> in(10, 42); |
198 | std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v; |
199 | test_copy_ctor_exception_for_strong_guarantee(v, in); |
200 | } |
201 | check_new_delete_called(); |
202 | |
203 | { // Practical example: Testing with 100 integers. |
204 | auto in = getIntegerInputs(n: 100); |
205 | std::vector<throwing_data<int> > v; |
206 | test_copy_ctor_exception_for_strong_guarantee(v, values: in); |
207 | } |
208 | check_new_delete_called(); |
209 | |
210 | { // Practical example: Testing with 100 strings, each 256 characters long. |
211 | std::vector<std::string> in = getStringInputsWithLength(n: 100, len: 256); |
212 | std::vector<throwing_data<std::string> > v; |
213 | test_copy_ctor_exception_for_strong_guarantee(v, values: in); |
214 | } |
215 | check_new_delete_called(); |
216 | } |
217 | |
218 | #if TEST_STD_VER >= 11 |
219 | |
220 | // Check that if T is Cpp17MoveInsertible && !Cpp17CopyInsertible, and T's move-ctor is not noexcept, then |
221 | // std::vector::shrink_to_fit only provides basic guarantee. |
222 | void test_move_ctor_exceptions() { |
223 | { |
224 | std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
225 | std::vector<move_only_throwing_t<int>> v; |
226 | test_move_ctor_exception_for_basic_guarantee(v, in); |
227 | } |
228 | check_new_delete_called(); |
229 | |
230 | # if TEST_STD_VER >= 23 |
231 | { |
232 | std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
233 | std::vector<move_only_throwing_t<int>, increasing_allocator<move_only_throwing_t<int>>> v; |
234 | test_move_ctor_exception_for_basic_guarantee(v, in); |
235 | } |
236 | check_new_delete_called(); |
237 | # endif |
238 | |
239 | { |
240 | // Practical example: Testing with 100 strings, each 256 characters long. |
241 | std::vector<std::string> in = getStringInputsWithLength(100, 256); |
242 | std::vector<move_only_throwing_t<std::string> > v; |
243 | test_move_ctor_exception_for_basic_guarantee(v, in); |
244 | } |
245 | check_new_delete_called(); |
246 | } |
247 | |
248 | #endif |
249 | |
250 | int main(int, char**) { |
251 | test_allocation_exceptions(); |
252 | test_copy_ctor_exceptions(); |
253 | #if TEST_STD_VER >= 11 |
254 | test_move_ctor_exceptions(); |
255 | #endif |
256 | return 0; |
257 | } |
258 | |