1 //===-- secondary_test.cpp --------------------------------------*- C++ -*-===//
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 #include "tests/scudo_unit_test.h"
10 
11 #include "secondary.h"
12 
13 #include <stdio.h>
14 
15 #include <condition_variable>
16 #include <mutex>
17 #include <random>
18 #include <thread>
19 #include <vector>
20 
testSecondaryBasic(void)21 template <class SecondaryT> static void testSecondaryBasic(void) {
22   scudo::GlobalStats S;
23   S.init();
24   std::unique_ptr<SecondaryT> L(new SecondaryT);
25   L->init(&S);
26   const scudo::uptr Size = 1U << 16;
27   void *P = L->allocate(Size);
28   EXPECT_NE(P, nullptr);
29   memset(P, 'A', Size);
30   EXPECT_GE(SecondaryT::getBlockSize(P), Size);
31   L->deallocate(P);
32   // If the Secondary can't cache that pointer, it will be unmapped.
33   if (!L->canCache(Size))
34     EXPECT_DEATH(memset(P, 'A', Size), "");
35 
36   const scudo::uptr Align = 1U << 16;
37   P = L->allocate(Size + Align, Align);
38   EXPECT_NE(P, nullptr);
39   void *AlignedP = reinterpret_cast<void *>(
40       scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
41   memset(AlignedP, 'A', Size);
42   L->deallocate(P);
43 
44   std::vector<void *> V;
45   for (scudo::uptr I = 0; I < 32U; I++)
46     V.push_back(L->allocate(Size));
47   std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));
48   while (!V.empty()) {
49     L->deallocate(V.back());
50     V.pop_back();
51   }
52   scudo::ScopedString Str(1024);
53   L->getStats(&Str);
54   Str.output();
55 }
56 
TEST(ScudoSecondaryTest,SecondaryBasic)57 TEST(ScudoSecondaryTest, SecondaryBasic) {
58   testSecondaryBasic<scudo::MapAllocator<scudo::MapAllocatorNoCache>>();
59   testSecondaryBasic<scudo::MapAllocator<scudo::MapAllocatorCache<>>>();
60   testSecondaryBasic<
61       scudo::MapAllocator<scudo::MapAllocatorCache<128U, 64U, 1UL << 20>>>();
62 }
63 
64 using LargeAllocator = scudo::MapAllocator<scudo::MapAllocatorCache<>>;
65 
66 // This exercises a variety of combinations of size and alignment for the
67 // MapAllocator. The size computation done here mimic the ones done by the
68 // combined allocator.
TEST(ScudoSecondaryTest,SecondaryCombinations)69 TEST(ScudoSecondaryTest, SecondaryCombinations) {
70   constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
71   constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
72   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
73   L->init(nullptr);
74   for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
75     for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
76          AlignLog++) {
77       const scudo::uptr Align = 1U << AlignLog;
78       for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
79         if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
80           continue;
81         const scudo::uptr UserSize =
82             scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
83         const scudo::uptr Size =
84             HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
85         void *P = L->allocate(Size, Align);
86         EXPECT_NE(P, nullptr);
87         void *AlignedP = reinterpret_cast<void *>(
88             scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
89         memset(AlignedP, 0xff, UserSize);
90         L->deallocate(P);
91       }
92     }
93   }
94   scudo::ScopedString Str(1024);
95   L->getStats(&Str);
96   Str.output();
97 }
98 
TEST(ScudoSecondaryTest,SecondaryIterate)99 TEST(ScudoSecondaryTest, SecondaryIterate) {
100   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
101   L->init(nullptr);
102   std::vector<void *> V;
103   const scudo::uptr PageSize = scudo::getPageSizeCached();
104   for (scudo::uptr I = 0; I < 32U; I++)
105     V.push_back(L->allocate((std::rand() % 16) * PageSize));
106   auto Lambda = [V](scudo::uptr Block) {
107     EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
108               V.end());
109   };
110   L->disable();
111   L->iterateOverBlocks(Lambda);
112   L->enable();
113   while (!V.empty()) {
114     L->deallocate(V.back());
115     V.pop_back();
116   }
117   scudo::ScopedString Str(1024);
118   L->getStats(&Str);
119   Str.output();
120 }
121 
TEST(ScudoSecondaryTest,SecondaryOptions)122 TEST(ScudoSecondaryTest, SecondaryOptions) {
123   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
124   L->init(nullptr);
125   // Attempt to set a maximum number of entries higher than the array size.
126   EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
127   // A negative number will be cast to a scudo::u32, and fail.
128   EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, -1));
129   if (L->canCache(0U)) {
130     // Various valid combinations.
131     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
132     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
133     EXPECT_TRUE(L->canCache(1UL << 18));
134     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
135     EXPECT_FALSE(L->canCache(1UL << 18));
136     EXPECT_TRUE(L->canCache(1UL << 16));
137     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
138     EXPECT_FALSE(L->canCache(1UL << 16));
139     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
140     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
141     EXPECT_TRUE(L->canCache(1UL << 16));
142   }
143 }
144 
145 static std::mutex Mutex;
146 static std::condition_variable Cv;
147 static bool Ready;
148 
performAllocations(LargeAllocator * L)149 static void performAllocations(LargeAllocator *L) {
150   std::vector<void *> V;
151   const scudo::uptr PageSize = scudo::getPageSizeCached();
152   {
153     std::unique_lock<std::mutex> Lock(Mutex);
154     while (!Ready)
155       Cv.wait(Lock);
156   }
157   for (scudo::uptr I = 0; I < 128U; I++) {
158     // Deallocate 75% of the blocks.
159     const bool Deallocate = (rand() & 3) != 0;
160     void *P = L->allocate((std::rand() % 16) * PageSize);
161     if (Deallocate)
162       L->deallocate(P);
163     else
164       V.push_back(P);
165   }
166   while (!V.empty()) {
167     L->deallocate(V.back());
168     V.pop_back();
169   }
170 }
171 
TEST(ScudoSecondaryTest,SecondaryThreadsRace)172 TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
173   Ready = false;
174   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
175   L->init(nullptr, /*ReleaseToOsInterval=*/0);
176   std::thread Threads[16];
177   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
178     Threads[I] = std::thread(performAllocations, L.get());
179   {
180     std::unique_lock<std::mutex> Lock(Mutex);
181     Ready = true;
182     Cv.notify_all();
183   }
184   for (auto &T : Threads)
185     T.join();
186   scudo::ScopedString Str(1024);
187   L->getStats(&Str);
188   Str.output();
189 }
190