1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #pragma once
18
19 #include <cstddef>
20 #include <limits>
21 #include <memory>
22 #include <ostream>
23
24 #include <folly/Function.h>
25 #include <folly/hash/Hash.h>
26 #include <folly/lang/SafeAssert.h>
27 #include <folly/portability/Asm.h>
28
29 namespace folly {
30 namespace test {
31
32 struct MoveOnlyTestInt {
33 int x;
34 bool destroyed{false};
35
MoveOnlyTestIntMoveOnlyTestInt36 MoveOnlyTestInt() noexcept : x(0) {}
MoveOnlyTestIntMoveOnlyTestInt37 /* implicit */ MoveOnlyTestInt(int x0) : x(x0) {}
MoveOnlyTestIntMoveOnlyTestInt38 MoveOnlyTestInt(MoveOnlyTestInt&& rhs) noexcept : x(rhs.x) {}
39 MoveOnlyTestInt(MoveOnlyTestInt const&) = delete;
40 MoveOnlyTestInt& operator=(MoveOnlyTestInt&& rhs) noexcept {
41 FOLLY_SAFE_CHECK(!rhs.destroyed, "");
42 x = rhs.x;
43 return *this;
44 }
45 MoveOnlyTestInt& operator=(MoveOnlyTestInt const&) = delete;
46
~MoveOnlyTestIntMoveOnlyTestInt47 ~MoveOnlyTestInt() {
48 FOLLY_SAFE_CHECK(!destroyed, "");
49 destroyed = true;
50 asm_volatile_memory(); // try to keep compiler from eliding the store
51 }
52
53 bool operator==(MoveOnlyTestInt const& rhs) const {
54 FOLLY_SAFE_CHECK(!destroyed, "");
55 FOLLY_SAFE_CHECK(!rhs.destroyed, "");
56 return x == rhs.x && destroyed == rhs.destroyed;
57 }
58 bool operator!=(MoveOnlyTestInt const& rhs) const { return !(*this == rhs); }
59 };
60
61 struct ThrowOnCopyTestInt {
62 int x{0};
63
ThrowOnCopyTestIntThrowOnCopyTestInt64 ThrowOnCopyTestInt() {}
65
ThrowOnCopyTestIntThrowOnCopyTestInt66 [[noreturn]] ThrowOnCopyTestInt(const ThrowOnCopyTestInt& other)
67 : x(other.x) {
68 throw std::exception{};
69 }
70
71 ThrowOnCopyTestInt& operator=(const ThrowOnCopyTestInt&) {
72 throw std::exception{};
73 }
74
75 bool operator==(const ThrowOnCopyTestInt& other) const {
76 return x == other.x;
77 }
78
79 bool operator!=(const ThrowOnCopyTestInt& other) const {
80 return !(x == other.x);
81 }
82 };
83
84 struct PermissiveConstructorTestInt {
85 int x;
86
PermissiveConstructorTestIntPermissiveConstructorTestInt87 PermissiveConstructorTestInt() noexcept : x(0) {}
PermissiveConstructorTestIntPermissiveConstructorTestInt88 /* implicit */ PermissiveConstructorTestInt(int x0) : x(x0) {}
89
90 template <typename T>
PermissiveConstructorTestIntPermissiveConstructorTestInt91 /* implicit */ PermissiveConstructorTestInt(T&& src)
92 : x(std::forward<T>(src)) {}
93
PermissiveConstructorTestIntPermissiveConstructorTestInt94 PermissiveConstructorTestInt(PermissiveConstructorTestInt&& rhs) noexcept
95 : x(rhs.x) {}
96 PermissiveConstructorTestInt(PermissiveConstructorTestInt const&) = delete;
97 PermissiveConstructorTestInt& operator=(
98 PermissiveConstructorTestInt&& rhs) noexcept {
99 x = rhs.x;
100 return *this;
101 }
102 PermissiveConstructorTestInt& operator=(PermissiveConstructorTestInt const&) =
103 delete;
104
105 bool operator==(PermissiveConstructorTestInt const& rhs) const {
106 return x == rhs.x;
107 }
108 bool operator!=(PermissiveConstructorTestInt const& rhs) const {
109 return !(*this == rhs);
110 }
111 };
112
113 // Tracked is implicitly constructible across tags
114 struct Counts {
115 uint64_t copyConstruct{0};
116 uint64_t moveConstruct{0};
117 uint64_t copyConvert{0};
118 uint64_t moveConvert{0};
119 uint64_t copyAssign{0};
120 uint64_t moveAssign{0};
121 uint64_t defaultConstruct{0};
122 uint64_t destroyed{0};
123
124 explicit Counts(
125 uint64_t copConstr = 0,
126 uint64_t movConstr = 0,
127 uint64_t copConv = 0,
128 uint64_t movConv = 0,
129 uint64_t copAssign = 0,
130 uint64_t movAssign = 0,
131 uint64_t def = 0,
132 uint64_t destr = 0)
133 : copyConstruct{copConstr},
134 moveConstruct{movConstr},
135 copyConvert{copConv},
136 moveConvert{movConv},
137 copyAssign{copAssign},
138 moveAssign{movAssign},
139 defaultConstruct{def},
140 destroyed{destr} {}
141
liveCountCounts142 int64_t liveCount() const {
143 return copyConstruct + moveConstruct + copyConvert + moveConvert +
144 defaultConstruct - destroyed;
145 }
146
147 // dist ignores destroyed count
distCounts148 uint64_t dist(Counts const& rhs) const {
149 auto d = [](uint64_t x, uint64_t y) { return (x - y) * (x - y); };
150 return d(copyConstruct, rhs.copyConstruct) +
151 d(moveConstruct, rhs.moveConstruct) + d(copyConvert, rhs.copyConvert) +
152 d(moveConvert, rhs.moveConvert) + d(copyAssign, rhs.copyAssign) +
153 d(moveAssign, rhs.moveAssign) +
154 d(defaultConstruct, rhs.defaultConstruct);
155 }
156
157 bool operator==(Counts const& rhs) const {
158 return dist(rhs) == 0 && destroyed == rhs.destroyed;
159 }
160 bool operator!=(Counts const& rhs) const { return !(*this == rhs); }
161 };
162
163 inline std::ostream& operator<<(std::ostream& xo, Counts const& counts) {
164 xo << "[";
165 std::string glue = "";
166 if (counts.copyConstruct > 0) {
167 xo << glue << counts.copyConstruct << " copy";
168 glue = ", ";
169 }
170 if (counts.moveConstruct > 0) {
171 xo << glue << counts.moveConstruct << " move";
172 glue = ", ";
173 }
174 if (counts.copyConvert > 0) {
175 xo << glue << counts.copyConvert << " copy convert";
176 glue = ", ";
177 }
178 if (counts.moveConvert > 0) {
179 xo << glue << counts.moveConvert << " move convert";
180 glue = ", ";
181 }
182 if (counts.copyAssign > 0) {
183 xo << glue << counts.copyAssign << " copy assign";
184 glue = ", ";
185 }
186 if (counts.moveAssign > 0) {
187 xo << glue << counts.moveAssign << " move assign";
188 glue = ", ";
189 }
190 if (counts.defaultConstruct > 0) {
191 xo << glue << counts.defaultConstruct << " default construct";
192 glue = ", ";
193 }
194 if (counts.destroyed > 0) {
195 xo << glue << counts.destroyed << " destroyed";
196 glue = ", ";
197 }
198 xo << "]";
199 return xo;
200 }
201
sumCounts()202 inline Counts& sumCounts() {
203 static thread_local Counts value{};
204 return value;
205 }
206
207 template <int Tag>
208 struct Tracked {
209 static_assert(Tag <= 5, "Need to extend Tracked<Tag> in TestUtil.cpp");
210
countsTracked211 static Counts& counts() {
212 static thread_local Counts value{};
213 return value;
214 }
215
216 uint64_t val_;
217
TrackedTracked218 Tracked() : val_{0} {
219 sumCounts().defaultConstruct++;
220 counts().defaultConstruct++;
221 }
TrackedTracked222 /* implicit */ Tracked(uint64_t const& val) : val_{val} {
223 sumCounts().copyConvert++;
224 counts().copyConvert++;
225 }
TrackedTracked226 /* implicit */ Tracked(uint64_t&& val) : val_{val} {
227 sumCounts().moveConvert++;
228 counts().moveConvert++;
229 }
TrackedTracked230 Tracked(Tracked const& rhs) : val_{rhs.val_} {
231 sumCounts().copyConstruct++;
232 counts().copyConstruct++;
233 }
TrackedTracked234 Tracked(Tracked&& rhs) noexcept : val_{rhs.val_} {
235 sumCounts().moveConstruct++;
236 counts().moveConstruct++;
237 }
238 Tracked& operator=(Tracked const& rhs) {
239 val_ = rhs.val_;
240 sumCounts().copyAssign++;
241 counts().copyAssign++;
242 return *this;
243 }
244 Tracked& operator=(Tracked&& rhs) noexcept {
245 val_ = rhs.val_;
246 sumCounts().moveAssign++;
247 counts().moveAssign++;
248 return *this;
249 }
250
251 template <int T>
TrackedTracked252 /* implicit */ Tracked(Tracked<T> const& rhs) : val_{rhs.val_} {
253 sumCounts().copyConvert++;
254 counts().copyConvert++;
255 }
256
257 template <int T>
TrackedTracked258 /* implicit */ Tracked(Tracked<T>&& rhs) : val_{rhs.val_} {
259 sumCounts().moveConvert++;
260 counts().moveConvert++;
261 }
262
~TrackedTracked263 ~Tracked() {
264 sumCounts().destroyed++;
265 counts().destroyed++;
266 }
267
268 bool operator==(Tracked const& rhs) const { return val_ == rhs.val_; }
269 bool operator!=(Tracked const& rhs) const { return !(*this == rhs); }
270 };
271
272 template <int Tag>
273 struct TransparentTrackedHash {
274 using is_transparent = void;
275
operatorTransparentTrackedHash276 size_t operator()(Tracked<Tag> const& tracked) const {
277 return tracked.val_ ^ Tag;
278 }
operatorTransparentTrackedHash279 size_t operator()(uint64_t v) const { return v ^ Tag; }
280 };
281
282 template <int Tag>
283 struct TransparentTrackedEqual {
284 using is_transparent = void;
285
unwrapTransparentTrackedEqual286 uint64_t unwrap(Tracked<Tag> const& v) const { return v.val_; }
unwrapTransparentTrackedEqual287 uint64_t unwrap(uint64_t v) const { return v; }
288
289 template <typename A, typename B>
operatorTransparentTrackedEqual290 bool operator()(A const& lhs, B const& rhs) const {
291 return unwrap(lhs) == unwrap(rhs);
292 }
293 };
294
testAllocatedMemorySize()295 inline size_t& testAllocatedMemorySize() {
296 static thread_local size_t value{0};
297 return value;
298 }
299
testAllocatedBlockCount()300 inline size_t& testAllocatedBlockCount() {
301 static thread_local size_t value{0};
302 return value;
303 }
304
testAllocationCount()305 inline size_t& testAllocationCount() {
306 static thread_local size_t value{0};
307 return value;
308 }
309
testAllocationMaxCount()310 inline size_t& testAllocationMaxCount() {
311 static thread_local size_t value{std::numeric_limits<std::size_t>::max()};
312 return value;
313 }
314
315 inline void limitTestAllocations(std::size_t allocationsBeforeException = 0) {
316 testAllocationMaxCount() = testAllocationCount() + allocationsBeforeException;
317 }
318
unlimitTestAllocations()319 inline void unlimitTestAllocations() {
320 testAllocationMaxCount() = std::numeric_limits<std::size_t>::max();
321 }
322
resetTracking()323 inline void resetTracking() {
324 sumCounts() = Counts{};
325 Tracked<0>::counts() = Counts{};
326 Tracked<1>::counts() = Counts{};
327 Tracked<2>::counts() = Counts{};
328 Tracked<3>::counts() = Counts{};
329 Tracked<4>::counts() = Counts{};
330 Tracked<5>::counts() = Counts{};
331 testAllocatedMemorySize() = 0;
332 testAllocatedBlockCount() = 0;
333 testAllocationCount() = 0;
334 testAllocationMaxCount() = std::numeric_limits<std::size_t>::max();
335 }
336
337 template <class T>
338 class SwapTrackingAlloc {
339 public:
340 using Alloc = std::allocator<T>;
341 using AllocTraits = std::allocator_traits<Alloc>;
342 using value_type = typename AllocTraits::value_type;
343
344 using pointer = typename AllocTraits::pointer;
345 using const_pointer = typename AllocTraits::const_pointer;
346 using reference = value_type&;
347 using const_reference = value_type const&;
348 using size_type = typename AllocTraits::size_type;
349
350 using propagate_on_container_swap = std::true_type;
351 using propagate_on_container_copy_assignment = std::true_type;
352 using propagate_on_container_move_assignment = std::true_type;
353
SwapTrackingAlloc()354 SwapTrackingAlloc() {}
355
356 template <class U>
SwapTrackingAlloc(SwapTrackingAlloc<U> const & other)357 /* implicit */ SwapTrackingAlloc(SwapTrackingAlloc<U> const& other) noexcept
358 : a_(other.a_), t_(other.t_) {}
359
360 template <class U>
361 SwapTrackingAlloc& operator=(SwapTrackingAlloc<U> const& other) noexcept {
362 a_ = other.a_;
363 t_ = other.t_;
364 return *this;
365 }
366
367 template <class U>
SwapTrackingAlloc(SwapTrackingAlloc<U> && other)368 /* implicit */ SwapTrackingAlloc(SwapTrackingAlloc<U>&& other) noexcept
369 : a_(std::move(other.a_)), t_(std::move(other.t_)) {}
370
371 template <class U>
372 SwapTrackingAlloc& operator=(SwapTrackingAlloc<U>&& other) noexcept {
373 a_ = std::move(other.a_);
374 t_ = std::move(other.t_);
375 return *this;
376 }
377
allocate(size_t n)378 T* allocate(size_t n) {
379 if (testAllocationCount() >= testAllocationMaxCount()) {
380 throw std::bad_alloc();
381 }
382 ++testAllocationCount();
383 testAllocatedMemorySize() += n * sizeof(T);
384 ++testAllocatedBlockCount();
385 std::size_t extra =
386 std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
387 T* p = a_.allocate(extra + n);
388 void* raw = static_cast<void*>(p);
389 *static_cast<std::size_t*>(raw) = n;
390 return p + extra;
391 }
deallocate(T * p,size_t n)392 void deallocate(T* p, size_t n) {
393 testAllocatedMemorySize() -= n * sizeof(T);
394 --testAllocatedBlockCount();
395 std::size_t extra =
396 std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
397 std::size_t check;
398 void* raw = static_cast<void*>(p - extra);
399 check = *static_cast<std::size_t*>(raw);
400 FOLLY_SAFE_CHECK(check == n, "");
401 a_.deallocate(p - extra, n + extra);
402 }
403
404 private:
405 std::allocator<T> a_;
406 Tracked<0> t_;
407
408 template <class U>
409 friend class SwapTrackingAlloc;
410 };
411
412 template <class T>
swap(SwapTrackingAlloc<T> &,SwapTrackingAlloc<T> &)413 void swap(SwapTrackingAlloc<T>&, SwapTrackingAlloc<T>&) noexcept {
414 // For argument dependent lookup:
415 // This function will be called if the custom swap functions of a container
416 // is used. Otherwise, std::swap() will do 1 move construct and 2 move
417 // assigns which will get tracked by t_.
418 }
419
420 template <class T1, class T2>
421 bool operator==(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
422 return true;
423 }
424
425 template <class T1, class T2>
426 bool operator!=(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
427 return false;
428 }
429
430 template <class T>
431 class GenericAlloc {
432 public:
433 using value_type = T;
434
435 using pointer = T*;
436 using const_pointer = T const*;
437 using reference = T&;
438 using const_reference = T const&;
439 using size_type = std::size_t;
440
441 using propagate_on_container_swap = std::true_type;
442 using propagate_on_container_copy_assignment = std::true_type;
443 using propagate_on_container_move_assignment = std::true_type;
444
445 using AllocBytesFunc = folly::Function<void*(std::size_t)>;
446 using DeallocBytesFunc = folly::Function<void(void*, std::size_t)>;
447
448 GenericAlloc() = delete;
449
450 template <typename A, typename D>
GenericAlloc(A && alloc,D && dealloc)451 GenericAlloc(A&& alloc, D&& dealloc)
452 : alloc_{std::make_shared<AllocBytesFunc>(std::forward<A>(alloc))},
453 dealloc_{std::make_shared<DeallocBytesFunc>(std::forward<D>(dealloc))} {
454 }
455
456 template <class U>
GenericAlloc(GenericAlloc<U> const & other)457 /* implicit */ GenericAlloc(GenericAlloc<U> const& other) noexcept
458 : alloc_{other.alloc_}, dealloc_{other.dealloc_} {}
459
460 template <class U>
461 GenericAlloc& operator=(GenericAlloc<U> const& other) noexcept {
462 alloc_ = other.alloc_;
463 dealloc_ = other.dealloc_;
464 return *this;
465 }
466
467 template <class U>
GenericAlloc(GenericAlloc<U> && other)468 /* implicit */ GenericAlloc(GenericAlloc<U>&& other) noexcept
469 : alloc_(std::move(other.alloc_)), dealloc_(std::move(other.dealloc_)) {}
470
471 template <class U>
472 GenericAlloc& operator=(GenericAlloc<U>&& other) noexcept {
473 alloc_ = std::move(other.alloc_);
474 dealloc_ = std::move(other.dealloc_);
475 return *this;
476 }
477
allocate(size_t n)478 T* allocate(size_t n) { return static_cast<T*>((*alloc_)(n * sizeof(T))); }
deallocate(T * p,size_t n)479 void deallocate(T* p, size_t n) {
480 (*dealloc_)(static_cast<void*>(p), n * sizeof(T));
481 }
482
483 template <typename U>
484 bool operator==(GenericAlloc<U> const& rhs) const {
485 return alloc_ == rhs.alloc_;
486 }
487
488 template <typename U>
489 bool operator!=(GenericAlloc<U> const& rhs) const {
490 return !(*this == rhs);
491 }
492
493 private:
494 std::shared_ptr<AllocBytesFunc> alloc_;
495 std::shared_ptr<DeallocBytesFunc> dealloc_;
496
497 template <class U>
498 friend class GenericAlloc;
499 };
500
501 template <typename T>
502 class GenericEqual {
503 public:
504 using EqualFunc = folly::Function<bool(T const&, T const&)>;
505
506 GenericEqual() = delete;
507
508 template <typename E>
GenericEqual(E && equal)509 /* implicit */ GenericEqual(E&& equal)
510 : equal_{std::make_shared<EqualFunc>(std::forward<E>(equal))} {}
511
operator()512 bool operator()(T const& lhs, T const& rhs) const {
513 return (*equal_)(lhs, rhs);
514 }
515
516 private:
517 std::shared_ptr<EqualFunc> equal_;
518 };
519
520 template <typename T>
521 class GenericHasher {
522 public:
523 using HasherFunc = folly::Function<std::size_t(T const&)>;
524
525 GenericHasher() = delete;
526
527 template <typename H>
GenericHasher(H && hasher)528 /* implicit */ GenericHasher(H&& hasher)
529 : hasher_{std::make_shared<HasherFunc>(std::forward<H>(hasher))} {}
530
operator()531 std::size_t operator()(T const& val) const { return (*hasher_)(val); }
532
533 private:
534 std::shared_ptr<HasherFunc> hasher_;
535 };
536
537 struct HashFirst {
538 template <typename P>
operatorHashFirst539 std::size_t operator()(P const& p) const {
540 return folly::Hash{}(p.first);
541 }
542 };
543
544 struct EqualFirst {
545 template <typename P>
operatorEqualFirst546 bool operator()(P const& lhs, P const& rhs) const {
547 return lhs.first == rhs.first;
548 }
549 };
550
551 } // namespace test
552 } // namespace folly
553
554 namespace std {
555 template <>
556 struct hash<folly::test::MoveOnlyTestInt> {
557 std::size_t operator()(folly::test::MoveOnlyTestInt const& val) const {
558 FOLLY_SAFE_CHECK(!val.destroyed, "");
559 return val.x;
560 }
561 };
562
563 template <>
564 struct hash<folly::test::ThrowOnCopyTestInt> {
565 std::size_t operator()(folly::test::ThrowOnCopyTestInt const& val) const {
566 return val.x;
567 }
568 };
569
570 template <>
571 struct hash<folly::test::PermissiveConstructorTestInt> {
572 std::size_t operator()(
573 folly::test::PermissiveConstructorTestInt const& val) const {
574 return val.x;
575 }
576 };
577
578 template <int Tag>
579 struct hash<folly::test::Tracked<Tag>> {
580 size_t operator()(folly::test::Tracked<Tag> const& tracked) const {
581 return tracked.val_ ^ Tag;
582 }
583 };
584 } // namespace std
585