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 | // (bug report: https://llvm.org/PR58392) |
12 | // Check that vector constructors don't leak memory when an operation inside the constructor throws an exception |
13 | |
14 | #include <cstddef> |
15 | #include <memory> |
16 | #include <type_traits> |
17 | #include <vector> |
18 | |
19 | #include "count_new.h" |
20 | #include "test_iterators.h" |
21 | |
22 | template <class T> |
23 | struct Allocator { |
24 | using value_type = T; |
25 | using is_always_equal = std::false_type; |
26 | |
27 | template <class U> |
28 | Allocator(const Allocator<U>&) {} |
29 | |
30 | Allocator(bool should_throw = true) { |
31 | if (should_throw) |
32 | throw 0; |
33 | } |
34 | |
35 | T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); } |
36 | void deallocate(T* ptr, std::size_t n) { std::allocator<T>().deallocate(ptr, n); } |
37 | |
38 | template <class U> |
39 | friend bool operator==(const Allocator&, const Allocator<U>&) { return true; } |
40 | }; |
41 | |
42 | struct ThrowingT { |
43 | int* throw_after_n_ = nullptr; |
44 | ThrowingT() { throw 0; } |
45 | |
46 | ThrowingT(int& throw_after_n) : throw_after_n_(&throw_after_n) { |
47 | if (throw_after_n == 0) |
48 | throw 0; |
49 | --throw_after_n; |
50 | } |
51 | |
52 | ThrowingT(const ThrowingT&) { |
53 | if (throw_after_n_ == nullptr || *throw_after_n_ == 0) |
54 | throw 1; |
55 | --*throw_after_n_; |
56 | } |
57 | |
58 | ThrowingT& operator=(const ThrowingT&) { |
59 | if (throw_after_n_ == nullptr || *throw_after_n_ == 0) |
60 | throw 1; |
61 | --*throw_after_n_; |
62 | return *this; |
63 | } |
64 | }; |
65 | |
66 | template <class IterCat> |
67 | struct Iterator { |
68 | using iterator_category = IterCat; |
69 | using difference_type = std::ptrdiff_t; |
70 | using value_type = int; |
71 | using reference = int&; |
72 | using pointer = int*; |
73 | |
74 | int i_; |
75 | Iterator(int i = 0) : i_(i) {} |
76 | int& operator*() { |
77 | if (i_ == 1) |
78 | throw 1; |
79 | return i_; |
80 | } |
81 | |
82 | friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.i_ == rhs.i_; } |
83 | |
84 | friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return lhs.i_ != rhs.i_; } |
85 | |
86 | Iterator& operator++() { |
87 | ++i_; |
88 | return *this; |
89 | } |
90 | |
91 | Iterator operator++(int) { |
92 | auto tmp = *this; |
93 | ++i_; |
94 | return tmp; |
95 | } |
96 | }; |
97 | |
98 | void check_new_delete_called() { |
99 | assert(globalMemCounter.new_called == globalMemCounter.delete_called); |
100 | assert(globalMemCounter.new_array_called == globalMemCounter.delete_array_called); |
101 | assert(globalMemCounter.aligned_new_called == globalMemCounter.aligned_delete_called); |
102 | assert(globalMemCounter.aligned_new_array_called == globalMemCounter.aligned_delete_array_called); |
103 | } |
104 | |
105 | int main(int, char**) { |
106 | using AllocVec = std::vector<int, Allocator<int> >; |
107 | try { // vector() |
108 | AllocVec vec; |
109 | } catch (int) { |
110 | } |
111 | check_new_delete_called(); |
112 | |
113 | try { // Throw in vector(size_type) from type |
114 | std::vector<ThrowingT> get_alloc(1); |
115 | } catch (int) { |
116 | } |
117 | check_new_delete_called(); |
118 | |
119 | #if TEST_STD_VER >= 14 |
120 | try { // Throw in vector(size_type, value_type) from type |
121 | int throw_after = 1; |
122 | ThrowingT v(throw_after); |
123 | std::vector<ThrowingT> get_alloc(1, v); |
124 | } catch (int) { |
125 | } |
126 | check_new_delete_called(); |
127 | |
128 | try { // Throw in vector(size_type, const allocator_type&) from allocator |
129 | Allocator<int> alloc(false); |
130 | AllocVec get_alloc(0, alloc); |
131 | } catch (int) { |
132 | } |
133 | check_new_delete_called(); |
134 | |
135 | try { // Throw in vector(size_type, const allocator_type&) from the type |
136 | std::vector<ThrowingT> vec(1, std::allocator<ThrowingT>()); |
137 | } catch (int) { |
138 | } |
139 | check_new_delete_called(); |
140 | #endif // TEST_STD_VER >= 14 |
141 | |
142 | try { // Throw in vector(InputIterator, InputIterator) from input iterator |
143 | std::vector<int> vec((Iterator<std::input_iterator_tag>()), Iterator<std::input_iterator_tag>(2)); |
144 | } catch (int) { |
145 | } |
146 | check_new_delete_called(); |
147 | |
148 | try { // Throw in vector(InputIterator, InputIterator) from forward iterator |
149 | std::vector<int> vec((Iterator<std::forward_iterator_tag>()), Iterator<std::forward_iterator_tag>(2)); |
150 | } catch (int) { |
151 | } |
152 | check_new_delete_called(); |
153 | |
154 | try { // Throw in vector(InputIterator, InputIterator) from allocator |
155 | int a[] = {1, 2}; |
156 | AllocVec vec(cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 2)); |
157 | } catch (int) { |
158 | } |
159 | check_new_delete_called(); |
160 | |
161 | try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from input iterator |
162 | std::allocator<int> alloc; |
163 | std::vector<int> vec(Iterator<std::input_iterator_tag>(), Iterator<std::input_iterator_tag>(2), alloc); |
164 | } catch (int) { |
165 | } |
166 | check_new_delete_called(); |
167 | |
168 | try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from forward iterator |
169 | std::allocator<int> alloc; |
170 | std::vector<int> vec(Iterator<std::forward_iterator_tag>(), Iterator<std::forward_iterator_tag>(2), alloc); |
171 | } catch (int) { |
172 | } |
173 | check_new_delete_called(); |
174 | |
175 | try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator |
176 | int a[] = {1, 2}; |
177 | Allocator<int> alloc(false); |
178 | AllocVec vec(cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 2), alloc); |
179 | } catch (int) { |
180 | // FIXME: never called. |
181 | } |
182 | check_new_delete_called(); |
183 | |
184 | try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator |
185 | int a[] = {1, 2}; |
186 | Allocator<int> alloc(false); |
187 | AllocVec vec(forward_iterator<int*>(a), forward_iterator<int*>(a + 2), alloc); |
188 | } catch (int) { |
189 | // FIXME: never called. |
190 | } |
191 | check_new_delete_called(); |
192 | |
193 | try { // Throw in vector(const vector&) from type |
194 | std::vector<ThrowingT> vec; |
195 | int throw_after = 0; |
196 | vec.emplace_back(args&: throw_after); |
197 | auto vec2 = vec; |
198 | } catch (int) { |
199 | } |
200 | check_new_delete_called(); |
201 | |
202 | try { // Throw in vector(const vector&, const allocator_type&) from type |
203 | std::vector<ThrowingT> vec; |
204 | int throw_after = 1; |
205 | vec.emplace_back(args&: throw_after); |
206 | std::vector<ThrowingT> vec2(vec, std::allocator<int>()); |
207 | } catch (int) { |
208 | } |
209 | check_new_delete_called(); |
210 | |
211 | try { // Throw in vector(vector&&, const allocator_type&) from type |
212 | std::vector<ThrowingT, Allocator<ThrowingT> > vec(Allocator<ThrowingT>(false)); |
213 | int throw_after = 1; |
214 | vec.emplace_back(args&: throw_after); |
215 | std::vector<ThrowingT, Allocator<ThrowingT> > vec2(std::move(vec), Allocator<ThrowingT>(false)); |
216 | } catch (int) { |
217 | } |
218 | check_new_delete_called(); |
219 | |
220 | #if TEST_STD_VER >= 11 |
221 | try { // Throw in vector(initializer_list<value_type>) from type |
222 | int throw_after = 1; |
223 | std::vector<ThrowingT> vec({ThrowingT(throw_after)}); |
224 | } catch (int) { |
225 | } |
226 | check_new_delete_called(); |
227 | |
228 | try { // Throw in vector(initializer_list<value_type>, const allocator_type&) constructor from type |
229 | int throw_after = 1; |
230 | std::vector<ThrowingT> vec({ThrowingT(throw_after)}, std::allocator<ThrowingT>()); |
231 | } catch (int) { |
232 | } |
233 | check_new_delete_called(); |
234 | #endif // TEST_STD_VER >= 11 |
235 | |
236 | return 0; |
237 | } |
238 | |