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 "secondary.h"
10 
11 #include "gtest/gtest.h"
12 
13 #include <stdio.h>
14 
15 #include <condition_variable>
16 #include <mutex>
17 #include <thread>
18 
19 template <class SecondaryT> static void testSecondaryBasic(void) {
20   scudo::GlobalStats S;
21   S.init();
22   SecondaryT *L = new SecondaryT;
23   L->init(&S);
24   const scudo::uptr Size = 1U << 16;
25   void *P = L->allocate(Size);
26   EXPECT_NE(P, nullptr);
27   memset(P, 'A', Size);
28   EXPECT_GE(SecondaryT::getBlockSize(P), Size);
29   L->deallocate(P);
30   // If we are not using a free list, blocks are unmapped on deallocation.
31   if (SecondaryT::getMaxFreeListSize() == 0U)
32     EXPECT_DEATH(memset(P, 'A', Size), "");
33 
34   const scudo::uptr Align = 1U << 16;
35   P = L->allocate(Size + Align, Align);
36   EXPECT_NE(P, nullptr);
37   void *AlignedP = reinterpret_cast<void *>(
38       scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
39   memset(AlignedP, 'A', Size);
40   L->deallocate(P);
41 
42   std::vector<void *> V;
43   for (scudo::uptr I = 0; I < 32U; I++)
44     V.push_back(L->allocate(Size));
45   std::random_shuffle(V.begin(), V.end());
46   while (!V.empty()) {
47     L->deallocate(V.back());
48     V.pop_back();
49   }
50   scudo::ScopedString Str(1024);
51   L->getStats(&Str);
52   Str.output();
53 }
54 
55 TEST(ScudoSecondaryTest, SecondaryBasic) {
56   testSecondaryBasic<scudo::MapAllocator<>>();
57   testSecondaryBasic<scudo::MapAllocator<0U>>();
58   testSecondaryBasic<scudo::MapAllocator<64U>>();
59 }
60 
61 using LargeAllocator = scudo::MapAllocator<>;
62 
63 // This exercises a variety of combinations of size and alignment for the
64 // MapAllocator. The size computation done here mimic the ones done by the
65 // combined allocator.
66 TEST(ScudoSecondaryTest, SecondaryCombinations) {
67   constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
68   constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
69   LargeAllocator *L = new LargeAllocator;
70   L->init(nullptr);
71   for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
72     for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
73          AlignLog++) {
74       const scudo::uptr Align = 1U << AlignLog;
75       for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
76         if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
77           continue;
78         const scudo::uptr UserSize =
79             scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
80         const scudo::uptr Size =
81             HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
82         void *P = L->allocate(Size, Align);
83         EXPECT_NE(P, nullptr);
84         void *AlignedP = reinterpret_cast<void *>(
85             scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
86         memset(AlignedP, 0xff, UserSize);
87         L->deallocate(P);
88       }
89     }
90   }
91   scudo::ScopedString Str(1024);
92   L->getStats(&Str);
93   Str.output();
94 }
95 
96 TEST(ScudoSecondaryTest, SecondaryIterate) {
97   LargeAllocator *L = new LargeAllocator;
98   L->init(nullptr);
99   std::vector<void *> V;
100   const scudo::uptr PageSize = scudo::getPageSizeCached();
101   for (scudo::uptr I = 0; I < 32U; I++)
102     V.push_back(L->allocate((std::rand() % 16) * PageSize));
103   auto Lambda = [V](scudo::uptr Block) {
104     EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
105               V.end());
106   };
107   L->disable();
108   L->iterateOverBlocks(Lambda);
109   L->enable();
110   while (!V.empty()) {
111     L->deallocate(V.back());
112     V.pop_back();
113   }
114   scudo::ScopedString Str(1024);
115   L->getStats(&Str);
116   Str.output();
117 }
118 
119 static std::mutex Mutex;
120 static std::condition_variable Cv;
121 static bool Ready = false;
122 
123 static void performAllocations(LargeAllocator *L) {
124   std::vector<void *> V;
125   const scudo::uptr PageSize = scudo::getPageSizeCached();
126   {
127     std::unique_lock<std::mutex> Lock(Mutex);
128     while (!Ready)
129       Cv.wait(Lock);
130   }
131   for (scudo::uptr I = 0; I < 32U; I++)
132     V.push_back(L->allocate((std::rand() % 16) * PageSize));
133   while (!V.empty()) {
134     L->deallocate(V.back());
135     V.pop_back();
136   }
137 }
138 
139 TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
140   LargeAllocator *L = new LargeAllocator;
141   L->init(nullptr);
142   std::thread Threads[10];
143   for (scudo::uptr I = 0; I < 10U; I++)
144     Threads[I] = std::thread(performAllocations, L);
145   {
146     std::unique_lock<std::mutex> Lock(Mutex);
147     Ready = true;
148     Cv.notify_all();
149   }
150   for (auto &T : Threads)
151     T.join();
152   scudo::ScopedString Str(1024);
153   L->getStats(&Str);
154   Str.output();
155 }
156