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