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 
21 template <class SecondaryT> static void testSecondaryBasic(void) {
22   scudo::GlobalStats S;
23   S.init();
24   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 (!SecondaryT::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 
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<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.
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   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 
105 TEST(ScudoSecondaryTest, SecondaryIterate) {
106   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 
128 static std::mutex Mutex;
129 static std::condition_variable Cv;
130 static bool Ready = false;
131 
132 static void performAllocations(LargeAllocator *L) {
133   std::vector<void *> V;
134   const scudo::uptr PageSize = scudo::getPageSizeCached();
135   {
136     std::unique_lock<std::mutex> Lock(Mutex);
137     while (!Ready)
138       Cv.wait(Lock);
139   }
140   for (scudo::uptr I = 0; I < 128U; I++) {
141     // Deallocate 75% of the blocks.
142     const bool Deallocate = (rand() & 3) != 0;
143     void *P = L->allocate((std::rand() % 16) * PageSize);
144     if (Deallocate)
145       L->deallocate(P);
146     else
147       V.push_back(P);
148   }
149   while (!V.empty()) {
150     L->deallocate(V.back());
151     V.pop_back();
152   }
153 }
154 
155 TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
156   LargeAllocator *L = new LargeAllocator;
157   L->init(nullptr, /*ReleaseToOsInterval=*/0);
158   std::thread Threads[16];
159   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
160     Threads[I] = std::thread(performAllocations, L);
161   {
162     std::unique_lock<std::mutex> Lock(Mutex);
163     Ready = true;
164     Cv.notify_all();
165   }
166   for (auto &T : Threads)
167     T.join();
168   scudo::ScopedString Str(1024);
169   L->getStats(&Str);
170   Str.output();
171 }
172