589 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "memtag.h"10#include "tests/scudo_unit_test.h"11 12#include "allocator_config.h"13#include "allocator_config_wrapper.h"14#include "secondary.h"15 16#include <string.h>17 18#include <algorithm>19#include <condition_variable>20#include <memory>21#include <mutex>22#include <random>23#include <thread>24#include <vector>25 26// Get this once to use through-out the tests.27const scudo::uptr PageSize = scudo::getPageSizeCached();28 29template <typename Config> static scudo::Options getOptionsForConfig() {30 if (!scudo::systemSupportsMemoryTagging())31 return {};32 scudo::AtomicOptions AO;33 AO.set(scudo::OptionBit::UseMemoryTagging);34 return AO.load();35}36 37template <class Config> struct AllocatorInfoType {38 std::unique_ptr<scudo::MapAllocator<scudo::SecondaryConfig<Config>>>39 Allocator;40 scudo::GlobalStats GlobalStats;41 scudo::Options Options;42 43 AllocatorInfoType(scudo::s32 ReleaseToOsInterval) {44 using SecondaryT = scudo::MapAllocator<scudo::SecondaryConfig<Config>>;45 Options = getOptionsForConfig<scudo::SecondaryConfig<Config>>();46 GlobalStats.init();47 Allocator.reset(new SecondaryT);48 Allocator->init(&GlobalStats, ReleaseToOsInterval);49 }50 51 AllocatorInfoType() : AllocatorInfoType(-1) {}52 53 ~AllocatorInfoType() {54 if (Allocator == nullptr) {55 return;56 }57 58 if (TEST_HAS_FAILURE) {59 // Print all of the stats if the test fails.60 scudo::ScopedString Str;61 Allocator->getStats(&Str);62 Str.output();63 }64 65 Allocator->unmapTestOnly();66 }67};68 69struct TestNoCacheConfig {70 static const bool MaySupportMemoryTagging = false;71 template <typename> using TSDRegistryT = void;72 template <typename> using PrimaryT = void;73 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;74 75 struct Secondary {76 template <typename Config>77 using CacheT = scudo::MapAllocatorNoCache<Config>;78 };79};80 81struct TestNoCacheNoGuardPageConfig {82 static const bool MaySupportMemoryTagging = false;83 template <typename> using TSDRegistryT = void;84 template <typename> using PrimaryT = void;85 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;86 87 struct Secondary {88 template <typename Config>89 using CacheT = scudo::MapAllocatorNoCache<Config>;90 static const bool EnableGuardPages = false;91 };92};93 94struct TestCacheConfig {95 static const bool MaySupportMemoryTagging = false;96 template <typename> using TSDRegistryT = void;97 template <typename> using PrimaryT = void;98 template <typename> using SecondaryT = void;99 100 struct Secondary {101 struct Cache {102 static const scudo::u32 EntriesArraySize = 128U;103 static const scudo::u32 QuarantineSize = 0U;104 static const scudo::u32 DefaultMaxEntriesCount = 64U;105 static const scudo::uptr DefaultMaxEntrySize = 1UL << 20;106 static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;107 static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;108 };109 110 template <typename Config> using CacheT = scudo::MapAllocatorCache<Config>;111 };112};113 114struct TestCacheNoGuardPageConfig {115 static const bool MaySupportMemoryTagging = false;116 template <typename> using TSDRegistryT = void;117 template <typename> using PrimaryT = void;118 template <typename> using SecondaryT = void;119 120 struct Secondary {121 struct Cache {122 static const scudo::u32 EntriesArraySize = 128U;123 static const scudo::u32 QuarantineSize = 0U;124 static const scudo::u32 DefaultMaxEntriesCount = 64U;125 static const scudo::uptr DefaultMaxEntrySize = 1UL << 20;126 static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;127 static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;128 };129 130 template <typename Config> using CacheT = scudo::MapAllocatorCache<Config>;131 static const bool EnableGuardPages = false;132 };133};134 135template <typename Config> static void testBasic() {136 using SecondaryT = scudo::MapAllocator<scudo::SecondaryConfig<Config>>;137 AllocatorInfoType<Config> Info;138 139 const scudo::uptr Size = 1U << 16;140 void *P = Info.Allocator->allocate(Info.Options, Size);141 EXPECT_NE(P, nullptr);142 memset(P, 'A', Size);143 EXPECT_GE(SecondaryT::getBlockSize(P), Size);144 Info.Allocator->deallocate(Info.Options, P);145 146 // If the Secondary can't cache that pointer, it will be unmapped.147 if (!Info.Allocator->canCache(Size)) {148 EXPECT_DEATH(149 {150 // Repeat few time to avoid missing crash if it's mmaped by unrelated151 // code.152 for (int i = 0; i < 10; ++i) {153 P = Info.Allocator->allocate(Info.Options, Size);154 Info.Allocator->deallocate(Info.Options, P);155 memset(P, 'A', Size);156 }157 },158 "");159 }160 161 const scudo::uptr Align = 1U << 16;162 P = Info.Allocator->allocate(Info.Options, Size + Align, Align);163 EXPECT_NE(P, nullptr);164 void *AlignedP = reinterpret_cast<void *>(165 scudo::roundUp(reinterpret_cast<scudo::uptr>(P), Align));166 memset(AlignedP, 'A', Size);167 Info.Allocator->deallocate(Info.Options, P);168 169 std::vector<void *> V;170 for (scudo::uptr I = 0; I < 32U; I++)171 V.push_back(Info.Allocator->allocate(Info.Options, Size));172 std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));173 while (!V.empty()) {174 Info.Allocator->deallocate(Info.Options, V.back());175 V.pop_back();176 }177}178 179TEST(ScudoSecondaryTest, Basic) {180 testBasic<TestNoCacheConfig>();181 testBasic<TestNoCacheNoGuardPageConfig>();182 testBasic<TestCacheConfig>();183 testBasic<TestCacheNoGuardPageConfig>();184 testBasic<scudo::DefaultConfig>();185}186 187// This exercises a variety of combinations of size and alignment for the188// MapAllocator. The size computation done here mimic the ones done by the189// combined allocator.190template <typename Config> void testAllocatorCombinations() {191 AllocatorInfoType<Config> Info;192 193 constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);194 constexpr scudo::uptr HeaderSize = scudo::roundUp(8, MinAlign);195 for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {196 for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;197 AlignLog++) {198 const scudo::uptr Align = 1U << AlignLog;199 for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {200 if ((1LL << SizeLog) + Delta <= 0)201 continue;202 const scudo::uptr UserSize = scudo::roundUp(203 static_cast<scudo::uptr>((1LL << SizeLog) + Delta), MinAlign);204 const scudo::uptr Size =205 HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);206 void *P = Info.Allocator->allocate(Info.Options, Size, Align);207 EXPECT_NE(P, nullptr);208 void *AlignedP = reinterpret_cast<void *>(209 scudo::roundUp(reinterpret_cast<scudo::uptr>(P), Align));210 memset(AlignedP, 0xff, UserSize);211 Info.Allocator->deallocate(Info.Options, P);212 }213 }214 }215}216 217TEST(ScudoSecondaryTest, AllocatorCombinations) {218 testAllocatorCombinations<TestNoCacheConfig>();219 testAllocatorCombinations<TestNoCacheNoGuardPageConfig>();220}221 222template <typename Config> void testAllocatorIterate() {223 AllocatorInfoType<Config> Info;224 225 std::vector<void *> V;226 for (scudo::uptr I = 0; I < 32U; I++)227 V.push_back(Info.Allocator->allocate(228 Info.Options,229 (static_cast<scudo::uptr>(std::rand()) % 16U) * PageSize));230 auto Lambda = [&V](scudo::uptr Block) {231 EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),232 V.end());233 };234 Info.Allocator->disable();235 Info.Allocator->iterateOverBlocks(Lambda);236 Info.Allocator->enable();237 while (!V.empty()) {238 Info.Allocator->deallocate(Info.Options, V.back());239 V.pop_back();240 }241}242 243TEST(ScudoSecondaryTest, AllocatorIterate) {244 testAllocatorIterate<TestNoCacheConfig>();245 testAllocatorIterate<TestNoCacheNoGuardPageConfig>();246}247 248template <typename Config> void testAllocatorWithReleaseThreadsRace() {249 AllocatorInfoType<Config> Info(/*ReleaseToOsInterval=*/0);250 251 std::mutex Mutex;252 std::condition_variable Cv;253 bool Ready = false;254 255 std::thread Threads[16];256 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)257 Threads[I] = std::thread([&Mutex, &Cv, &Ready, &Info]() {258 std::vector<void *> V;259 {260 std::unique_lock<std::mutex> Lock(Mutex);261 while (!Ready)262 Cv.wait(Lock);263 }264 for (scudo::uptr I = 0; I < 128U; I++) {265 // Deallocate 75% of the blocks.266 const bool Deallocate = (std::rand() & 3) != 0;267 void *P = Info.Allocator->allocate(268 Info.Options,269 (static_cast<scudo::uptr>(std::rand()) % 16U) * PageSize);270 if (Deallocate)271 Info.Allocator->deallocate(Info.Options, P);272 else273 V.push_back(P);274 }275 while (!V.empty()) {276 Info.Allocator->deallocate(Info.Options, V.back());277 V.pop_back();278 }279 });280 281 {282 std::unique_lock<std::mutex> Lock(Mutex);283 Ready = true;284 Cv.notify_all();285 }286 for (auto &T : Threads)287 T.join();288}289 290TEST(ScudoSecondaryTest, AllocatorWithReleaseThreadsRace) {291 testAllocatorWithReleaseThreadsRace<TestNoCacheConfig>();292 testAllocatorWithReleaseThreadsRace<TestNoCacheNoGuardPageConfig>();293}294 295template <typename Config>296void testGetMappedSize(scudo::uptr Size, scudo::uptr *mapped,297 scudo::uptr *guard_page_size) {298 AllocatorInfoType<Config> Info;299 300 scudo::uptr Stats[scudo::StatCount] = {};301 Info.GlobalStats.get(Stats);302 *mapped = Stats[scudo::StatMapped];303 Stats[scudo::StatMapped] = 0;304 305 // Make sure the allocation is aligned to a page boundary so that the checks306 // in the tests can avoid problems due to allocations having different307 // alignments.308 void *Ptr = Info.Allocator->allocate(Info.Options, Size, PageSize);309 EXPECT_NE(Ptr, nullptr);310 311 Info.GlobalStats.get(Stats);312 EXPECT_GE(Stats[scudo::StatMapped], *mapped);313 *mapped = Stats[scudo::StatMapped] - *mapped;314 315 Info.Allocator->deallocate(Info.Options, Ptr);316 317 *guard_page_size = Info.Allocator->getGuardPageSize();318}319 320TEST(ScudoSecondaryTest, VerifyGuardPageOption) {321 static scudo::uptr AllocSize = 1000 * PageSize;322 323 // Verify that a config with guard pages enabled:324 // - Non-zero sized guard page325 // - Mapped in at least the size of the allocation plus 2 * guard page size326 scudo::uptr guard_mapped = 0;327 scudo::uptr guard_page_size = 0;328 testGetMappedSize<TestNoCacheConfig>(AllocSize, &guard_mapped,329 &guard_page_size);330 EXPECT_GT(guard_page_size, 0U);331 EXPECT_GE(guard_mapped, AllocSize + 2 * guard_page_size);332 333 // Verify that a config with guard pages disabled:334 // - Zero sized guard page335 // - The total mapped in is greater than the allocation size336 scudo::uptr no_guard_mapped = 0;337 scudo::uptr no_guard_page_size = 0;338 testGetMappedSize<TestNoCacheNoGuardPageConfig>(AllocSize, &no_guard_mapped,339 &no_guard_page_size);340 EXPECT_EQ(no_guard_page_size, 0U);341 EXPECT_GE(no_guard_mapped, AllocSize);342 343 // Verify that a guard page config mapped in at least twice the size of344 // their guard page when compared to a no guard page config.345 EXPECT_GE(guard_mapped, no_guard_mapped + guard_page_size * 2);346}347 348// Value written to cache entries that are unmapped.349static scudo::u32 UnmappedMarker = 0xDEADBEEF;350 351template <class Config> struct CacheInfoType {352 static void addMarkerToMapCallback(scudo::MemMapT &MemMap) {353 // When a cache entry is unmaped, don't unmap it write a special marker354 // to indicate the cache entry was released. The real unmap will happen355 // in the destructor. It is assumed that all of these maps will be in356 // the MemMaps vector.357 scudo::u32 *Ptr = reinterpret_cast<scudo::u32 *>(MemMap.getBase());358 *Ptr = UnmappedMarker;359 }360 361 using SecondaryConfig = scudo::SecondaryConfig<TestCacheConfig>;362 using CacheConfig = SecondaryConfig::CacheConfig;363 using CacheT = scudo::MapAllocatorCache<CacheConfig, addMarkerToMapCallback>;364 scudo::Options Options = getOptionsForConfig<SecondaryConfig>();365 std::unique_ptr<CacheT> Cache = std::make_unique<CacheT>();366 std::vector<scudo::MemMapT> MemMaps;367 // The current test allocation size is set to the maximum368 // cache entry size369 static constexpr scudo::uptr TestAllocSize =370 CacheConfig::getDefaultMaxEntrySize();371 372 CacheInfoType() { Cache->init(/*ReleaseToOsInterval=*/-1); }373 374 ~CacheInfoType() {375 if (Cache == nullptr) {376 return;377 }378 379 // Clean up MemMaps380 for (auto &MemMap : MemMaps)381 MemMap.unmap();382 }383 384 scudo::MemMapT allocate(scudo::uptr Size) {385 scudo::uptr MapSize = scudo::roundUp(Size, PageSize);386 scudo::ReservedMemoryT ReservedMemory;387 CHECK(ReservedMemory.create(0U, MapSize, nullptr, MAP_ALLOWNOMEM));388 389 scudo::MemMapT MemMap = ReservedMemory.dispatch(390 ReservedMemory.getBase(), ReservedMemory.getCapacity());391 MemMap.remap(MemMap.getBase(), MemMap.getCapacity(), "scudo:test",392 MAP_RESIZABLE | MAP_ALLOWNOMEM);393 return MemMap;394 }395 396 void fillCacheWithSameSizeBlocks(scudo::uptr NumEntries, scudo::uptr Size) {397 for (scudo::uptr I = 0; I < NumEntries; I++) {398 MemMaps.emplace_back(allocate(Size));399 auto &MemMap = MemMaps[I];400 Cache->store(Options, MemMap.getBase(), MemMap.getCapacity(),401 MemMap.getBase(), MemMap);402 }403 }404 405 void storeMemMap(scudo::MemMapT &MemMap) {406 Cache->store(Options, MemMap.getBase(), MemMap.getCapacity(),407 MemMap.getBase(), MemMap);408 }409};410 411TEST(ScudoSecondaryTest, AllocatorCacheEntryOrder) {412 CacheInfoType<TestCacheConfig> Info;413 using CacheConfig = CacheInfoType<TestCacheConfig>::CacheConfig;414 415 Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount,416 CacheConfig::getEntriesArraySize());417 418 Info.fillCacheWithSameSizeBlocks(CacheConfig::getEntriesArraySize(),419 Info.TestAllocSize);420 421 // Retrieval order should be the inverse of insertion order422 for (scudo::uptr I = CacheConfig::getEntriesArraySize(); I > 0; I--) {423 scudo::uptr EntryHeaderPos;424 scudo::CachedBlock Entry = Info.Cache->retrieve(425 0, Info.TestAllocSize, PageSize, 0, EntryHeaderPos);426 EXPECT_EQ(Entry.MemMap.getBase(), Info.MemMaps[I - 1].getBase());427 }428}429 430TEST(ScudoSecondaryTest, AllocatorCachePartialChunkHeuristicRetrievalTest) {431 CacheInfoType<TestCacheConfig> Info;432 433 const scudo::uptr FragmentedPages =434 1 + scudo::CachedBlock::MaxReleasedCachePages;435 scudo::uptr EntryHeaderPos;436 scudo::CachedBlock Entry;437 scudo::MemMapT MemMap = Info.allocate(PageSize + FragmentedPages * PageSize);438 Info.Cache->store(Info.Options, MemMap.getBase(), MemMap.getCapacity(),439 MemMap.getBase(), MemMap);440 441 // FragmentedPages > MaxAllowedFragmentedPages so PageSize442 // cannot be retrieved from the cache443 Entry = Info.Cache->retrieve(/*MaxAllowedFragmentedPages=*/0, PageSize,444 PageSize, 0, EntryHeaderPos);445 EXPECT_FALSE(Entry.isValid());446 447 // FragmentedPages == MaxAllowedFragmentedPages so PageSize448 // can be retrieved from the cache449 Entry = Info.Cache->retrieve(FragmentedPages, PageSize, PageSize, 0,450 EntryHeaderPos);451 EXPECT_TRUE(Entry.isValid());452 453 MemMap.unmap();454}455 456TEST(ScudoSecondaryTest, AllocatorCacheMemoryLeakTest) {457 CacheInfoType<TestCacheConfig> Info;458 using CacheConfig = CacheInfoType<TestCacheConfig>::CacheConfig;459 460 // Fill the cache above MaxEntriesCount to force an eviction461 // The first cache entry should be evicted (because it is the oldest)462 // due to the maximum number of entries being reached463 Info.fillCacheWithSameSizeBlocks(CacheConfig::getDefaultMaxEntriesCount() + 1,464 Info.TestAllocSize);465 466 std::vector<scudo::CachedBlock> RetrievedEntries;467 468 // First MemMap should be evicted from cache because it was the first469 // inserted into the cache470 for (scudo::uptr I = CacheConfig::getDefaultMaxEntriesCount(); I > 0; I--) {471 scudo::uptr EntryHeaderPos;472 RetrievedEntries.push_back(Info.Cache->retrieve(473 0, Info.TestAllocSize, PageSize, 0, EntryHeaderPos));474 EXPECT_EQ(Info.MemMaps[I].getBase(),475 RetrievedEntries.back().MemMap.getBase());476 }477 478 // Evicted entry should be marked due to unmap callback479 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[0].getBase()),480 UnmappedMarker);481}482 483TEST(ScudoSecondaryTest, AllocatorCacheOptions) {484 CacheInfoType<TestCacheConfig> Info;485 486 // Attempt to set a maximum number of entries higher than the array size.487 EXPECT_TRUE(488 Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));489 490 // Attempt to set an invalid (negative) number of entries491 EXPECT_FALSE(Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount, -1));492 493 // Various valid combinations.494 EXPECT_TRUE(Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount, 4U));495 EXPECT_TRUE(496 Info.Cache->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));497 EXPECT_TRUE(Info.Cache->canCache(1UL << 18));498 EXPECT_TRUE(499 Info.Cache->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));500 EXPECT_FALSE(Info.Cache->canCache(1UL << 18));501 EXPECT_TRUE(Info.Cache->canCache(1UL << 16));502 EXPECT_TRUE(Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount, 0U));503 EXPECT_FALSE(Info.Cache->canCache(1UL << 16));504 EXPECT_TRUE(Info.Cache->setOption(scudo::Option::MaxCacheEntriesCount, 4U));505 EXPECT_TRUE(506 Info.Cache->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));507 EXPECT_TRUE(Info.Cache->canCache(1UL << 16));508}509 510TEST(ScudoSecondaryTest, ReleaseOlderThanAllEntries) {511 CacheInfoType<TestCacheConfig> Info;512 using CacheConfig = CacheInfoType<TestCacheConfig>::CacheConfig;513 514 Info.Cache->releaseOlderThanTestOnly(UINT64_MAX);515 516 Info.fillCacheWithSameSizeBlocks(CacheConfig::getDefaultMaxEntriesCount(),517 1024);518 for (size_t I = 0; I < Info.MemMaps.size(); I++) {519 // Set the first u32 value to a non-zero value.520 *reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()) = 10;521 }522 523 Info.Cache->releaseOlderThanTestOnly(UINT64_MAX);524 525 EXPECT_EQ(Info.MemMaps.size(), CacheConfig::getDefaultMaxEntriesCount());526 for (size_t I = 0; I < Info.MemMaps.size(); I++) {527 // All released maps will now be zero.528 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);529 }530}531 532// This test assumes that the timestamp comes from getMonotonicFast.533TEST(ScudoSecondaryTest, ReleaseOlderThanGroups) {534 CacheInfoType<TestCacheConfig> Info;535 536 // Disable the release interval so we can do tests the releaseOlderThan537 // function.538 Info.Cache->setOption(scudo::Option::ReleaseInterval, -1);539 540 // Create all of the maps we are going to use.541 for (size_t I = 0; I < 6; I++) {542 Info.MemMaps.emplace_back(Info.allocate(1024));543 // Set the first u32 value to a non-zero value.544 *reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()) = 10;545 }546 547 // Create three groups of entries at three different intervals.548 Info.storeMemMap(Info.MemMaps[0]);549 Info.storeMemMap(Info.MemMaps[1]);550 scudo::u64 FirstTime = scudo::getMonotonicTimeFast();551 552 // Need to make sure the next set of entries are stamped with a newer time.553 while (scudo::getMonotonicTimeFast() <= FirstTime)554 ;555 556 Info.storeMemMap(Info.MemMaps[2]);557 Info.storeMemMap(Info.MemMaps[3]);558 scudo::u64 SecondTime = scudo::getMonotonicTimeFast();559 560 // Need to make sure the next set of entries are stamped with a newer time.561 while (scudo::getMonotonicTimeFast() <= SecondTime)562 ;563 564 Info.storeMemMap(Info.MemMaps[4]);565 Info.storeMemMap(Info.MemMaps[5]);566 scudo::u64 ThirdTime = scudo::getMonotonicTimeFast();567 568 Info.Cache->releaseOlderThanTestOnly(FirstTime);569 for (size_t I = 0; I < 2; I++) {570 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);571 }572 for (size_t I = 2; I < 6; I++) {573 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 10U);574 }575 576 Info.Cache->releaseOlderThanTestOnly(SecondTime);577 for (size_t I = 0; I < 4; I++) {578 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);579 }580 for (size_t I = 4; I < 6; I++) {581 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 10U);582 }583 584 Info.Cache->releaseOlderThanTestOnly(ThirdTime);585 for (size_t I = 0; I < 6; I++) {586 EXPECT_EQ(*reinterpret_cast<scudo::u32 *>(Info.MemMaps[I].getBase()), 0U);587 }588}589