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 <cstddef>
16 #include <cstdint>
17 #include <functional>
18 #include <limits>
19 #include <memory>
20 #include <ostream>
21 #include <set>
22 #include <type_traits>
23 #include <utility>
24
25 #include "gmock/gmock.h"
26 #include "gtest/gtest.h"
27 #include "absl/base/config.h"
28 #include "absl/container/internal/raw_hash_set.h"
29 #include "absl/container/internal/tracked.h"
30
31 namespace absl {
32 ABSL_NAMESPACE_BEGIN
33 namespace container_internal {
34 namespace {
35 using ::testing::AnyOf;
36
37 enum AllocSpec {
38 kPropagateOnCopy = 1,
39 kPropagateOnMove = 2,
40 kPropagateOnSwap = 4,
41 };
42
43 struct AllocState {
44 size_t num_allocs = 0;
45 std::set<void*> owned;
46 };
47
48 template <class T,
49 int Spec = kPropagateOnCopy | kPropagateOnMove | kPropagateOnSwap>
50 class CheckedAlloc {
51 public:
52 template <class, int>
53 friend class CheckedAlloc;
54
55 using value_type = T;
56
CheckedAlloc()57 CheckedAlloc() {}
CheckedAlloc(size_t id)58 explicit CheckedAlloc(size_t id) : id_(id) {}
59 CheckedAlloc(const CheckedAlloc&) = default;
60 CheckedAlloc& operator=(const CheckedAlloc&) = default;
61
62 template <class U>
CheckedAlloc(const CheckedAlloc<U,Spec> & that)63 CheckedAlloc(const CheckedAlloc<U, Spec>& that)
64 : id_(that.id_), state_(that.state_) {}
65
66 template <class U>
67 struct rebind {
68 using other = CheckedAlloc<U, Spec>;
69 };
70
71 using propagate_on_container_copy_assignment =
72 std::integral_constant<bool, (Spec & kPropagateOnCopy) != 0>;
73
74 using propagate_on_container_move_assignment =
75 std::integral_constant<bool, (Spec & kPropagateOnMove) != 0>;
76
77 using propagate_on_container_swap =
78 std::integral_constant<bool, (Spec & kPropagateOnSwap) != 0>;
79
select_on_container_copy_construction() const80 CheckedAlloc select_on_container_copy_construction() const {
81 if (Spec & kPropagateOnCopy) return *this;
82 return {};
83 }
84
allocate(size_t n)85 T* allocate(size_t n) {
86 T* ptr = std::allocator<T>().allocate(n);
87 track_alloc(ptr);
88 return ptr;
89 }
deallocate(T * ptr,size_t n)90 void deallocate(T* ptr, size_t n) {
91 memset(ptr, 0, n * sizeof(T)); // The freed memory must be unpoisoned.
92 track_dealloc(ptr);
93 return std::allocator<T>().deallocate(ptr, n);
94 }
95
operator ==(const CheckedAlloc & a,const CheckedAlloc & b)96 friend bool operator==(const CheckedAlloc& a, const CheckedAlloc& b) {
97 return a.id_ == b.id_;
98 }
operator !=(const CheckedAlloc & a,const CheckedAlloc & b)99 friend bool operator!=(const CheckedAlloc& a, const CheckedAlloc& b) {
100 return !(a == b);
101 }
102
num_allocs() const103 size_t num_allocs() const { return state_->num_allocs; }
104
swap(CheckedAlloc & that)105 void swap(CheckedAlloc& that) {
106 using std::swap;
107 swap(id_, that.id_);
108 swap(state_, that.state_);
109 }
110
swap(CheckedAlloc & a,CheckedAlloc & b)111 friend void swap(CheckedAlloc& a, CheckedAlloc& b) { a.swap(b); }
112
operator <<(std::ostream & o,const CheckedAlloc & a)113 friend std::ostream& operator<<(std::ostream& o, const CheckedAlloc& a) {
114 return o << "alloc(" << a.id_ << ")";
115 }
116
117 private:
track_alloc(void * ptr)118 void track_alloc(void* ptr) {
119 AllocState* state = state_.get();
120 ++state->num_allocs;
121 if (!state->owned.insert(ptr).second)
122 ADD_FAILURE() << *this << " got previously allocated memory: " << ptr;
123 }
track_dealloc(void * ptr)124 void track_dealloc(void* ptr) {
125 if (state_->owned.erase(ptr) != 1)
126 ADD_FAILURE() << *this
127 << " deleting memory owned by another allocator: " << ptr;
128 }
129
130 size_t id_ = std::numeric_limits<size_t>::max();
131
132 std::shared_ptr<AllocState> state_ = std::make_shared<AllocState>();
133 };
134
135 struct Identity {
operator ()absl::container_internal::__anon7bf2266e0111::Identity136 int32_t operator()(int32_t v) const { return v; }
137 };
138
139 struct Policy {
140 using slot_type = Tracked<int32_t>;
141 using init_type = Tracked<int32_t>;
142 using key_type = int32_t;
143
144 template <class allocator_type, class... Args>
constructabsl::container_internal::__anon7bf2266e0111::Policy145 static void construct(allocator_type* alloc, slot_type* slot,
146 Args&&... args) {
147 std::allocator_traits<allocator_type>::construct(
148 *alloc, slot, std::forward<Args>(args)...);
149 }
150
151 template <class allocator_type>
destroyabsl::container_internal::__anon7bf2266e0111::Policy152 static void destroy(allocator_type* alloc, slot_type* slot) {
153 std::allocator_traits<allocator_type>::destroy(*alloc, slot);
154 }
155
156 template <class allocator_type>
transferabsl::container_internal::__anon7bf2266e0111::Policy157 static void transfer(allocator_type* alloc, slot_type* new_slot,
158 slot_type* old_slot) {
159 construct(alloc, new_slot, std::move(*old_slot));
160 destroy(alloc, old_slot);
161 }
162
163 template <class F>
applyabsl::container_internal::__anon7bf2266e0111::Policy164 static auto apply(F&& f, int32_t v) -> decltype(std::forward<F>(f)(v, v)) {
165 return std::forward<F>(f)(v, v);
166 }
167
168 template <class F>
applyabsl::container_internal::__anon7bf2266e0111::Policy169 static auto apply(F&& f, const slot_type& v)
170 -> decltype(std::forward<F>(f)(v.val(), v)) {
171 return std::forward<F>(f)(v.val(), v);
172 }
173
174 template <class F>
applyabsl::container_internal::__anon7bf2266e0111::Policy175 static auto apply(F&& f, slot_type&& v)
176 -> decltype(std::forward<F>(f)(v.val(), std::move(v))) {
177 return std::forward<F>(f)(v.val(), std::move(v));
178 }
179
elementabsl::container_internal::__anon7bf2266e0111::Policy180 static slot_type& element(slot_type* slot) { return *slot; }
181 };
182
183 template <int Spec>
184 struct PropagateTest : public ::testing::Test {
185 using Alloc = CheckedAlloc<Tracked<int32_t>, Spec>;
186
187 using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, Alloc>;
188
PropagateTestabsl::container_internal::__anon7bf2266e0111::PropagateTest189 PropagateTest() {
190 EXPECT_EQ(a1, t1.get_allocator());
191 EXPECT_NE(a2, t1.get_allocator());
192 }
193
194 Alloc a1 = Alloc(1);
195 Table t1 = Table(0, a1);
196 Alloc a2 = Alloc(2);
197 };
198
199 using PropagateOnAll =
200 PropagateTest<kPropagateOnCopy | kPropagateOnMove | kPropagateOnSwap>;
201 using NoPropagateOnCopy = PropagateTest<kPropagateOnMove | kPropagateOnSwap>;
202 using NoPropagateOnMove = PropagateTest<kPropagateOnCopy | kPropagateOnSwap>;
203
TEST_F(PropagateOnAll,Empty)204 TEST_F(PropagateOnAll, Empty) { EXPECT_EQ(0, a1.num_allocs()); }
205
TEST_F(PropagateOnAll,InsertAllocates)206 TEST_F(PropagateOnAll, InsertAllocates) {
207 auto it = t1.insert(0).first;
208 EXPECT_EQ(1, a1.num_allocs());
209 EXPECT_EQ(0, it->num_moves());
210 EXPECT_EQ(0, it->num_copies());
211 }
212
TEST_F(PropagateOnAll,InsertDecomposes)213 TEST_F(PropagateOnAll, InsertDecomposes) {
214 auto it = t1.insert(0).first;
215 EXPECT_EQ(1, a1.num_allocs());
216 EXPECT_EQ(0, it->num_moves());
217 EXPECT_EQ(0, it->num_copies());
218
219 EXPECT_FALSE(t1.insert(0).second);
220 EXPECT_EQ(1, a1.num_allocs());
221 EXPECT_EQ(0, it->num_moves());
222 EXPECT_EQ(0, it->num_copies());
223 }
224
TEST_F(PropagateOnAll,RehashMoves)225 TEST_F(PropagateOnAll, RehashMoves) {
226 auto it = t1.insert(0).first;
227 EXPECT_EQ(0, it->num_moves());
228 t1.rehash(2 * t1.capacity());
229 EXPECT_EQ(2, a1.num_allocs());
230 it = t1.find(0);
231 EXPECT_EQ(1, it->num_moves());
232 EXPECT_EQ(0, it->num_copies());
233 }
234
TEST_F(PropagateOnAll,CopyConstructor)235 TEST_F(PropagateOnAll, CopyConstructor) {
236 auto it = t1.insert(0).first;
237 Table u(t1);
238 EXPECT_EQ(2, a1.num_allocs());
239 EXPECT_EQ(0, it->num_moves());
240 EXPECT_EQ(1, it->num_copies());
241 }
242
TEST_F(NoPropagateOnCopy,CopyConstructor)243 TEST_F(NoPropagateOnCopy, CopyConstructor) {
244 auto it = t1.insert(0).first;
245 Table u(t1);
246 EXPECT_EQ(1, a1.num_allocs());
247 EXPECT_EQ(1, u.get_allocator().num_allocs());
248 EXPECT_EQ(0, it->num_moves());
249 EXPECT_EQ(1, it->num_copies());
250 }
251
TEST_F(PropagateOnAll,CopyConstructorWithSameAlloc)252 TEST_F(PropagateOnAll, CopyConstructorWithSameAlloc) {
253 auto it = t1.insert(0).first;
254 Table u(t1, a1);
255 EXPECT_EQ(2, a1.num_allocs());
256 EXPECT_EQ(0, it->num_moves());
257 EXPECT_EQ(1, it->num_copies());
258 }
259
TEST_F(NoPropagateOnCopy,CopyConstructorWithSameAlloc)260 TEST_F(NoPropagateOnCopy, CopyConstructorWithSameAlloc) {
261 auto it = t1.insert(0).first;
262 Table u(t1, a1);
263 EXPECT_EQ(2, a1.num_allocs());
264 EXPECT_EQ(0, it->num_moves());
265 EXPECT_EQ(1, it->num_copies());
266 }
267
TEST_F(PropagateOnAll,CopyConstructorWithDifferentAlloc)268 TEST_F(PropagateOnAll, 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(NoPropagateOnCopy,CopyConstructorWithDifferentAlloc)278 TEST_F(NoPropagateOnCopy, CopyConstructorWithDifferentAlloc) {
279 auto it = t1.insert(0).first;
280 Table u(t1, a2);
281 EXPECT_EQ(a2, u.get_allocator());
282 EXPECT_EQ(1, a1.num_allocs());
283 EXPECT_EQ(1, a2.num_allocs());
284 EXPECT_EQ(0, it->num_moves());
285 EXPECT_EQ(1, it->num_copies());
286 }
287
TEST_F(PropagateOnAll,MoveConstructor)288 TEST_F(PropagateOnAll, MoveConstructor) {
289 t1.insert(0);
290 Table u(std::move(t1));
291 auto it = u.begin();
292 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
293 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
294 EXPECT_EQ(0, it->num_copies());
295 }
296
TEST_F(NoPropagateOnMove,MoveConstructor)297 TEST_F(NoPropagateOnMove, MoveConstructor) {
298 t1.insert(0);
299 Table u(std::move(t1));
300 auto it = u.begin();
301 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
302 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
303 EXPECT_EQ(0, it->num_copies());
304 }
305
TEST_F(PropagateOnAll,MoveConstructorWithSameAlloc)306 TEST_F(PropagateOnAll, MoveConstructorWithSameAlloc) {
307 t1.insert(0);
308 Table u(std::move(t1), a1);
309 auto it = u.begin();
310 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
311 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
312 EXPECT_EQ(0, it->num_copies());
313 }
314
TEST_F(NoPropagateOnMove,MoveConstructorWithSameAlloc)315 TEST_F(NoPropagateOnMove, MoveConstructorWithSameAlloc) {
316 t1.insert(0);
317 Table u(std::move(t1), a1);
318 auto it = u.begin();
319 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
320 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
321 EXPECT_EQ(0, it->num_copies());
322 }
323
TEST_F(PropagateOnAll,MoveConstructorWithDifferentAlloc)324 TEST_F(PropagateOnAll, MoveConstructorWithDifferentAlloc) {
325 auto it = t1.insert(0).first;
326 Table u(std::move(t1), a2);
327 it = u.find(0);
328 EXPECT_EQ(a2, u.get_allocator());
329 EXPECT_EQ(1, a1.num_allocs());
330 EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
331 EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
332 EXPECT_EQ(0, it->num_copies());
333 }
334
TEST_F(NoPropagateOnMove,MoveConstructorWithDifferentAlloc)335 TEST_F(NoPropagateOnMove, MoveConstructorWithDifferentAlloc) {
336 auto it = t1.insert(0).first;
337 Table u(std::move(t1), a2);
338 it = u.find(0);
339 EXPECT_EQ(a2, u.get_allocator());
340 EXPECT_EQ(1, a1.num_allocs());
341 EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
342 EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
343 EXPECT_EQ(0, it->num_copies());
344 }
345
TEST_F(PropagateOnAll,CopyAssignmentWithSameAlloc)346 TEST_F(PropagateOnAll, CopyAssignmentWithSameAlloc) {
347 auto it = t1.insert(0).first;
348 Table u(0, a1);
349 u = t1;
350 EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
351 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
352 EXPECT_EQ(1, it->num_copies());
353 }
354
TEST_F(NoPropagateOnCopy,CopyAssignmentWithSameAlloc)355 TEST_F(NoPropagateOnCopy, CopyAssignmentWithSameAlloc) {
356 auto it = t1.insert(0).first;
357 Table u(0, a1);
358 u = t1;
359 EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
360 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
361 EXPECT_EQ(1, it->num_copies());
362 }
363
TEST_F(PropagateOnAll,CopyAssignmentWithDifferentAlloc)364 TEST_F(PropagateOnAll, CopyAssignmentWithDifferentAlloc) {
365 auto it = t1.insert(0).first;
366 Table u(0, a2);
367 u = t1;
368 EXPECT_EQ(a1, u.get_allocator());
369 EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
370 EXPECT_EQ(0, a2.num_allocs());
371 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
372 EXPECT_EQ(1, it->num_copies());
373 }
374
TEST_F(NoPropagateOnCopy,CopyAssignmentWithDifferentAlloc)375 TEST_F(NoPropagateOnCopy, CopyAssignmentWithDifferentAlloc) {
376 auto it = t1.insert(0).first;
377 Table u(0, a2);
378 u = t1;
379 EXPECT_EQ(a2, u.get_allocator());
380 EXPECT_EQ(1, a1.num_allocs());
381 EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
382 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
383 EXPECT_EQ(1, it->num_copies());
384 }
385
TEST_F(PropagateOnAll,MoveAssignmentWithSameAlloc)386 TEST_F(PropagateOnAll, MoveAssignmentWithSameAlloc) {
387 t1.insert(0);
388 Table u(0, a1);
389 u = std::move(t1);
390 auto it = u.begin();
391 EXPECT_EQ(a1, u.get_allocator());
392 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
393 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
394 EXPECT_EQ(0, it->num_copies());
395 }
396
TEST_F(NoPropagateOnMove,MoveAssignmentWithSameAlloc)397 TEST_F(NoPropagateOnMove, MoveAssignmentWithSameAlloc) {
398 t1.insert(0);
399 Table u(0, a1);
400 u = std::move(t1);
401 auto it = u.begin();
402 EXPECT_EQ(a1, u.get_allocator());
403 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
404 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
405 EXPECT_EQ(0, it->num_copies());
406 }
407
TEST_F(PropagateOnAll,MoveAssignmentWithDifferentAlloc)408 TEST_F(PropagateOnAll, MoveAssignmentWithDifferentAlloc) {
409 t1.insert(0);
410 Table u(0, a2);
411 u = std::move(t1);
412 auto it = u.begin();
413 EXPECT_EQ(a1, u.get_allocator());
414 EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
415 EXPECT_EQ(0, a2.num_allocs());
416 EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
417 EXPECT_EQ(0, it->num_copies());
418 }
419
TEST_F(NoPropagateOnMove,MoveAssignmentWithDifferentAlloc)420 TEST_F(NoPropagateOnMove, MoveAssignmentWithDifferentAlloc) {
421 t1.insert(0);
422 Table u(0, a2);
423 u = std::move(t1);
424 auto it = u.find(0);
425 EXPECT_EQ(a2, u.get_allocator());
426 EXPECT_EQ(1, a1.num_allocs());
427 EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
428 EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
429 EXPECT_EQ(0, it->num_copies());
430 }
431
TEST_F(PropagateOnAll,Swap)432 TEST_F(PropagateOnAll, Swap) {
433 auto it = t1.insert(0).first;
434 Table u(0, a2);
435 u.swap(t1);
436 EXPECT_EQ(a1, u.get_allocator());
437 EXPECT_EQ(a2, t1.get_allocator());
438 EXPECT_EQ(1, a1.num_allocs());
439 EXPECT_EQ(0, a2.num_allocs());
440 EXPECT_EQ(0, it->num_moves());
441 EXPECT_EQ(0, it->num_copies());
442 }
443
444 // This allocator is similar to std::pmr::polymorphic_allocator.
445 // Note the disabled assignment.
446 template <class T>
447 class PAlloc {
448 template <class>
449 friend class PAlloc;
450
451 public:
452 // types
453 using value_type = T;
454
455 PAlloc() noexcept = default;
PAlloc(size_t id)456 explicit PAlloc(size_t id) noexcept : id_(id) {}
457 PAlloc(const PAlloc&) noexcept = default;
458 PAlloc& operator=(const PAlloc&) noexcept = delete;
459
460 template <class U>
PAlloc(const PAlloc<U> & that)461 PAlloc(const PAlloc<U>& that) noexcept : id_(that.id_) {} // NOLINT
462
463 template <class U>
464 struct rebind {
465 using other = PAlloc<U>;
466 };
467
select_on_container_copy_construction() const468 constexpr PAlloc select_on_container_copy_construction() const { return {}; }
469
470 // public member functions
allocate(size_t)471 T* allocate(size_t) { return new T; }
deallocate(T * p,size_t)472 void deallocate(T* p, size_t) { delete p; }
473
operator ==(const PAlloc & a,const PAlloc & b)474 friend bool operator==(const PAlloc& a, const PAlloc& b) {
475 return a.id_ == b.id_;
476 }
operator !=(const PAlloc & a,const PAlloc & b)477 friend bool operator!=(const PAlloc& a, const PAlloc& b) { return !(a == b); }
478
479 private:
480 size_t id_ = std::numeric_limits<size_t>::max();
481 };
482
TEST(NoPropagateDeletedAssignment,CopyConstruct)483 TEST(NoPropagateDeletedAssignment, 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(NoPropagateDeletedAssignment,CopyAssignment)492 TEST(NoPropagateDeletedAssignment, CopyAssignment) {
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
TEST(NoPropagateDeletedAssignment,MoveAssignment)502 TEST(NoPropagateDeletedAssignment, MoveAssignment) {
503 using PA = PAlloc<char>;
504 using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
505
506 Table t1(PA{1}), t2(PA{2});
507 t1 = std::move(t2);
508 EXPECT_EQ(t1.get_allocator(), PA(1));
509 }
510
511 } // namespace
512 } // namespace container_internal
513 ABSL_NAMESPACE_END
514 } // namespace absl
515