• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #if !SCUDO_FUCHSIA
60   testSecondaryBasic<scudo::MapAllocator<scudo::MapAllocatorCache<>>>();
61   testSecondaryBasic<
62       scudo::MapAllocator<scudo::MapAllocatorCache<128U, 64U, 1UL << 20>>>();
63 #endif
64 }
65 
66 #if SCUDO_FUCHSIA
67 using LargeAllocator = scudo::MapAllocator<scudo::MapAllocatorNoCache>;
68 #else
69 using LargeAllocator = scudo::MapAllocator<scudo::MapAllocatorCache<>>;
70 #endif
71 
72 // This exercises a variety of combinations of size and alignment for the
73 // MapAllocator. The size computation done here mimic the ones done by the
74 // combined allocator.
TEST(ScudoSecondaryTest,SecondaryCombinations)75 TEST(ScudoSecondaryTest, SecondaryCombinations) {
76   constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
77   constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
78   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
79   L->init(nullptr);
80   for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
81     for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
82          AlignLog++) {
83       const scudo::uptr Align = 1U << AlignLog;
84       for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
85         if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
86           continue;
87         const scudo::uptr UserSize =
88             scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
89         const scudo::uptr Size =
90             HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
91         void *P = L->allocate(Size, Align);
92         EXPECT_NE(P, nullptr);
93         void *AlignedP = reinterpret_cast<void *>(
94             scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
95         memset(AlignedP, 0xff, UserSize);
96         L->deallocate(P);
97       }
98     }
99   }
100   scudo::ScopedString Str(1024);
101   L->getStats(&Str);
102   Str.output();
103 }
104 
TEST(ScudoSecondaryTest,SecondaryIterate)105 TEST(ScudoSecondaryTest, SecondaryIterate) {
106   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
107   L->init(nullptr);
108   std::vector<void *> V;
109   const scudo::uptr PageSize = scudo::getPageSizeCached();
110   for (scudo::uptr I = 0; I < 32U; I++)
111     V.push_back(L->allocate((std::rand() % 16) * PageSize));
112   auto Lambda = [V](scudo::uptr Block) {
113     EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
114               V.end());
115   };
116   L->disable();
117   L->iterateOverBlocks(Lambda);
118   L->enable();
119   while (!V.empty()) {
120     L->deallocate(V.back());
121     V.pop_back();
122   }
123   scudo::ScopedString Str(1024);
124   L->getStats(&Str);
125   Str.output();
126 }
127 
TEST(ScudoSecondaryTest,SecondaryOptions)128 TEST(ScudoSecondaryTest, SecondaryOptions) {
129   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
130   L->init(nullptr);
131   // Attempt to set a maximum number of entries higher than the array size.
132   EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
133   // A negative number will be cast to a scudo::u32, and fail.
134   EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, -1));
135   if (L->canCache(0U)) {
136     // Various valid combinations.
137     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
138     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
139     EXPECT_TRUE(L->canCache(1UL << 18));
140     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
141     EXPECT_FALSE(L->canCache(1UL << 18));
142     EXPECT_TRUE(L->canCache(1UL << 16));
143     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
144     EXPECT_FALSE(L->canCache(1UL << 16));
145     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
146     EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
147     EXPECT_TRUE(L->canCache(1UL << 16));
148   }
149 }
150 
151 static std::mutex Mutex;
152 static std::condition_variable Cv;
153 static bool Ready;
154 
performAllocations(LargeAllocator * L)155 static void performAllocations(LargeAllocator *L) {
156   std::vector<void *> V;
157   const scudo::uptr PageSize = scudo::getPageSizeCached();
158   {
159     std::unique_lock<std::mutex> Lock(Mutex);
160     while (!Ready)
161       Cv.wait(Lock);
162   }
163   for (scudo::uptr I = 0; I < 128U; I++) {
164     // Deallocate 75% of the blocks.
165     const bool Deallocate = (rand() & 3) != 0;
166     void *P = L->allocate((std::rand() % 16) * PageSize);
167     if (Deallocate)
168       L->deallocate(P);
169     else
170       V.push_back(P);
171   }
172   while (!V.empty()) {
173     L->deallocate(V.back());
174     V.pop_back();
175   }
176 }
177 
TEST(ScudoSecondaryTest,SecondaryThreadsRace)178 TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
179   Ready = false;
180   std::unique_ptr<LargeAllocator> L(new LargeAllocator);
181   L->init(nullptr, /*ReleaseToOsInterval=*/0);
182   std::thread Threads[16];
183   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
184     Threads[I] = std::thread(performAllocations, L.get());
185   {
186     std::unique_lock<std::mutex> Lock(Mutex);
187     Ready = true;
188     Cv.notify_all();
189   }
190   for (auto &T : Threads)
191     T.join();
192   scudo::ScopedString Str(1024);
193   L->getStats(&Str);
194   Str.output();
195 }
196