1 //===-- primary_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 "primary32.h" 12 #include "primary64.h" 13 #include "size_class_map.h" 14 15 #include <condition_variable> 16 #include <mutex> 17 #include <thread> 18 #include <vector> 19 20 // Note that with small enough regions, the SizeClassAllocator64 also works on 21 // 32-bit architectures. It's not something we want to encourage, but we still 22 // should ensure the tests pass. 23 24 template <typename Primary> static void testPrimary() { 25 const scudo::uptr NumberOfAllocations = 32U; 26 auto Deleter = [](Primary *P) { 27 P->unmapTestOnly(); 28 delete P; 29 }; 30 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); 31 Allocator->init(/*ReleaseToOsInterval=*/-1); 32 typename Primary::CacheT Cache; 33 Cache.init(nullptr, Allocator.get()); 34 for (scudo::uptr I = 0; I <= 16U; I++) { 35 const scudo::uptr Size = 1UL << I; 36 if (!Primary::canAllocate(Size)) 37 continue; 38 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); 39 void *Pointers[NumberOfAllocations]; 40 for (scudo::uptr J = 0; J < NumberOfAllocations; J++) { 41 void *P = Cache.allocate(ClassId); 42 memset(P, 'B', Size); 43 Pointers[J] = P; 44 } 45 for (scudo::uptr J = 0; J < NumberOfAllocations; J++) 46 Cache.deallocate(ClassId, Pointers[J]); 47 } 48 Cache.destroy(nullptr); 49 Allocator->releaseToOS(); 50 scudo::ScopedString Str(1024); 51 Allocator->getStats(&Str); 52 Str.output(); 53 } 54 55 TEST(ScudoPrimaryTest, BasicPrimary) { 56 using SizeClassMap = scudo::DefaultSizeClassMap; 57 #if !SCUDO_FUCHSIA 58 testPrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>(); 59 #endif 60 testPrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>(); 61 testPrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U, true>>(); 62 } 63 64 // The 64-bit SizeClassAllocator can be easily OOM'd with small region sizes. 65 // For the 32-bit one, it requires actually exhausting memory, so we skip it. 66 TEST(ScudoPrimaryTest, Primary64OOM) { 67 using Primary = scudo::SizeClassAllocator64<scudo::DefaultSizeClassMap, 20U>; 68 using TransferBatch = Primary::CacheT::TransferBatch; 69 Primary Allocator; 70 Allocator.init(/*ReleaseToOsInterval=*/-1); 71 typename Primary::CacheT Cache; 72 scudo::GlobalStats Stats; 73 Stats.init(); 74 Cache.init(&Stats, &Allocator); 75 bool AllocationFailed = false; 76 std::vector<TransferBatch *> Batches; 77 const scudo::uptr ClassId = Primary::SizeClassMap::LargestClassId; 78 const scudo::uptr Size = Primary::getSizeByClassId(ClassId); 79 for (scudo::uptr I = 0; I < 10000U; I++) { 80 TransferBatch *B = Allocator.popBatch(&Cache, ClassId); 81 if (!B) { 82 AllocationFailed = true; 83 break; 84 } 85 for (scudo::u32 J = 0; J < B->getCount(); J++) 86 memset(B->get(J), 'B', Size); 87 Batches.push_back(B); 88 } 89 while (!Batches.empty()) { 90 Allocator.pushBatch(ClassId, Batches.back()); 91 Batches.pop_back(); 92 } 93 Cache.destroy(nullptr); 94 Allocator.releaseToOS(); 95 scudo::ScopedString Str(1024); 96 Allocator.getStats(&Str); 97 Str.output(); 98 EXPECT_EQ(AllocationFailed, true); 99 Allocator.unmapTestOnly(); 100 } 101 102 template <typename Primary> static void testIteratePrimary() { 103 auto Deleter = [](Primary *P) { 104 P->unmapTestOnly(); 105 delete P; 106 }; 107 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); 108 Allocator->init(/*ReleaseToOsInterval=*/-1); 109 typename Primary::CacheT Cache; 110 Cache.init(nullptr, Allocator.get()); 111 std::vector<std::pair<scudo::uptr, void *>> V; 112 for (scudo::uptr I = 0; I < 64U; I++) { 113 const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize; 114 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); 115 void *P = Cache.allocate(ClassId); 116 V.push_back(std::make_pair(ClassId, P)); 117 } 118 scudo::uptr Found = 0; 119 auto Lambda = [V, &Found](scudo::uptr Block) { 120 for (const auto &Pair : V) { 121 if (Pair.second == reinterpret_cast<void *>(Block)) 122 Found++; 123 } 124 }; 125 Allocator->disable(); 126 Allocator->iterateOverBlocks(Lambda); 127 Allocator->enable(); 128 EXPECT_EQ(Found, V.size()); 129 while (!V.empty()) { 130 auto Pair = V.back(); 131 Cache.deallocate(Pair.first, Pair.second); 132 V.pop_back(); 133 } 134 Cache.destroy(nullptr); 135 Allocator->releaseToOS(); 136 scudo::ScopedString Str(1024); 137 Allocator->getStats(&Str); 138 Str.output(); 139 } 140 141 TEST(ScudoPrimaryTest, PrimaryIterate) { 142 using SizeClassMap = scudo::DefaultSizeClassMap; 143 #if !SCUDO_FUCHSIA 144 testIteratePrimary<scudo::SizeClassAllocator32<SizeClassMap, 18U>>(); 145 #endif 146 testIteratePrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U>>(); 147 testIteratePrimary<scudo::SizeClassAllocator64<SizeClassMap, 24U, true>>(); 148 } 149 150 static std::mutex Mutex; 151 static std::condition_variable Cv; 152 static bool Ready = false; 153 154 template <typename Primary> static void performAllocations(Primary *Allocator) { 155 static THREADLOCAL typename Primary::CacheT Cache; 156 Cache.init(nullptr, Allocator); 157 std::vector<std::pair<scudo::uptr, void *>> V; 158 { 159 std::unique_lock<std::mutex> Lock(Mutex); 160 while (!Ready) 161 Cv.wait(Lock); 162 } 163 for (scudo::uptr I = 0; I < 256U; I++) { 164 const scudo::uptr Size = std::rand() % Primary::SizeClassMap::MaxSize / 4; 165 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); 166 void *P = Cache.allocate(ClassId); 167 if (P) 168 V.push_back(std::make_pair(ClassId, P)); 169 } 170 while (!V.empty()) { 171 auto Pair = V.back(); 172 Cache.deallocate(Pair.first, Pair.second); 173 V.pop_back(); 174 } 175 Cache.destroy(nullptr); 176 } 177 178 template <typename Primary> static void testPrimaryThreaded() { 179 auto Deleter = [](Primary *P) { 180 P->unmapTestOnly(); 181 delete P; 182 }; 183 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); 184 Allocator->init(/*ReleaseToOsInterval=*/-1); 185 std::thread Threads[32]; 186 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++) 187 Threads[I] = std::thread(performAllocations<Primary>, Allocator.get()); 188 { 189 std::unique_lock<std::mutex> Lock(Mutex); 190 Ready = true; 191 Cv.notify_all(); 192 } 193 for (auto &T : Threads) 194 T.join(); 195 Allocator->releaseToOS(); 196 scudo::ScopedString Str(1024); 197 Allocator->getStats(&Str); 198 Str.output(); 199 } 200 201 TEST(ScudoPrimaryTest, PrimaryThreaded) { 202 using SizeClassMap = scudo::SvelteSizeClassMap; 203 #if !SCUDO_FUCHSIA 204 testPrimaryThreaded<scudo::SizeClassAllocator32<SizeClassMap, 18U>>(); 205 #endif 206 testPrimaryThreaded<scudo::SizeClassAllocator64<SizeClassMap, 24U>>(); 207 testPrimaryThreaded<scudo::SizeClassAllocator64<SizeClassMap, 24U, true>>(); 208 } 209 210 // Through a simple allocation that spans two pages, verify that releaseToOS 211 // actually releases some bytes (at least one page worth). This is a regression 212 // test for an error in how the release criteria were computed. 213 template <typename Primary> static void testReleaseToOS() { 214 auto Deleter = [](Primary *P) { 215 P->unmapTestOnly(); 216 delete P; 217 }; 218 std::unique_ptr<Primary, decltype(Deleter)> Allocator(new Primary, Deleter); 219 Allocator->init(/*ReleaseToOsInterval=*/-1); 220 typename Primary::CacheT Cache; 221 Cache.init(nullptr, Allocator.get()); 222 const scudo::uptr Size = scudo::getPageSizeCached() * 2; 223 EXPECT_TRUE(Primary::canAllocate(Size)); 224 const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size); 225 void *P = Cache.allocate(ClassId); 226 EXPECT_NE(P, nullptr); 227 Cache.deallocate(ClassId, P); 228 Cache.destroy(nullptr); 229 EXPECT_GT(Allocator->releaseToOS(), 0U); 230 } 231 232 TEST(ScudoPrimaryTest, ReleaseToOS) { 233 using SizeClassMap = scudo::DefaultSizeClassMap; 234 #if !SCUDO_FUCHSIA 235 testReleaseToOS<scudo::SizeClassAllocator32<SizeClassMap, 18U>>(); 236 #endif 237 testReleaseToOS<scudo::SizeClassAllocator64<SizeClassMap, 24U>>(); 238 testReleaseToOS<scudo::SizeClassAllocator64<SizeClassMap, 24U, true>>(); 239 } 240