xref: /openbsd-src/gnu/llvm/compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp (revision 5a38ef86d0b61900239c7913d24a05e7b88a58f0)
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 "memtag.h"
10 #include "tests/scudo_unit_test.h"
11 
12 #include "allocator_config.h"
13 #include "secondary.h"
14 
15 #include <condition_variable>
16 #include <memory>
17 #include <mutex>
18 #include <random>
19 #include <stdio.h>
20 #include <thread>
21 #include <vector>
22 
23 template <typename Config> static scudo::Options getOptionsForConfig() {
24   if (!Config::MaySupportMemoryTagging || !scudo::archSupportsMemoryTagging() ||
25       !scudo::systemSupportsMemoryTagging())
26     return {};
27   scudo::AtomicOptions AO;
28   AO.set(scudo::OptionBit::UseMemoryTagging);
29   return AO.load();
30 }
31 
32 template <typename Config> static void testSecondaryBasic(void) {
33   using SecondaryT = scudo::MapAllocator<Config>;
34   scudo::Options Options = getOptionsForConfig<Config>();
35 
36   scudo::GlobalStats S;
37   S.init();
38   std::unique_ptr<SecondaryT> L(new SecondaryT);
39   L->init(&S);
40   const scudo::uptr Size = 1U << 16;
41   void *P = L->allocate(Options, Size);
42   EXPECT_NE(P, nullptr);
43   memset(P, 'A', Size);
44   EXPECT_GE(SecondaryT::getBlockSize(P), Size);
45   L->deallocate(Options, P);
46 
47   // If the Secondary can't cache that pointer, it will be unmapped.
48   if (!L->canCache(Size)) {
49     EXPECT_DEATH(
50         {
51           // Repeat few time to avoid missing crash if it's mmaped by unrelated
52           // code.
53           for (int i = 0; i < 10; ++i) {
54             P = L->allocate(Options, Size);
55             L->deallocate(Options, P);
56             memset(P, 'A', Size);
57           }
58         },
59         "");
60   }
61 
62   const scudo::uptr Align = 1U << 16;
63   P = L->allocate(Options, Size + Align, Align);
64   EXPECT_NE(P, nullptr);
65   void *AlignedP = reinterpret_cast<void *>(
66       scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
67   memset(AlignedP, 'A', Size);
68   L->deallocate(Options, P);
69 
70   std::vector<void *> V;
71   for (scudo::uptr I = 0; I < 32U; I++)
72     V.push_back(L->allocate(Options, Size));
73   std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));
74   while (!V.empty()) {
75     L->deallocate(Options, V.back());
76     V.pop_back();
77   }
78   scudo::ScopedString Str;
79   L->getStats(&Str);
80   Str.output();
81   L->unmapTestOnly();
82 }
83 
84 struct NoCacheConfig {
85   typedef scudo::MapAllocatorNoCache SecondaryCache;
86   static const bool MaySupportMemoryTagging = false;
87 };
88 
89 struct TestConfig {
90   typedef scudo::MapAllocatorCache<TestConfig> SecondaryCache;
91   static const bool MaySupportMemoryTagging = false;
92   static const scudo::u32 SecondaryCacheEntriesArraySize = 128U;
93   static const scudo::u32 SecondaryCacheQuarantineSize = 0U;
94   static const scudo::u32 SecondaryCacheDefaultMaxEntriesCount = 64U;
95   static const scudo::uptr SecondaryCacheDefaultMaxEntrySize = 1UL << 20;
96   static const scudo::s32 SecondaryCacheMinReleaseToOsIntervalMs = INT32_MIN;
97   static const scudo::s32 SecondaryCacheMaxReleaseToOsIntervalMs = INT32_MAX;
98 };
99 
100 TEST(ScudoSecondaryTest, SecondaryBasic) {
101   testSecondaryBasic<NoCacheConfig>();
102   testSecondaryBasic<scudo::DefaultConfig>();
103   testSecondaryBasic<TestConfig>();
104 }
105 
106 struct MapAllocatorTest : public Test {
107   using Config = scudo::DefaultConfig;
108   using LargeAllocator = scudo::MapAllocator<Config>;
109 
110   void SetUp() override { Allocator->init(nullptr); }
111 
112   void TearDown() override { Allocator->unmapTestOnly(); }
113 
114   std::unique_ptr<LargeAllocator> Allocator =
115       std::make_unique<LargeAllocator>();
116   scudo::Options Options = getOptionsForConfig<Config>();
117 };
118 
119 // This exercises a variety of combinations of size and alignment for the
120 // MapAllocator. The size computation done here mimic the ones done by the
121 // combined allocator.
122 TEST_F(MapAllocatorTest, SecondaryCombinations) {
123   constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
124   constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
125   for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
126     for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
127          AlignLog++) {
128       const scudo::uptr Align = 1U << AlignLog;
129       for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
130         if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
131           continue;
132         const scudo::uptr UserSize =
133             scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
134         const scudo::uptr Size =
135             HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
136         void *P = Allocator->allocate(Options, Size, Align);
137         EXPECT_NE(P, nullptr);
138         void *AlignedP = reinterpret_cast<void *>(
139             scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
140         memset(AlignedP, 0xff, UserSize);
141         Allocator->deallocate(Options, P);
142       }
143     }
144   }
145   scudo::ScopedString Str;
146   Allocator->getStats(&Str);
147   Str.output();
148 }
149 
150 TEST_F(MapAllocatorTest, SecondaryIterate) {
151   std::vector<void *> V;
152   const scudo::uptr PageSize = scudo::getPageSizeCached();
153   for (scudo::uptr I = 0; I < 32U; I++)
154     V.push_back(Allocator->allocate(Options, (std::rand() % 16) * PageSize));
155   auto Lambda = [V](scudo::uptr Block) {
156     EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
157               V.end());
158   };
159   Allocator->disable();
160   Allocator->iterateOverBlocks(Lambda);
161   Allocator->enable();
162   while (!V.empty()) {
163     Allocator->deallocate(Options, V.back());
164     V.pop_back();
165   }
166   scudo::ScopedString Str;
167   Allocator->getStats(&Str);
168   Str.output();
169 }
170 
171 TEST_F(MapAllocatorTest, SecondaryOptions) {
172   // Attempt to set a maximum number of entries higher than the array size.
173   EXPECT_FALSE(
174       Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
175   // A negative number will be cast to a scudo::u32, and fail.
176   EXPECT_FALSE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, -1));
177   if (Allocator->canCache(0U)) {
178     // Various valid combinations.
179     EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
180     EXPECT_TRUE(
181         Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
182     EXPECT_TRUE(Allocator->canCache(1UL << 18));
183     EXPECT_TRUE(
184         Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
185     EXPECT_FALSE(Allocator->canCache(1UL << 18));
186     EXPECT_TRUE(Allocator->canCache(1UL << 16));
187     EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
188     EXPECT_FALSE(Allocator->canCache(1UL << 16));
189     EXPECT_TRUE(Allocator->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
190     EXPECT_TRUE(
191         Allocator->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
192     EXPECT_TRUE(Allocator->canCache(1UL << 16));
193   }
194 }
195 
196 struct MapAllocatorWithReleaseTest : public MapAllocatorTest {
197   void SetUp() override { Allocator->init(nullptr, /*ReleaseToOsInterval=*/0); }
198 
199   void performAllocations() {
200     std::vector<void *> V;
201     const scudo::uptr PageSize = scudo::getPageSizeCached();
202     {
203       std::unique_lock<std::mutex> Lock(Mutex);
204       while (!Ready)
205         Cv.wait(Lock);
206     }
207     for (scudo::uptr I = 0; I < 128U; I++) {
208       // Deallocate 75% of the blocks.
209       const bool Deallocate = (rand() & 3) != 0;
210       void *P = Allocator->allocate(Options, (std::rand() % 16) * PageSize);
211       if (Deallocate)
212         Allocator->deallocate(Options, P);
213       else
214         V.push_back(P);
215     }
216     while (!V.empty()) {
217       Allocator->deallocate(Options, V.back());
218       V.pop_back();
219     }
220   }
221 
222   std::mutex Mutex;
223   std::condition_variable Cv;
224   bool Ready = false;
225 };
226 
227 TEST_F(MapAllocatorWithReleaseTest, SecondaryThreadsRace) {
228   std::thread Threads[16];
229   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
230     Threads[I] =
231         std::thread(&MapAllocatorWithReleaseTest::performAllocations, this);
232   {
233     std::unique_lock<std::mutex> Lock(Mutex);
234     Ready = true;
235     Cv.notify_all();
236   }
237   for (auto &T : Threads)
238     T.join();
239   scudo::ScopedString Str;
240   Allocator->getStats(&Str);
241   Str.output();
242 }
243