1 /*
2     Copyright (c) 2005-2022 Intel Corporation
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 // Basic testing of an allocator
18 // Tests against requirements in 20.1.5 of ISO C++ Standard (1998).
19 // Does not check for thread safety or false sharing issues.
20 //
21 // Tests for compatibility with the host's STL are in
22 // test_Allocator_STL.h.  Those tests are in a separate file
23 // because they bring in lots of STL headers, and the tests here
24 // are supposed to work in the abscense of STL.
25 
26 #ifndef __TBB_test_common_allocator_test_common_H_
27 #define __TBB_test_common_allocator_test_common_H_
28 
29 #include "common/test.h"
30 #include "common/utils.h"
31 #include <utility> //for std::pair
32 #include <cstring>
33 
34 //! Compile-time error if x and y have different types
35 template<typename T>
AssertSameType(const T &,const T &)36 void AssertSameType( const T& /*x*/, const T& /*y*/ ) {}
37 
38 //! The function to zero-initialize arrays; useful to avoid warnings
39 template <typename T>
zero_fill(void * array,size_t n)40 void zero_fill(void* array, size_t n) {
41     memset(array, 0, sizeof(T)*n);
42 }
43 
44 template<typename A>
45 struct is_zero_filling {
46     static const bool value = false;
47 };
48 
49 int NumberOfFoo;
50 
51 template<typename T, size_t N>
52 struct Foo {
53     T foo_array[N];
FooFoo54     Foo() {
55         zero_fill<T>(foo_array, N);
56         ++NumberOfFoo;
57     }
FooFoo58     Foo( const Foo& x ) {
59         *this = x;
60         //Internal call of assignment
61     }
62     Foo& operator=( const Foo& x ) {
63         for (size_t i = 0; i < N; i++)
64             foo_array[i] = x.foo_array[i];
65         ++NumberOfFoo;
66         return *this;
67     }
68 
~FooFoo69     ~Foo() {
70         --NumberOfFoo;
71     }
72 };
73 
PseudoRandomValue(size_t j,size_t k)74 inline char PseudoRandomValue( size_t j, size_t k ) {
75     return char(j*3 ^ j>>4 ^ k);
76 }
77 
78 #if __APPLE__
79 #include <fcntl.h>
80 #include <unistd.h>
81 
82 // A RAII class to disable stderr in a certain scope. It's not thread-safe.
83 class DisableStderr {
84     int stderrCopy;
dupToStderrAndClose(int fd)85     static void dupToStderrAndClose(int fd) {
86         int ret = dup2(fd, STDERR_FILENO); // close current stderr
87         REQUIRE(ret != -1);
88         ret = close(fd);
89         REQUIRE(ret != -1);
90     }
91 public:
DisableStderr()92     DisableStderr() {
93         int devNull = open("/dev/null", O_WRONLY);
94         REQUIRE(devNull != -1);
95         stderrCopy = dup(STDERR_FILENO);
96         REQUIRE(stderrCopy != -1);
97         dupToStderrAndClose(devNull);
98     }
~DisableStderr()99     ~DisableStderr() {
100         dupToStderrAndClose(stderrCopy);
101     }
102 };
103 #endif
104 
105 //! T is type and A is allocator for that type
106 template<typename T, typename A>
TestBrokenAllocator(A & a)107 void TestBrokenAllocator(A& a) {
108     T x;
109     const T cx = T();
110     // See Table 32 in ISO ++ Standard
111     typename A::pointer px = &x;
112     typename A::const_pointer pcx = &cx;
113 
114     typename A::reference rx = x;
115     REQUIRE(&rx == &x);
116 
117     typename A::const_reference rcx = cx;
118     REQUIRE(&rcx==&cx);
119 
120     typename A::value_type v = x;
121 
122     typename A::size_type size;
123     size = 0;
124     --size;
125     REQUIRE_MESSAGE(size > 0, "not an unsigned integral type?");
126 
127     typename A::difference_type difference;
128     difference = 0;
129     --difference;
130     REQUIRE_MESSAGE(difference<0, "not an signed integral type?");
131 
132     // "rebind" tested by our caller
133 
134     REQUIRE(a.address(rx) == px);
135 
136     REQUIRE(a.address(rcx) == pcx);
137 
138     // Test "a.max_size()"
139     AssertSameType(a.max_size(), typename A::size_type(0));
140     // Following assertion catches case where max_size() is so large that computation of
141     // number of bytes for such an allocation would overflow size_type.
142     REQUIRE_MESSAGE((a.max_size() * typename A::size_type(sizeof(T)) >= a.max_size()), "max_size larger than reasonable");
143 
144     // Test "a.construct(p,t)"
145     int n = NumberOfFoo;
146     typename A::pointer p = a.allocate(1);
147     a.construct(p, cx);
148     REQUIRE_MESSAGE(NumberOfFoo == n + 1, "constructor for Foo not called?");
149 
150     // Test "a.destroy(p)"
151     a.destroy(p);
152     REQUIRE_MESSAGE(NumberOfFoo == n, "destructor for Foo not called?");
153     a.deallocate(p, 1);
154 
155     {
156         typedef typename A::template rebind<std::pair<typename A::value_type, typename A::value_type> >::other pair_allocator_type;
157         pair_allocator_type pair_allocator(a);
158         int NumberOfFooBeforeConstruct = NumberOfFoo;
159         typename pair_allocator_type::pointer pair_pointer = pair_allocator.allocate(1);
160         pair_allocator.construct(pair_pointer, cx, cx);
161         REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct+2, "constructor for Foo not called appropriate number of times?");
162 
163         pair_allocator.destroy(pair_pointer);
164         REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct, "destructor for Foo not called appropriate number of times?");
165         pair_allocator.deallocate(pair_pointer, 1);
166     }
167 }
168 
169 //! T is type and A is allocator for that type
170 template<typename T, typename A>
TestAllocatorConcept(A & a)171 void TestAllocatorConcept(A& a) {
172     // Test "a.allocate(p,n)
173     typename std::allocator_traits<A>::pointer array[100];
174     std::size_t sizeof_T = sizeof(T);
175     for(std::size_t k = 0; k < 100; ++k) {
176         array[k] = a.allocate(k);
177         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
178         for(std::size_t j=0; j < k * sizeof_T; ++j)
179             s[j] = PseudoRandomValue(j, k);
180     }
181 
182     // Test "a.deallocate(p,n)
183     for(std::size_t k = 0; k < 100; ++k) {
184         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
185         for(std::size_t j = 0; j < k * sizeof_T; ++j)
186             REQUIRE(s[j] == PseudoRandomValue(j, k));
187         a.deallocate(array[k], k);
188     }
189 }
190 
191 //! T is type and A is allocator for that type
192 template<typename T, typename A>
TestAllocatorExceptions(A & a)193 void TestAllocatorExceptions(A& a) {
194 #if TBB_USE_EXCEPTIONS
195     volatile size_t too_big = (~std::size_t(0) - 1024 * 1024) / sizeof(T);
196     bool exception_caught = false;
197     typename std::allocator_traits<A>::pointer p1 = nullptr;
198     try {
199 #if __APPLE__
200         // On macOS*, failure to map memory results in messages to stderr;
201         // suppress them.
202         DisableStderr disableStderr;
203 #endif
204         p1 = a.allocate(too_big);
205     } catch (std::bad_alloc&) {
206         exception_caught = true;
207     }
208     REQUIRE_MESSAGE(exception_caught, "allocate expected to throw bad_alloc");
209     a.deallocate(p1, too_big);
210 #endif // TBB_USE_EXCEPTIONS
211     utils::suppress_unused_warning(a);
212 }
213 
214 #if _MSC_VER && !defined(__INTEL_COMPILER)
215     // Workaround for erroneous "conditional expression is constant" warning in method check_allocate.
216     #pragma warning (disable: 4127)
217 #endif
218 
219 // A is an allocator for some type
220 template<typename A>
221 struct Body: utils::NoAssign {
222     using pointer_type = typename std::allocator_traits<A>::pointer;
223     using value_type = typename std::allocator_traits<A>::value_type;
224     // For the int types and above this test runs too long
225     static const std::size_t max_k = sizeof(value_type) < sizeof(int) ? 100000 : 5000;
226     A &a;
BodyBody227     Body(A &a_) : a(a_) {}
check_allocateBody228     void check_allocate(pointer_type array[], std::size_t i, std::size_t t) const
229     {
230         REQUIRE(array[i] == nullptr);
231         std::size_t size = i * (i & 3);
232         array[i] = a.allocate(size);
233         REQUIRE_MESSAGE(array[i] != nullptr, "allocator returned null");
234         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
235         for(std::size_t j = 0; j < size * sizeof(value_type); ++j) {
236             if(is_zero_filling<typename std::allocator_traits<A>::template rebind_alloc<void>>::value)
237                 REQUIRE(!s[j]);
238             s[j] = PseudoRandomValue(i, t);
239         }
240     }
241 
check_deallocateBody242     void check_deallocate(pointer_type array[], std::size_t i, std::size_t t) const
243     {
244         REQUIRE(array[i] != nullptr);
245         size_t size = i * (i & 3);
246         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
247         for(std::size_t j=0; j < size * sizeof(value_type); ++j)
248             REQUIRE_MESSAGE(s[j] == PseudoRandomValue(i, t), "Thread safety test failed");
249         a.deallocate(array[i], size);
250         array[i] = nullptr;
251     }
252 
operatorBody253     void operator()(std::size_t thread_id) const {
254         pointer_type array[256];
255 
256         for(std::size_t k = 0; k < 256; ++k)
257             array[k] = nullptr;
258         for(std::size_t k = 0; k < max_k; ++k) {
259             std::size_t i = static_cast<unsigned char>(PseudoRandomValue(k, thread_id));
260             if(!array[i]) check_allocate(array, i, thread_id);
261             else check_deallocate(array, i, thread_id);
262         }
263         for(std::size_t k = 0; k < 256; ++k)
264             if(array[k])
265                 check_deallocate(array, k, thread_id);
266     }
267 };
268 
269 template<typename A>
TestThreadSafety(A & a)270 void TestThreadSafety(A &a) {
271     utils::NativeParallelFor(4, Body<A>(a));
272 }
273 
274 enum TestName { Concept, Broken, Exceptions, ThreadSafety, Comparison };
275 
276 template<typename Allocator>
277 void TestAllocator(TestName name, const Allocator &a = Allocator()) {
278 
279     using FooChar = Foo<char, 1>;
280     using FooDouble = Foo<double, 1>;
281     using FooInt = Foo<int, 17>;
282     using FooFloat = Foo<float, 23>;
283     #if TBB_ALLOCATOR_TRAITS_BROKEN
284         using AllocatorFooChar = typename Allocator::template rebind<FooChar>::other;
285         using AllocatorFooDouble = typename Allocator::template rebind<FooDouble>::other;
286         using AllocatorFooInt = typename AllocatorFooChar::template rebind<FooInt>::other;
287         using AllocatorFooFloat = typename AllocatorFooDouble::template rebind<FooFloat>::other;
288     #else
289         using AllocatorFooChar = typename std::allocator_traits<Allocator>::template rebind_alloc<FooChar>;
290         using AllocatorFooDouble = typename std::allocator_traits<Allocator>::template rebind_alloc<FooDouble>;
291         using AllocatorFooInt = typename std::allocator_traits<AllocatorFooChar>::template rebind_alloc<FooInt>;
292         using AllocatorFooFloat = typename std::allocator_traits<AllocatorFooDouble>::template rebind_alloc<FooFloat>;
293     #endif
294 
295     NumberOfFoo = 0;
296     Allocator a_cpy(a);
297     AllocatorFooChar a1(a);
298     AllocatorFooDouble a2(a);
299     AllocatorFooInt b1(a1);
300     AllocatorFooFloat b2(a2);
301 
302     switch(name) {
303     case Comparison:
304         REQUIRE(a_cpy == a);
305         REQUIRE(a1 == b1);
306         REQUIRE(!(a2 != b2));
307         break;
308     case Concept:
309         TestAllocatorConcept<FooInt>(b1);
310         TestAllocatorConcept<typename AllocatorFooChar::value_type>(a1);
311         TestAllocatorConcept<FooFloat>(b2);
312         TestAllocatorConcept<typename AllocatorFooDouble::value_type>(a2);
313         break;
314     case Broken:
315     #if TBB_ALLOCATOR_TRAITS_BROKEN
316         TestBrokenAllocator<FooInt>(b1);
317         TestBrokenAllocator<typename AllocatorFooChar::value_type>(a1);
318         TestBrokenAllocator<FooFloat>(b2);
319         TestBrokenAllocator<typename AllocatorFooDouble::value_type>(a2);
320     #endif
321         break;
322     case Exceptions:
323         TestAllocatorExceptions<FooInt>(b1);
324         TestAllocatorExceptions<typename AllocatorFooChar::value_type>(a1);
325         TestAllocatorExceptions<FooFloat>(b2);
326         TestAllocatorExceptions<typename AllocatorFooDouble::value_type>(a2);
327         break;
328     case ThreadSafety:
329         TestThreadSafety(a1);
330         TestThreadSafety(a2);
331         break;
332     }
333     REQUIRE_MESSAGE(NumberOfFoo == 0, "Allocate/deallocate count mismatched");
334 }
335 #endif // __TBB_test_common_allocator_test_common_H_
336