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