diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -267,6 +267,17 @@ bool UnlockRequired; auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired); Block = TSD->Cache.allocate(ClassId); + // If the allocation failed, the most likely reason with a 64-bit primary + // is the region being full. In that event, retry once using the + // immediately larger class (except if the failing class was already the + // largest). This will waste some memory but will allow the application to + // not fail. + if (SCUDO_ANDROID) { + if (UNLIKELY(!Block)) { + if (ClassId < SizeClassMap::LargestClassId) + Block = TSD->Cache.allocate(++ClassId); + } + } if (UnlockRequired) TSD->unlock(); } else { diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp --- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp @@ -344,20 +344,21 @@ #endif } - struct DeathSizeClassConfig { static const scudo::uptr NumBits = 1; static const scudo::uptr MinSizeLog = 10; static const scudo::uptr MidSizeLog = 10; - static const scudo::uptr MaxSizeLog = 10; - static const scudo::u32 MaxNumCachedHint = 1; - static const scudo::uptr MaxBytesCachedLog = 10; + static const scudo::uptr MaxSizeLog = 11; + static const scudo::u32 MaxNumCachedHint = 4; + static const scudo::uptr MaxBytesCachedLog = 12; }; +static const scudo::uptr DeathRegionSizeLog = 20U; struct DeathConfig { - // Tiny allocator, its Primary only serves chunks of 1024 bytes. + // Tiny allocator, its Primary only serves chunks of two sizes. using DeathSizeClassMap = scudo::FixedSizeClassMap; - typedef scudo::SizeClassAllocator64 Primary; + typedef scudo::SizeClassAllocator64 + Primary; typedef scudo::MapAllocator Secondary; template using TSDRegistryT = scudo::TSDRegistrySharedT; }; @@ -415,3 +416,39 @@ Allocator->releaseToOS(); } + +// Verify that when a region gets full, Android will still manage to +// fulfill the allocation through a larger size class. +TEST(ScudoCombinedTest, FullRegion) { + using AllocatorT = scudo::Allocator; + auto Deleter = [](AllocatorT *A) { + A->unmapTestOnly(); + delete A; + }; + std::unique_ptr Allocator(new AllocatorT, + Deleter); + Allocator->reset(); + + const scudo::uptr Size = 1000U; + const scudo::uptr MaxNumberOfChunks = + (1U << DeathRegionSizeLog) / + DeathConfig::DeathSizeClassMap::getSizeByClassId(1U); + void *P; + std::vector V; + scudo::uptr FailedAllocationsCount = 0; + for (scudo::uptr I = 0; I <= MaxNumberOfChunks; I++) { + P = Allocator->allocate(Size, Origin); + if (!P) + FailedAllocationsCount++; + else + V.push_back(P); + } + while (!V.empty()) { + Allocator->deallocate(V.back(), Origin); + V.pop_back(); + } + if (SCUDO_ANDROID) + EXPECT_EQ(FailedAllocationsCount, 0U); + else + EXPECT_GT(FailedAllocationsCount, 0U); +}