1 //===-- release_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 "list.h" 12 #include "release.h" 13 #include "size_class_map.h" 14 15 #include <string.h> 16 17 #include <algorithm> 18 #include <random> 19 #include <set> 20 21 TEST(ScudoReleaseTest, PackedCounterArray) { 22 for (scudo::uptr I = 0; I < SCUDO_WORDSIZE; I++) { 23 // Various valid counter's max values packed into one word. 24 scudo::PackedCounterArray Counters2N(1U, 1U, 1UL << I); 25 EXPECT_EQ(sizeof(scudo::uptr), Counters2N.getBufferSize()); 26 // Check the "all bit set" values too. 27 scudo::PackedCounterArray Counters2N1_1(1U, 1U, ~0UL >> I); 28 EXPECT_EQ(sizeof(scudo::uptr), Counters2N1_1.getBufferSize()); 29 // Verify the packing ratio, the counter is Expected to be packed into the 30 // closest power of 2 bits. 31 scudo::PackedCounterArray Counters(1U, SCUDO_WORDSIZE, 1UL << I); 32 EXPECT_EQ(sizeof(scudo::uptr) * scudo::roundUpToPowerOfTwo(I + 1), 33 Counters.getBufferSize()); 34 } 35 36 // Go through 1, 2, 4, 8, .. {32,64} bits per counter. 37 for (scudo::uptr I = 0; (SCUDO_WORDSIZE >> I) != 0; I++) { 38 // Make sure counters request one memory page for the buffer. 39 const scudo::uptr NumCounters = 40 (scudo::getPageSizeCached() / 8) * (SCUDO_WORDSIZE >> I); 41 scudo::PackedCounterArray Counters(1U, NumCounters, 42 1UL << ((1UL << I) - 1)); 43 Counters.inc(0U, 0U); 44 for (scudo::uptr C = 1; C < NumCounters - 1; C++) { 45 EXPECT_EQ(0UL, Counters.get(0U, C)); 46 Counters.inc(0U, C); 47 EXPECT_EQ(1UL, Counters.get(0U, C - 1)); 48 } 49 EXPECT_EQ(0UL, Counters.get(0U, NumCounters - 1)); 50 Counters.inc(0U, NumCounters - 1); 51 if (I > 0) { 52 Counters.incRange(0u, 0U, NumCounters - 1); 53 for (scudo::uptr C = 0; C < NumCounters; C++) 54 EXPECT_EQ(2UL, Counters.get(0U, C)); 55 } 56 } 57 } 58 59 class StringRangeRecorder { 60 public: 61 std::string ReportedPages; 62 63 StringRangeRecorder() 64 : PageSizeScaledLog(scudo::getLog2(scudo::getPageSizeCached())) {} 65 66 void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) { 67 From >>= PageSizeScaledLog; 68 To >>= PageSizeScaledLog; 69 EXPECT_LT(From, To); 70 if (!ReportedPages.empty()) 71 EXPECT_LT(LastPageReported, From); 72 ReportedPages.append(From - LastPageReported, '.'); 73 ReportedPages.append(To - From, 'x'); 74 LastPageReported = To; 75 } 76 77 private: 78 const scudo::uptr PageSizeScaledLog; 79 scudo::uptr LastPageReported = 0; 80 }; 81 82 TEST(ScudoReleaseTest, FreePagesRangeTracker) { 83 // 'x' denotes a page to be released, '.' denotes a page to be kept around. 84 const char *TestCases[] = { 85 "", 86 ".", 87 "x", 88 "........", 89 "xxxxxxxxxxx", 90 "..............xxxxx", 91 "xxxxxxxxxxxxxxxxxx.....", 92 "......xxxxxxxx........", 93 "xxx..........xxxxxxxxxxxxxxx", 94 "......xxxx....xxxx........", 95 "xxx..........xxxxxxxx....xxxxxxx", 96 "x.x.x.x.x.x.x.x.x.x.x.x.", 97 ".x.x.x.x.x.x.x.x.x.x.x.x", 98 ".x.x.x.x.x.x.x.x.x.x.x.x.", 99 "x.x.x.x.x.x.x.x.x.x.x.x.x", 100 }; 101 typedef scudo::FreePagesRangeTracker<StringRangeRecorder> RangeTracker; 102 103 for (auto TestCase : TestCases) { 104 StringRangeRecorder Recorder; 105 RangeTracker Tracker(&Recorder); 106 for (scudo::uptr I = 0; TestCase[I] != 0; I++) 107 Tracker.processNextPage(TestCase[I] == 'x'); 108 Tracker.finish(); 109 // Strip trailing '.'-pages before comparing the results as they are not 110 // going to be reported to range_recorder anyway. 111 const char *LastX = strrchr(TestCase, 'x'); 112 std::string Expected(TestCase, 113 LastX == nullptr ? 0 : (LastX - TestCase + 1)); 114 EXPECT_STREQ(Expected.c_str(), Recorder.ReportedPages.c_str()); 115 } 116 } 117 118 class ReleasedPagesRecorder { 119 public: 120 std::set<scudo::uptr> ReportedPages; 121 122 void releasePageRangeToOS(scudo::uptr From, scudo::uptr To) { 123 const scudo::uptr PageSize = scudo::getPageSizeCached(); 124 for (scudo::uptr I = From; I < To; I += PageSize) 125 ReportedPages.insert(I); 126 } 127 }; 128 129 // Simplified version of a TransferBatch. 130 template <class SizeClassMap> struct FreeBatch { 131 static const scudo::u32 MaxCount = SizeClassMap::MaxNumCachedHint; 132 void clear() { Count = 0; } 133 void add(scudo::uptr P) { 134 DCHECK_LT(Count, MaxCount); 135 Batch[Count++] = P; 136 } 137 scudo::u32 getCount() const { return Count; } 138 scudo::uptr get(scudo::u32 I) const { 139 DCHECK_LE(I, Count); 140 return Batch[I]; 141 } 142 FreeBatch *Next; 143 144 private: 145 scudo::u32 Count; 146 scudo::uptr Batch[MaxCount]; 147 }; 148 149 template <class SizeClassMap> void testReleaseFreeMemoryToOS() { 150 typedef FreeBatch<SizeClassMap> Batch; 151 const scudo::uptr PagesCount = 1024; 152 const scudo::uptr PageSize = scudo::getPageSizeCached(); 153 std::mt19937 R; 154 scudo::u32 RandState = 42; 155 156 for (scudo::uptr I = 1; I <= SizeClassMap::LargestClassId; I++) { 157 const scudo::uptr BlockSize = SizeClassMap::getSizeByClassId(I); 158 const scudo::uptr MaxBlocks = PagesCount * PageSize / BlockSize; 159 160 // Generate the random free list. 161 std::vector<scudo::uptr> FreeArray; 162 bool InFreeRange = false; 163 scudo::uptr CurrentRangeEnd = 0; 164 for (scudo::uptr I = 0; I < MaxBlocks; I++) { 165 if (I == CurrentRangeEnd) { 166 InFreeRange = (scudo::getRandomU32(&RandState) & 1U) == 1; 167 CurrentRangeEnd += (scudo::getRandomU32(&RandState) & 0x7f) + 1; 168 } 169 if (InFreeRange) 170 FreeArray.push_back(I * BlockSize); 171 } 172 if (FreeArray.empty()) 173 continue; 174 // Shuffle the array to ensure that the order is irrelevant. 175 std::shuffle(FreeArray.begin(), FreeArray.end(), R); 176 177 // Build the FreeList from the FreeArray. 178 scudo::SinglyLinkedList<Batch> FreeList; 179 FreeList.clear(); 180 Batch *CurrentBatch = nullptr; 181 for (auto const &Block : FreeArray) { 182 if (!CurrentBatch) { 183 CurrentBatch = new Batch; 184 CurrentBatch->clear(); 185 FreeList.push_back(CurrentBatch); 186 } 187 CurrentBatch->add(Block); 188 if (CurrentBatch->getCount() == Batch::MaxCount) 189 CurrentBatch = nullptr; 190 } 191 192 // Release the memory. 193 ReleasedPagesRecorder Recorder; 194 releaseFreeMemoryToOS(FreeList, 0, MaxBlocks * BlockSize, 1U, BlockSize, 195 &Recorder); 196 197 // Verify that there are no released pages touched by used chunks and all 198 // ranges of free chunks big enough to contain the entire memory pages had 199 // these pages released. 200 scudo::uptr VerifiedReleasedPages = 0; 201 std::set<scudo::uptr> FreeBlocks(FreeArray.begin(), FreeArray.end()); 202 203 scudo::uptr CurrentBlock = 0; 204 InFreeRange = false; 205 scudo::uptr CurrentFreeRangeStart = 0; 206 for (scudo::uptr I = 0; I < MaxBlocks; I++) { 207 const bool IsFreeBlock = 208 FreeBlocks.find(CurrentBlock) != FreeBlocks.end(); 209 if (IsFreeBlock) { 210 if (!InFreeRange) { 211 InFreeRange = true; 212 CurrentFreeRangeStart = CurrentBlock; 213 } 214 } else { 215 // Verify that this used chunk does not touch any released page. 216 const scudo::uptr StartPage = CurrentBlock / PageSize; 217 const scudo::uptr EndPage = (CurrentBlock + BlockSize - 1) / PageSize; 218 for (scudo::uptr J = StartPage; J <= EndPage; J++) { 219 const bool PageReleased = Recorder.ReportedPages.find(J * PageSize) != 220 Recorder.ReportedPages.end(); 221 EXPECT_EQ(false, PageReleased); 222 } 223 224 if (InFreeRange) { 225 InFreeRange = false; 226 // Verify that all entire memory pages covered by this range of free 227 // chunks were released. 228 scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize); 229 while (P + PageSize <= CurrentBlock) { 230 const bool PageReleased = 231 Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end(); 232 EXPECT_EQ(true, PageReleased); 233 VerifiedReleasedPages++; 234 P += PageSize; 235 } 236 } 237 } 238 239 CurrentBlock += BlockSize; 240 } 241 242 if (InFreeRange) { 243 scudo::uptr P = scudo::roundUpTo(CurrentFreeRangeStart, PageSize); 244 const scudo::uptr EndPage = 245 scudo::roundUpTo(MaxBlocks * BlockSize, PageSize); 246 while (P + PageSize <= EndPage) { 247 const bool PageReleased = 248 Recorder.ReportedPages.find(P) != Recorder.ReportedPages.end(); 249 EXPECT_EQ(true, PageReleased); 250 VerifiedReleasedPages++; 251 P += PageSize; 252 } 253 } 254 255 EXPECT_EQ(Recorder.ReportedPages.size(), VerifiedReleasedPages); 256 257 while (!FreeList.empty()) { 258 CurrentBatch = FreeList.front(); 259 FreeList.pop_front(); 260 delete CurrentBatch; 261 } 262 } 263 } 264 265 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSDefault) { 266 testReleaseFreeMemoryToOS<scudo::DefaultSizeClassMap>(); 267 } 268 269 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSAndroid) { 270 testReleaseFreeMemoryToOS<scudo::AndroidSizeClassMap>(); 271 } 272 273 TEST(ScudoReleaseTest, ReleaseFreeMemoryToOSSvelte) { 274 testReleaseFreeMemoryToOS<scudo::SvelteSizeClassMap>(); 275 } 276