1 // Copyright 2018 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <limits>
16 #include <scoped_allocator>
17
18 #include "gtest/gtest.h"
19 #include "absl/container/internal/raw_hash_set.h"
20 #include "absl/container/internal/tracked.h"
21
22 namespace absl {
23 ABSL_NAMESPACE_BEGIN
24 namespace container_internal {
25 namespace {
26
27 enum AllocSpec {
28 kPropagateOnCopy = 1,
29 kPropagateOnMove = 2,
30 kPropagateOnSwap = 4,
31 };
32
33 struct AllocState {
34 size_t num_allocs = 0;
35 std::set<void*> owned;
36 };
37
38 template <class T,
39 int Spec = kPropagateOnCopy | kPropagateOnMove | kPropagateOnSwap>
40 class CheckedAlloc {
41 public:
42 template <class, int>
43 friend class CheckedAlloc;
44
45 using value_type = T;
46
CheckedAlloc()47 CheckedAlloc() {}
CheckedAlloc(size_t id)48 explicit CheckedAlloc(size_t id) : id_(id) {}
49 CheckedAlloc(const CheckedAlloc&) = default;
50 CheckedAlloc& operator=(const CheckedAlloc&) = default;
51
52 template <class U>
CheckedAlloc(const CheckedAlloc<U,Spec> & that)53 CheckedAlloc(const CheckedAlloc<U, Spec>& that)
54 : id_(that.id_), state_(that.state_) {}
55
56 template <class U>
57 struct rebind {
58 using other = CheckedAlloc<U, Spec>;
59 };
60
61 using propagate_on_container_copy_assignment =
62 std::integral_constant<bool, (Spec & kPropagateOnCopy) != 0>;
63
64 using propagate_on_container_move_assignment =
65 std::integral_constant<bool, (Spec & kPropagateOnMove) != 0>;
66
67 using propagate_on_container_swap =
68 std::integral_constant<bool, (Spec & kPropagateOnSwap) != 0>;
69
select_on_container_copy_construction() const70 CheckedAlloc select_on_container_copy_construction() const {
71 if (Spec & kPropagateOnCopy) return *this;
72 return {};
73 }
74
allocate(size_t n)75 T* allocate(size_t n) {
76 T* ptr = std::allocator<T>().allocate(n);
77 track_alloc(ptr);
78 return ptr;
79 }
deallocate(T * ptr,size_t n)80 void deallocate(T* ptr, size_t n) {
81 memset(ptr, 0, n * sizeof(T)); // The freed memory must be unpoisoned.
82 track_dealloc(ptr);
83 return std::allocator<T>().deallocate(ptr, n);
84 }
85
operator ==(const CheckedAlloc & a,const CheckedAlloc & b)86 friend bool operator==(const CheckedAlloc& a, const CheckedAlloc& b) {
87 return a.id_ == b.id_;
88 }
operator !=(const CheckedAlloc & a,const CheckedAlloc & b)89 friend bool operator!=(const CheckedAlloc& a, const CheckedAlloc& b) {
90 return !(a == b);
91 }
92
num_allocs() const93 size_t num_allocs() const { return state_->num_allocs; }
94
swap(CheckedAlloc & that)95 void swap(CheckedAlloc& that) {
96 using std::swap;
97 swap(id_, that.id_);
98 swap(state_, that.state_);
99 }
100
swap(CheckedAlloc & a,CheckedAlloc & b)101 friend void swap(CheckedAlloc& a, CheckedAlloc& b) { a.swap(b); }
102
operator <<(std::ostream & o,const CheckedAlloc & a)103 friend std::ostream& operator<<(std::ostream& o, const CheckedAlloc& a) {
104 return o << "alloc(" << a.id_ << ")";
105 }
106
107 private:
track_alloc(void * ptr)108 void track_alloc(void* ptr) {
109 AllocState* state = state_.get();
110 ++state->num_allocs;
111 if (!state->owned.insert(ptr).second)
112 ADD_FAILURE() << *this << " got previously allocated memory: " << ptr;
113 }
track_dealloc(void * ptr)114 void track_dealloc(void* ptr) {
115 if (state_->owned.erase(ptr) != 1)
116 ADD_FAILURE() << *this
117 << " deleting memory owned by another allocator: " << ptr;
118 }
119
120 size_t id_ = std::numeric_limits<size_t>::max();
121
122 std::shared_ptr<AllocState> state_ = std::make_shared<AllocState>();
123 };
124
125 struct Identity {
operator ()absl::container_internal::__anon212bd4150111::Identity126 int32_t operator()(int32_t v) const { return v; }
127 };
128
129 struct Policy {
130 using slot_type = Tracked<int32_t>;
131 using init_type = Tracked<int32_t>;
132 using key_type = int32_t;
133
134 template <class allocator_type, class... Args>
constructabsl::container_internal::__anon212bd4150111::Policy135 static void construct(allocator_type* alloc, slot_type* slot,
136 Args&&... args) {
137 std::allocator_traits<allocator_type>::construct(
138 *alloc, slot, std::forward<Args>(args)...);
139 }
140
141 template <class allocator_type>
destroyabsl::container_internal::__anon212bd4150111::Policy142 static void destroy(allocator_type* alloc, slot_type* slot) {
143 std::allocator_traits<allocator_type>::destroy(*alloc, slot);
144 }
145
146 template <class allocator_type>
transferabsl::container_internal::__anon212bd4150111::Policy147 static void transfer(allocator_type* alloc, slot_type* new_slot,
148 slot_type* old_slot) {
149 construct(alloc, new_slot, std::move(*old_slot));
150 destroy(alloc, old_slot);
151 }
152
153 template <class F>
applyabsl::container_internal::__anon212bd4150111::Policy154 static auto apply(F&& f, int32_t v) -> decltype(std::forward<F>(f)(v, v)) {
155 return std::forward<F>(f)(v, v);
156 }
157
158 template <class F>
applyabsl::container_internal::__anon212bd4150111::Policy159 static auto apply(F&& f, const slot_type& v)
160 -> decltype(std::forward<F>(f)(v.val(), v)) {
161 return std::forward<F>(f)(v.val(), v);
162 }
163
164 template <class F>
applyabsl::container_internal::__anon212bd4150111::Policy165 static auto apply(F&& f, slot_type&& v)
166 -> decltype(std::forward<F>(f)(v.val(), std::move(v))) {
167 return std::forward<F>(f)(v.val(), std::move(v));
168 }
169
elementabsl::container_internal::__anon212bd4150111::Policy170 static slot_type& element(slot_type* slot) { return *slot; }
171 };
172
173 template <int Spec>
174 struct PropagateTest : public ::testing::Test {
175 using Alloc = CheckedAlloc<Tracked<int32_t>, Spec>;
176
177 using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, Alloc>;
178
PropagateTestabsl::container_internal::__anon212bd4150111::PropagateTest179 PropagateTest() {
180 EXPECT_EQ(a1, t1.get_allocator());
181 EXPECT_NE(a2, t1.get_allocator());
182 }
183
184 Alloc a1 = Alloc(1);
185 Table t1 = Table(0, a1);
186 Alloc a2 = Alloc(2);
187 };
188
189 using PropagateOnAll =
190 PropagateTest<kPropagateOnCopy | kPropagateOnMove | kPropagateOnSwap>;
191 using NoPropagateOnCopy = PropagateTest<kPropagateOnMove | kPropagateOnSwap>;
192 using NoPropagateOnMove = PropagateTest<kPropagateOnCopy | kPropagateOnSwap>;
193
TEST_F(PropagateOnAll,Empty)194 TEST_F(PropagateOnAll, Empty) { EXPECT_EQ(0, a1.num_allocs()); }
195
TEST_F(PropagateOnAll,InsertAllocates)196 TEST_F(PropagateOnAll, InsertAllocates) {
197 auto it = t1.insert(0).first;
198 EXPECT_EQ(1, a1.num_allocs());
199 EXPECT_EQ(0, it->num_moves());
200 EXPECT_EQ(0, it->num_copies());
201 }
202
TEST_F(PropagateOnAll,InsertDecomposes)203 TEST_F(PropagateOnAll, InsertDecomposes) {
204 auto it = t1.insert(0).first;
205 EXPECT_EQ(1, a1.num_allocs());
206 EXPECT_EQ(0, it->num_moves());
207 EXPECT_EQ(0, it->num_copies());
208
209 EXPECT_FALSE(t1.insert(0).second);
210 EXPECT_EQ(1, a1.num_allocs());
211 EXPECT_EQ(0, it->num_moves());
212 EXPECT_EQ(0, it->num_copies());
213 }
214
TEST_F(PropagateOnAll,RehashMoves)215 TEST_F(PropagateOnAll, RehashMoves) {
216 auto it = t1.insert(0).first;
217 EXPECT_EQ(0, it->num_moves());
218 t1.rehash(2 * t1.capacity());
219 EXPECT_EQ(2, a1.num_allocs());
220 it = t1.find(0);
221 EXPECT_EQ(1, it->num_moves());
222 EXPECT_EQ(0, it->num_copies());
223 }
224
TEST_F(PropagateOnAll,CopyConstructor)225 TEST_F(PropagateOnAll, CopyConstructor) {
226 auto it = t1.insert(0).first;
227 Table u(t1);
228 EXPECT_EQ(2, a1.num_allocs());
229 EXPECT_EQ(0, it->num_moves());
230 EXPECT_EQ(1, it->num_copies());
231 }
232
TEST_F(NoPropagateOnCopy,CopyConstructor)233 TEST_F(NoPropagateOnCopy, CopyConstructor) {
234 auto it = t1.insert(0).first;
235 Table u(t1);
236 EXPECT_EQ(1, a1.num_allocs());
237 EXPECT_EQ(1, u.get_allocator().num_allocs());
238 EXPECT_EQ(0, it->num_moves());
239 EXPECT_EQ(1, it->num_copies());
240 }
241
TEST_F(PropagateOnAll,CopyConstructorWithSameAlloc)242 TEST_F(PropagateOnAll, CopyConstructorWithSameAlloc) {
243 auto it = t1.insert(0).first;
244 Table u(t1, a1);
245 EXPECT_EQ(2, a1.num_allocs());
246 EXPECT_EQ(0, it->num_moves());
247 EXPECT_EQ(1, it->num_copies());
248 }
249
TEST_F(NoPropagateOnCopy,CopyConstructorWithSameAlloc)250 TEST_F(NoPropagateOnCopy, CopyConstructorWithSameAlloc) {
251 auto it = t1.insert(0).first;
252 Table u(t1, a1);
253 EXPECT_EQ(2, a1.num_allocs());
254 EXPECT_EQ(0, it->num_moves());
255 EXPECT_EQ(1, it->num_copies());
256 }
257
TEST_F(PropagateOnAll,CopyConstructorWithDifferentAlloc)258 TEST_F(PropagateOnAll, CopyConstructorWithDifferentAlloc) {
259 auto it = t1.insert(0).first;
260 Table u(t1, a2);
261 EXPECT_EQ(a2, u.get_allocator());
262 EXPECT_EQ(1, a1.num_allocs());
263 EXPECT_EQ(1, a2.num_allocs());
264 EXPECT_EQ(0, it->num_moves());
265 EXPECT_EQ(1, it->num_copies());
266 }
267
TEST_F(NoPropagateOnCopy,CopyConstructorWithDifferentAlloc)268 TEST_F(NoPropagateOnCopy, CopyConstructorWithDifferentAlloc) {
269 auto it = t1.insert(0).first;
270 Table u(t1, a2);
271 EXPECT_EQ(a2, u.get_allocator());
272 EXPECT_EQ(1, a1.num_allocs());
273 EXPECT_EQ(1, a2.num_allocs());
274 EXPECT_EQ(0, it->num_moves());
275 EXPECT_EQ(1, it->num_copies());
276 }
277
TEST_F(PropagateOnAll,MoveConstructor)278 TEST_F(PropagateOnAll, MoveConstructor) {
279 auto it = t1.insert(0).first;
280 Table u(std::move(t1));
281 EXPECT_EQ(1, a1.num_allocs());
282 EXPECT_EQ(0, it->num_moves());
283 EXPECT_EQ(0, it->num_copies());
284 }
285
TEST_F(NoPropagateOnMove,MoveConstructor)286 TEST_F(NoPropagateOnMove, MoveConstructor) {
287 auto it = t1.insert(0).first;
288 Table u(std::move(t1));
289 EXPECT_EQ(1, a1.num_allocs());
290 EXPECT_EQ(0, it->num_moves());
291 EXPECT_EQ(0, it->num_copies());
292 }
293
TEST_F(PropagateOnAll,MoveConstructorWithSameAlloc)294 TEST_F(PropagateOnAll, MoveConstructorWithSameAlloc) {
295 auto it = t1.insert(0).first;
296 Table u(std::move(t1), a1);
297 EXPECT_EQ(1, a1.num_allocs());
298 EXPECT_EQ(0, it->num_moves());
299 EXPECT_EQ(0, it->num_copies());
300 }
301
TEST_F(NoPropagateOnMove,MoveConstructorWithSameAlloc)302 TEST_F(NoPropagateOnMove, MoveConstructorWithSameAlloc) {
303 auto it = t1.insert(0).first;
304 Table u(std::move(t1), a1);
305 EXPECT_EQ(1, a1.num_allocs());
306 EXPECT_EQ(0, it->num_moves());
307 EXPECT_EQ(0, it->num_copies());
308 }
309
TEST_F(PropagateOnAll,MoveConstructorWithDifferentAlloc)310 TEST_F(PropagateOnAll, MoveConstructorWithDifferentAlloc) {
311 auto it = t1.insert(0).first;
312 Table u(std::move(t1), a2);
313 it = u.find(0);
314 EXPECT_EQ(a2, u.get_allocator());
315 EXPECT_EQ(1, a1.num_allocs());
316 EXPECT_EQ(1, a2.num_allocs());
317 EXPECT_EQ(1, it->num_moves());
318 EXPECT_EQ(0, it->num_copies());
319 }
320
TEST_F(NoPropagateOnMove,MoveConstructorWithDifferentAlloc)321 TEST_F(NoPropagateOnMove, MoveConstructorWithDifferentAlloc) {
322 auto it = t1.insert(0).first;
323 Table u(std::move(t1), a2);
324 it = u.find(0);
325 EXPECT_EQ(a2, u.get_allocator());
326 EXPECT_EQ(1, a1.num_allocs());
327 EXPECT_EQ(1, a2.num_allocs());
328 EXPECT_EQ(1, it->num_moves());
329 EXPECT_EQ(0, it->num_copies());
330 }
331
TEST_F(PropagateOnAll,CopyAssignmentWithSameAlloc)332 TEST_F(PropagateOnAll, CopyAssignmentWithSameAlloc) {
333 auto it = t1.insert(0).first;
334 Table u(0, a1);
335 u = t1;
336 EXPECT_EQ(2, a1.num_allocs());
337 EXPECT_EQ(0, it->num_moves());
338 EXPECT_EQ(1, it->num_copies());
339 }
340
TEST_F(NoPropagateOnCopy,CopyAssignmentWithSameAlloc)341 TEST_F(NoPropagateOnCopy, CopyAssignmentWithSameAlloc) {
342 auto it = t1.insert(0).first;
343 Table u(0, a1);
344 u = t1;
345 EXPECT_EQ(2, a1.num_allocs());
346 EXPECT_EQ(0, it->num_moves());
347 EXPECT_EQ(1, it->num_copies());
348 }
349
TEST_F(PropagateOnAll,CopyAssignmentWithDifferentAlloc)350 TEST_F(PropagateOnAll, CopyAssignmentWithDifferentAlloc) {
351 auto it = t1.insert(0).first;
352 Table u(0, a2);
353 u = t1;
354 EXPECT_EQ(a1, u.get_allocator());
355 EXPECT_EQ(2, a1.num_allocs());
356 EXPECT_EQ(0, a2.num_allocs());
357 EXPECT_EQ(0, it->num_moves());
358 EXPECT_EQ(1, it->num_copies());
359 }
360
TEST_F(NoPropagateOnCopy,CopyAssignmentWithDifferentAlloc)361 TEST_F(NoPropagateOnCopy, CopyAssignmentWithDifferentAlloc) {
362 auto it = t1.insert(0).first;
363 Table u(0, a2);
364 u = t1;
365 EXPECT_EQ(a2, u.get_allocator());
366 EXPECT_EQ(1, a1.num_allocs());
367 EXPECT_EQ(1, a2.num_allocs());
368 EXPECT_EQ(0, it->num_moves());
369 EXPECT_EQ(1, it->num_copies());
370 }
371
TEST_F(PropagateOnAll,MoveAssignmentWithSameAlloc)372 TEST_F(PropagateOnAll, MoveAssignmentWithSameAlloc) {
373 auto it = t1.insert(0).first;
374 Table u(0, a1);
375 u = std::move(t1);
376 EXPECT_EQ(a1, u.get_allocator());
377 EXPECT_EQ(1, a1.num_allocs());
378 EXPECT_EQ(0, it->num_moves());
379 EXPECT_EQ(0, it->num_copies());
380 }
381
TEST_F(NoPropagateOnMove,MoveAssignmentWithSameAlloc)382 TEST_F(NoPropagateOnMove, MoveAssignmentWithSameAlloc) {
383 auto it = t1.insert(0).first;
384 Table u(0, a1);
385 u = std::move(t1);
386 EXPECT_EQ(a1, u.get_allocator());
387 EXPECT_EQ(1, a1.num_allocs());
388 EXPECT_EQ(0, it->num_moves());
389 EXPECT_EQ(0, it->num_copies());
390 }
391
TEST_F(PropagateOnAll,MoveAssignmentWithDifferentAlloc)392 TEST_F(PropagateOnAll, MoveAssignmentWithDifferentAlloc) {
393 auto it = t1.insert(0).first;
394 Table u(0, a2);
395 u = std::move(t1);
396 EXPECT_EQ(a1, u.get_allocator());
397 EXPECT_EQ(1, a1.num_allocs());
398 EXPECT_EQ(0, a2.num_allocs());
399 EXPECT_EQ(0, it->num_moves());
400 EXPECT_EQ(0, it->num_copies());
401 }
402
TEST_F(NoPropagateOnMove,MoveAssignmentWithDifferentAlloc)403 TEST_F(NoPropagateOnMove, MoveAssignmentWithDifferentAlloc) {
404 auto it = t1.insert(0).first;
405 Table u(0, a2);
406 u = std::move(t1);
407 it = u.find(0);
408 EXPECT_EQ(a2, u.get_allocator());
409 EXPECT_EQ(1, a1.num_allocs());
410 EXPECT_EQ(1, a2.num_allocs());
411 EXPECT_EQ(1, it->num_moves());
412 EXPECT_EQ(0, it->num_copies());
413 }
414
TEST_F(PropagateOnAll,Swap)415 TEST_F(PropagateOnAll, Swap) {
416 auto it = t1.insert(0).first;
417 Table u(0, a2);
418 u.swap(t1);
419 EXPECT_EQ(a1, u.get_allocator());
420 EXPECT_EQ(a2, t1.get_allocator());
421 EXPECT_EQ(1, a1.num_allocs());
422 EXPECT_EQ(0, a2.num_allocs());
423 EXPECT_EQ(0, it->num_moves());
424 EXPECT_EQ(0, it->num_copies());
425 }
426
427 // This allocator is similar to std::pmr::polymorphic_allocator.
428 // Note the disabled assignment.
429 template <class T>
430 class PAlloc {
431 template <class>
432 friend class PAlloc;
433
434 public:
435 // types
436 using value_type = T;
437
438 // traits
439 using propagate_on_container_swap = std::false_type;
440
441 PAlloc() noexcept = default;
PAlloc(size_t id)442 explicit PAlloc(size_t id) noexcept : id_(id) {}
443 PAlloc(const PAlloc&) noexcept = default;
444 PAlloc& operator=(const PAlloc&) noexcept = delete;
445
446 template <class U>
PAlloc(const PAlloc<U> & that)447 PAlloc(const PAlloc<U>& that) noexcept : id_(that.id_) {} // NOLINT
448
449 template <class U>
450 struct rebind {
451 using other = PAlloc<U>;
452 };
453
select_on_container_copy_construction() const454 constexpr PAlloc select_on_container_copy_construction() const { return {}; }
455
456 // public member functions
allocate(size_t)457 T* allocate(size_t) { return new T; }
deallocate(T * p,size_t)458 void deallocate(T* p, size_t) { delete p; }
459
operator ==(const PAlloc & a,const PAlloc & b)460 friend bool operator==(const PAlloc& a, const PAlloc& b) {
461 return a.id_ == b.id_;
462 }
operator !=(const PAlloc & a,const PAlloc & b)463 friend bool operator!=(const PAlloc& a, const PAlloc& b) { return !(a == b); }
464
465 private:
466 size_t id_ = std::numeric_limits<size_t>::max();
467 };
468
469 // This doesn't compile with GCC 5.4 and 5.5 due to a bug in noexcept handing.
470 #if !defined(__GNUC__) || __GNUC__ != 5 || (__GNUC_MINOR__ != 4 && \
471 __GNUC_MINOR__ != 5)
TEST(NoPropagateOn,Swap)472 TEST(NoPropagateOn, Swap) {
473 using PA = PAlloc<char>;
474 using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
475
476 Table t1(PA{1}), t2(PA{2});
477 swap(t1, t2);
478 EXPECT_EQ(t1.get_allocator(), PA(1));
479 EXPECT_EQ(t2.get_allocator(), PA(2));
480 }
481 #endif
482
TEST(NoPropagateOn,CopyConstruct)483 TEST(NoPropagateOn, CopyConstruct) {
484 using PA = PAlloc<char>;
485 using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
486
487 Table t1(PA{1}), t2(t1);
488 EXPECT_EQ(t1.get_allocator(), PA(1));
489 EXPECT_EQ(t2.get_allocator(), PA());
490 }
491
TEST(NoPropagateOn,Assignment)492 TEST(NoPropagateOn, Assignment) {
493 using PA = PAlloc<char>;
494 using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
495
496 Table t1(PA{1}), t2(PA{2});
497 t1 = t2;
498 EXPECT_EQ(t1.get_allocator(), PA(1));
499 EXPECT_EQ(t2.get_allocator(), PA(2));
500 }
501
502 } // namespace
503 } // namespace container_internal
504 ABSL_NAMESPACE_END
505 } // namespace absl
506