Force particular malloc tests to use _FORTIFY_SOURCE=2 We plan to make _FORTIFY_SOURCE=3 global on device. Some malloc tests use malloc_usable_size so we separated them out and forced them to use _FORTIFY_SOURCE=2. Bug: 291762537 Test: Presubmit plus I ran the below commands - atest malloc - atest malloc_iterate - atest MallocHooksTest Change-Id: Ie9e0cbc5f726c583c1bbdcae8105b8256b9c8887
diff --git a/libc/malloc_hooks/tests/malloc_hooks_tests.cpp b/libc/malloc_hooks/tests/malloc_hooks_tests.cpp index 39cd645..ff94755 100644 --- a/libc/malloc_hooks/tests/malloc_hooks_tests.cpp +++ b/libc/malloc_hooks/tests/malloc_hooks_tests.cpp
@@ -26,6 +26,13 @@ * SUCH DAMAGE. */ +// (b/291762537): This code uses malloc_usable_size(), and thus can't be +// built with _FORTIFY_SOURCE=3. +#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE == 3 +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 2 +#endif + #include <fcntl.h> #include <malloc.h> #include <stdlib.h>
diff --git a/tests/Android.bp b/tests/Android.bp index 34229ce..2f44553 100644 --- a/tests/Android.bp +++ b/tests/Android.bp
@@ -432,6 +432,7 @@ "locale_test.cpp", "malloc_iterate_test.cpp", "malloc_test.cpp", + "malloc_test_with_usable_size.cpp", "math_test.cpp", "membarrier_test.cpp", "memtag_globals_test.cpp",
diff --git a/tests/NOTICE b/tests/NOTICE index de95698..accde44 100644 --- a/tests/NOTICE +++ b/tests/NOTICE
@@ -482,3 +482,31 @@ ------------------------------------------------------------------- +Copyright (C) 2025 The Android Open Source Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +------------------------------------------------------------------- +
diff --git a/tests/malloc_iterate_test.cpp b/tests/malloc_iterate_test.cpp index 297f637..4146c57 100644 --- a/tests/malloc_iterate_test.cpp +++ b/tests/malloc_iterate_test.cpp
@@ -14,6 +14,13 @@ * limitations under the License. */ +// (b/291762537): This code uses malloc_usable_size(), and thus can't be +// built with _FORTIFY_SOURCE=3. +#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE == 3 +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 2 +#endif + #include <gtest/gtest.h> #if defined(__BIONIC__)
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp index db814dc..5c6db4b 100644 --- a/tests/malloc_test.cpp +++ b/tests/malloc_test.cpp
@@ -72,14 +72,6 @@ #endif -TEST(malloc, malloc_std) { - // Simple malloc test. - void *ptr = malloc(100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - free(ptr); -} - TEST(malloc, malloc_overflow) { SKIP_WITH_HWASAN; errno = 0; @@ -87,18 +79,6 @@ ASSERT_ERRNO(ENOMEM); } -TEST(malloc, calloc_std) { - // Simple calloc test. - size_t alloc_len = 100; - char *ptr = (char *)calloc(1, alloc_len); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(alloc_len, malloc_usable_size(ptr)); - for (size_t i = 0; i < alloc_len; i++) { - ASSERT_EQ(0, ptr[i]); - } - free(ptr); -} - TEST(malloc, calloc_mem_init_disabled) { #if defined(__BIONIC__) // calloc should still zero memory if mem-init is disabled. @@ -140,21 +120,6 @@ ASSERT_ERRNO(ENOMEM); } -TEST(malloc, memalign_multiple) { - SKIP_WITH_HWASAN << "hwasan requires power of 2 alignment"; - // Memalign test where the alignment is any value. - for (size_t i = 0; i <= 12; i++) { - for (size_t alignment = 1 << i; alignment < (1U << (i+1)); alignment++) { - char *ptr = reinterpret_cast<char*>(memalign(alignment, 100)); - ASSERT_TRUE(ptr != nullptr) << "Failed at alignment " << alignment; - ASSERT_LE(100U, malloc_usable_size(ptr)) << "Failed at alignment " << alignment; - ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(ptr) % ((1U << i))) - << "Failed at alignment " << alignment; - free(ptr); - } - } -} - TEST(malloc, memalign_overflow) { SKIP_WITH_HWASAN; ASSERT_EQ(nullptr, memalign(4096, SIZE_MAX)); @@ -170,179 +135,6 @@ } } -TEST(malloc, memalign_realloc) { - // Memalign and then realloc the pointer a couple of times. - for (size_t alignment = 1; alignment <= 4096; alignment <<= 1) { - char *ptr = (char*)memalign(alignment, 100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - ASSERT_EQ(0U, (intptr_t)ptr % alignment); - memset(ptr, 0x23, 100); - - ptr = (char*)realloc(ptr, 200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - ASSERT_TRUE(ptr != nullptr); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(0x23, ptr[i]); - } - memset(ptr, 0x45, 200); - - ptr = (char*)realloc(ptr, 300); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(300U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 200; i++) { - ASSERT_EQ(0x45, ptr[i]); - } - memset(ptr, 0x67, 300); - - ptr = (char*)realloc(ptr, 250); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(250U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 250; i++) { - ASSERT_EQ(0x67, ptr[i]); - } - free(ptr); - } -} - -TEST(malloc, malloc_realloc_larger) { - // Realloc to a larger size, malloc is used for the original allocation. - char *ptr = (char *)malloc(100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - memset(ptr, 67, 100); - - ptr = (char *)realloc(ptr, 200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(67, ptr[i]); - } - free(ptr); -} - -TEST(malloc, malloc_realloc_smaller) { - // Realloc to a smaller size, malloc is used for the original allocation. - char *ptr = (char *)malloc(200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - memset(ptr, 67, 200); - - ptr = (char *)realloc(ptr, 100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(67, ptr[i]); - } - free(ptr); -} - -TEST(malloc, malloc_multiple_realloc) { - // Multiple reallocs, malloc is used for the original allocation. - char *ptr = (char *)malloc(200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - memset(ptr, 0x23, 200); - - ptr = (char *)realloc(ptr, 100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(0x23, ptr[i]); - } - - ptr = (char*)realloc(ptr, 50); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(50U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 50; i++) { - ASSERT_EQ(0x23, ptr[i]); - } - - ptr = (char*)realloc(ptr, 150); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(150U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 50; i++) { - ASSERT_EQ(0x23, ptr[i]); - } - memset(ptr, 0x23, 150); - - ptr = (char*)realloc(ptr, 425); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(425U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 150; i++) { - ASSERT_EQ(0x23, ptr[i]); - } - free(ptr); -} - -TEST(malloc, calloc_realloc_larger) { - // Realloc to a larger size, calloc is used for the original allocation. - char *ptr = (char *)calloc(1, 100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - - ptr = (char *)realloc(ptr, 200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(0, ptr[i]); - } - free(ptr); -} - -TEST(malloc, calloc_realloc_smaller) { - // Realloc to a smaller size, calloc is used for the original allocation. - char *ptr = (char *)calloc(1, 200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - - ptr = (char *)realloc(ptr, 100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(0, ptr[i]); - } - free(ptr); -} - -TEST(malloc, calloc_multiple_realloc) { - // Multiple reallocs, calloc is used for the original allocation. - char *ptr = (char *)calloc(1, 200); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(200U, malloc_usable_size(ptr)); - - ptr = (char *)realloc(ptr, 100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(100U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 100; i++) { - ASSERT_EQ(0, ptr[i]); - } - - ptr = (char*)realloc(ptr, 50); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(50U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 50; i++) { - ASSERT_EQ(0, ptr[i]); - } - - ptr = (char*)realloc(ptr, 150); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(150U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 50; i++) { - ASSERT_EQ(0, ptr[i]); - } - memset(ptr, 0, 150); - - ptr = (char*)realloc(ptr, 425); - ASSERT_TRUE(ptr != nullptr); - ASSERT_LE(425U, malloc_usable_size(ptr)); - for (size_t i = 0; i < 150; i++) { - ASSERT_EQ(0, ptr[i]); - } - free(ptr); -} - TEST(malloc, realloc_overflow) { SKIP_WITH_HWASAN; errno = 0; @@ -361,19 +153,6 @@ extern "C" void* valloc(size_t); #endif -TEST(malloc, pvalloc_std) { -#if defined(HAVE_DEPRECATED_MALLOC_FUNCS) - size_t pagesize = sysconf(_SC_PAGESIZE); - void* ptr = pvalloc(100); - ASSERT_TRUE(ptr != nullptr); - ASSERT_TRUE((reinterpret_cast<uintptr_t>(ptr) & (pagesize-1)) == 0); - ASSERT_LE(pagesize, malloc_usable_size(ptr)); - free(ptr); -#else - GTEST_SKIP() << "pvalloc not supported."; -#endif -} - TEST(malloc, pvalloc_overflow) { #if defined(HAVE_DEPRECATED_MALLOC_FUNCS) ASSERT_EQ(nullptr, pvalloc(SIZE_MAX)); @@ -538,25 +317,6 @@ #endif } -TEST(malloc, calloc_usable_size) { - for (size_t size = 1; size <= 2048; size++) { - void* pointer = malloc(size); - ASSERT_TRUE(pointer != nullptr); - memset(pointer, 0xeb, malloc_usable_size(pointer)); - free(pointer); - - // We should get a previous pointer that has been set to non-zero. - // If calloc does not zero out all of the data, this will fail. - uint8_t* zero_mem = reinterpret_cast<uint8_t*>(calloc(1, size)); - ASSERT_TRUE(pointer != nullptr); - size_t usable_size = malloc_usable_size(zero_mem); - for (size_t i = 0; i < usable_size; i++) { - ASSERT_EQ(0, zero_mem[i]) << "Failed at allocation size " << size << " at byte " << i; - } - free(zero_mem); - } -} - TEST(malloc, malloc_0) { void* p = malloc(0); ASSERT_TRUE(p != nullptr); @@ -808,132 +568,6 @@ #endif } -TEST(malloc, reallocarray) { -#if HAVE_REALLOCARRAY - void* p = reallocarray(nullptr, 2, 32); - ASSERT_TRUE(p != nullptr); - ASSERT_GE(malloc_usable_size(p), 64U); -#else - GTEST_SKIP() << "reallocarray not available"; -#endif -} - -TEST(malloc, mallinfo) { -#if defined(__BIONIC__) || defined(ANDROID_HOST_MUSL) - SKIP_WITH_HWASAN << "hwasan does not implement mallinfo"; - static size_t sizes[] = { - 8, 32, 128, 4096, 32768, 131072, 1024000, 10240000, 20480000, 300000000 - }; - - static constexpr size_t kMaxAllocs = 50; - - for (size_t size : sizes) { - // If some of these allocations are stuck in a thread cache, then keep - // looping until we make an allocation that changes the total size of the - // memory allocated. - // jemalloc implementations counts the thread cache allocations against - // total memory allocated. - void* ptrs[kMaxAllocs] = {}; - bool pass = false; - for (size_t i = 0; i < kMaxAllocs; i++) { - size_t allocated = mallinfo().uordblks; - ptrs[i] = malloc(size); - ASSERT_TRUE(ptrs[i] != nullptr); - size_t new_allocated = mallinfo().uordblks; - if (allocated != new_allocated) { - size_t usable_size = malloc_usable_size(ptrs[i]); - // Only check if the total got bigger by at least allocation size. - // Sometimes the mallinfo numbers can go backwards due to compaction - // and/or freeing of cached data. - if (new_allocated >= allocated + usable_size) { - pass = true; - break; - } - } - } - for (void* ptr : ptrs) { - free(ptr); - } - ASSERT_TRUE(pass) - << "For size " << size << " allocated bytes did not increase after " - << kMaxAllocs << " allocations."; - } -#else - GTEST_SKIP() << "glibc is broken"; -#endif -} - -TEST(malloc, mallinfo2) { -#if defined(__BIONIC__) || defined(ANDROID_HOST_MUSL) - SKIP_WITH_HWASAN << "hwasan does not implement mallinfo2"; - static size_t sizes[] = {8, 32, 128, 4096, 32768, 131072, 1024000, 10240000, 20480000, 300000000}; - - static constexpr size_t kMaxAllocs = 50; - - for (size_t size : sizes) { - // If some of these allocations are stuck in a thread cache, then keep - // looping until we make an allocation that changes the total size of the - // memory allocated. - // jemalloc implementations counts the thread cache allocations against - // total memory allocated. - void* ptrs[kMaxAllocs] = {}; - bool pass = false; - for (size_t i = 0; i < kMaxAllocs; i++) { - struct mallinfo info = mallinfo(); - struct mallinfo2 info2 = mallinfo2(); - // Verify that mallinfo and mallinfo2 are exactly the same. - ASSERT_EQ(static_cast<size_t>(info.arena), info2.arena); - ASSERT_EQ(static_cast<size_t>(info.ordblks), info2.ordblks); - ASSERT_EQ(static_cast<size_t>(info.smblks), info2.smblks); - ASSERT_EQ(static_cast<size_t>(info.hblks), info2.hblks); - ASSERT_EQ(static_cast<size_t>(info.hblkhd), info2.hblkhd); - ASSERT_EQ(static_cast<size_t>(info.usmblks), info2.usmblks); - ASSERT_EQ(static_cast<size_t>(info.fsmblks), info2.fsmblks); - ASSERT_EQ(static_cast<size_t>(info.uordblks), info2.uordblks); - ASSERT_EQ(static_cast<size_t>(info.fordblks), info2.fordblks); - ASSERT_EQ(static_cast<size_t>(info.keepcost), info2.keepcost); - - size_t allocated = info2.uordblks; - ptrs[i] = malloc(size); - ASSERT_TRUE(ptrs[i] != nullptr); - - info = mallinfo(); - info2 = mallinfo2(); - // Verify that mallinfo and mallinfo2 are exactly the same. - ASSERT_EQ(static_cast<size_t>(info.arena), info2.arena); - ASSERT_EQ(static_cast<size_t>(info.ordblks), info2.ordblks); - ASSERT_EQ(static_cast<size_t>(info.smblks), info2.smblks); - ASSERT_EQ(static_cast<size_t>(info.hblks), info2.hblks); - ASSERT_EQ(static_cast<size_t>(info.hblkhd), info2.hblkhd); - ASSERT_EQ(static_cast<size_t>(info.usmblks), info2.usmblks); - ASSERT_EQ(static_cast<size_t>(info.fsmblks), info2.fsmblks); - ASSERT_EQ(static_cast<size_t>(info.uordblks), info2.uordblks); - ASSERT_EQ(static_cast<size_t>(info.fordblks), info2.fordblks); - ASSERT_EQ(static_cast<size_t>(info.keepcost), info2.keepcost); - - size_t new_allocated = info2.uordblks; - if (allocated != new_allocated) { - size_t usable_size = malloc_usable_size(ptrs[i]); - // Only check if the total got bigger by at least allocation size. - // Sometimes the mallinfo2 numbers can go backwards due to compaction - // and/or freeing of cached data. - if (new_allocated >= allocated + usable_size) { - pass = true; - break; - } - } - } - for (void* ptr : ptrs) { - free(ptr); - } - ASSERT_TRUE(pass) << "For size " << size << " allocated bytes did not increase after " - << kMaxAllocs << " allocations."; - } -#else - GTEST_SKIP() << "glibc is broken"; -#endif -} - template <typename Type> void __attribute__((optnone)) VerifyAlignment(Type* floating) { size_t expected_alignment = alignof(Type); @@ -1065,69 +699,6 @@ AlignCheck(); } -// Jemalloc doesn't pass this test right now, so leave it as disabled. -TEST(malloc, DISABLED_alloc_after_fork) { - // Both of these need to be a power of 2. - static constexpr size_t kMinAllocationSize = 8; - static constexpr size_t kMaxAllocationSize = 2097152; - - static constexpr size_t kNumAllocatingThreads = 5; - static constexpr size_t kNumForkLoops = 100; - - std::atomic_bool stop; - - // Create threads that simply allocate and free different sizes. - std::vector<std::thread*> threads; - for (size_t i = 0; i < kNumAllocatingThreads; i++) { - std::thread* t = new std::thread([&stop] { - while (!stop) { - for (size_t size = kMinAllocationSize; size <= kMaxAllocationSize; size <<= 1) { - void* ptr; - DoNotOptimize(ptr = malloc(size)); - free(ptr); - } - } - }); - threads.push_back(t); - } - - // Create a thread to fork and allocate. - for (size_t i = 0; i < kNumForkLoops; i++) { - pid_t pid; - if ((pid = fork()) == 0) { - for (size_t size = kMinAllocationSize; size <= kMaxAllocationSize; size <<= 1) { - void* ptr; - DoNotOptimize(ptr = malloc(size)); - ASSERT_TRUE(ptr != nullptr); - // Make sure we can touch all of the allocation. - memset(ptr, 0x1, size); - ASSERT_LE(size, malloc_usable_size(ptr)); - free(ptr); - } - _exit(10); - } - ASSERT_NE(-1, pid); - AssertChildExited(pid, 10); - } - - stop = true; - for (auto thread : threads) { - thread->join(); - delete thread; - } -} - -TEST(android_mallopt, error_on_unexpected_option) { -#if defined(__BIONIC__) - const int unrecognized_option = -1; - errno = 0; - EXPECT_EQ(false, android_mallopt(unrecognized_option, nullptr, 0)); - EXPECT_ERRNO(ENOTSUP); -#else - GTEST_SKIP() << "bionic-only test"; -#endif -} - bool IsDynamic() { #if defined(__LP64__) Elf64_Ehdr ehdr; @@ -1582,167 +1153,6 @@ } } -void VerifyAllocationsAreZero(std::function<void*(size_t)> alloc_func, std::string function_name, - std::vector<size_t>& test_sizes, size_t max_allocations) { - // Vector of zero'd data used for comparisons. Make it twice the largest size. - std::vector<char> zero(test_sizes.back() * 2, 0); - - SCOPED_TRACE(testing::Message() << function_name << " failed to zero memory"); - - for (size_t test_size : test_sizes) { - std::vector<void*> ptrs(max_allocations); - for (size_t i = 0; i < ptrs.size(); i++) { - SCOPED_TRACE(testing::Message() << "size " << test_size << " at iteration " << i); - ptrs[i] = alloc_func(test_size); - ASSERT_TRUE(ptrs[i] != nullptr); - size_t alloc_size = malloc_usable_size(ptrs[i]); - ASSERT_LE(alloc_size, zero.size()); - ASSERT_EQ(0, memcmp(ptrs[i], zero.data(), alloc_size)); - - // Set the memory to non-zero to make sure if the pointer - // is reused it's still zero. - memset(ptrs[i], 0xab, alloc_size); - } - // Free the pointers. - for (size_t i = 0; i < ptrs.size(); i++) { - free(ptrs[i]); - } - for (size_t i = 0; i < ptrs.size(); i++) { - SCOPED_TRACE(testing::Message() << "size " << test_size << " at iteration " << i); - ptrs[i] = malloc(test_size); - ASSERT_TRUE(ptrs[i] != nullptr); - size_t alloc_size = malloc_usable_size(ptrs[i]); - ASSERT_LE(alloc_size, zero.size()); - ASSERT_EQ(0, memcmp(ptrs[i], zero.data(), alloc_size)); - } - // Free all of the pointers later to maximize the chance of reusing from - // the first loop. - for (size_t i = 0; i < ptrs.size(); i++) { - free(ptrs[i]); - } - } -} - -// Verify that small and medium allocations are always zero. -// @CddTest = 9.7/C-4-1 -TEST(malloc, zeroed_allocations_small_medium_sizes) { -#if !defined(__BIONIC__) - GTEST_SKIP() << "Only valid on bionic"; -#endif - SKIP_WITH_HWASAN << "Only test system allocator, not hwasan allocator."; - - if (IsLowRamDevice()) { - GTEST_SKIP() << "Skipped on low memory devices."; - } - - constexpr size_t kMaxAllocations = 1024; - std::vector<size_t> test_sizes = {16, 48, 128, 1024, 4096, 65536}; - VerifyAllocationsAreZero([](size_t size) -> void* { return malloc(size); }, "malloc", test_sizes, - kMaxAllocations); - - VerifyAllocationsAreZero([](size_t size) -> void* { return memalign(64, size); }, "memalign", - test_sizes, kMaxAllocations); - - VerifyAllocationsAreZero( - [](size_t size) -> void* { - void* ptr; - if (posix_memalign(&ptr, 64, size) == 0) { - return ptr; - } - return nullptr; - }, - "posix_memalign", test_sizes, kMaxAllocations); -} - -// Verify that large allocations are always zero. -// @CddTest = 9.7/C-4-1 -TEST(malloc, zeroed_allocations_large_sizes) { -#if !defined(__BIONIC__) - GTEST_SKIP() << "Only valid on bionic"; -#endif - SKIP_WITH_HWASAN << "Only test system allocator, not hwasan allocator."; - - if (IsLowRamDevice()) { - GTEST_SKIP() << "Skipped on low memory devices."; - } - - constexpr size_t kMaxAllocations = 20; - std::vector<size_t> test_sizes = {1000000, 2000000, 3000000, 4000000}; - VerifyAllocationsAreZero([](size_t size) -> void* { return malloc(size); }, "malloc", test_sizes, - kMaxAllocations); - - VerifyAllocationsAreZero([](size_t size) -> void* { return memalign(64, size); }, "memalign", - test_sizes, kMaxAllocations); - - VerifyAllocationsAreZero( - [](size_t size) -> void* { - void* ptr; - if (posix_memalign(&ptr, 64, size) == 0) { - return ptr; - } - return nullptr; - }, - "posix_memalign", test_sizes, kMaxAllocations); -} - -// Verify that reallocs are zeroed when expanded. -// @CddTest = 9.7/C-4-1 -TEST(malloc, zeroed_allocations_realloc) { -#if !defined(__BIONIC__) - GTEST_SKIP() << "Only valid on bionic"; -#endif - SKIP_WITH_HWASAN << "Only test system allocator, not hwasan allocator."; - - if (IsLowRamDevice()) { - GTEST_SKIP() << "Skipped on low memory devices."; - } - - // Vector of zero'd data used for comparisons. - constexpr size_t kMaxMemorySize = 131072; - std::vector<char> zero(kMaxMemorySize, 0); - - constexpr size_t kMaxAllocations = 1024; - std::vector<size_t> test_sizes = {16, 48, 128, 1024, 4096, 65536}; - // Do a number of allocations and set them to non-zero. - for (size_t test_size : test_sizes) { - std::vector<void*> ptrs(kMaxAllocations); - for (size_t i = 0; i < kMaxAllocations; i++) { - ptrs[i] = malloc(test_size); - ASSERT_TRUE(ptrs[i] != nullptr); - - // Set the memory to non-zero to make sure if the pointer - // is reused it's still zero. - memset(ptrs[i], 0xab, malloc_usable_size(ptrs[i])); - } - // Free the pointers. - for (size_t i = 0; i < kMaxAllocations; i++) { - free(ptrs[i]); - } - } - - // Do the reallocs to a larger size and verify the rest of the allocation - // is zero. - constexpr size_t kInitialSize = 8; - for (size_t test_size : test_sizes) { - std::vector<void*> ptrs(kMaxAllocations); - for (size_t i = 0; i < kMaxAllocations; i++) { - ptrs[i] = malloc(kInitialSize); - ASSERT_TRUE(ptrs[i] != nullptr); - size_t orig_alloc_size = malloc_usable_size(ptrs[i]); - - ptrs[i] = realloc(ptrs[i], test_size); - ASSERT_TRUE(ptrs[i] != nullptr); - size_t new_alloc_size = malloc_usable_size(ptrs[i]); - char* ptr = reinterpret_cast<char*>(ptrs[i]); - ASSERT_EQ(0, memcmp(&ptr[orig_alloc_size], zero.data(), new_alloc_size - orig_alloc_size)) - << "realloc from " << kInitialSize << " to size " << test_size << " at iteration " << i; - } - for (size_t i = 0; i < kMaxAllocations; i++) { - free(ptrs[i]); - } - } -} - TEST(android_mallopt, get_decay_time_enabled_errors) { #if defined(__BIONIC__) errno = 0;
diff --git a/tests/malloc_test_with_usable_size.cpp b/tests/malloc_test_with_usable_size.cpp new file mode 100644 index 0000000..150e599 --- /dev/null +++ b/tests/malloc_test_with_usable_size.cpp
@@ -0,0 +1,672 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +// (b/291762537): This code uses malloc_usable_size(), and thus can't be +// built with _FORTIFY_SOURCE=3. +#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE == 3 +#undef _FORTIFY_SOURCE +#define _FORTIFY_SOURCE 2 +#endif + +#include <gtest/gtest.h> + +#include <elf.h> +#include <limits.h> +#include <malloc.h> +#include <pthread.h> +#include <semaphore.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include <sys/cdefs.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <algorithm> +#include <atomic> +#include <functional> +#include <string> +#include <thread> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <tinyxml2.h> + +#include <android-base/file.h> +#include <android-base/test_utils.h> + +#include "DoNotOptimize.h" +#include "utils.h" + +#if defined(__BIONIC__) + +#include "SignalUtils.h" +#include "dlext_private_tests.h" + +#include "platform/bionic/malloc.h" +#include "platform/bionic/mte.h" +#include "platform/bionic/reserved_signals.h" +#include "private/bionic_config.h" + +#define HAVE_REALLOCARRAY 1 + +#elif defined(__GLIBC__) + +#define HAVE_REALLOCARRAY __GLIBC_PREREQ(2, 26) + +#elif defined(ANDROID_HOST_MUSL) + +#define HAVE_REALLOCARRAY 1 + +#endif + +TEST(malloc, malloc_std) { + // Simple malloc test. + void* ptr = malloc(100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + free(ptr); +} + +TEST(malloc, calloc_std) { + // Simple calloc test. + size_t alloc_len = 100; + char* ptr = (char*)calloc(1, alloc_len); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(alloc_len, malloc_usable_size(ptr)); + for (size_t i = 0; i < alloc_len; i++) { + ASSERT_EQ(0, ptr[i]); + } + free(ptr); +} + +TEST(malloc, memalign_multiple) { + SKIP_WITH_HWASAN << "hwasan requires power of 2 alignment"; + // Memalign test where the alignment is any value. + for (size_t i = 0; i <= 12; i++) { + for (size_t alignment = 1 << i; alignment < (1U << (i + 1)); alignment++) { + char* ptr = reinterpret_cast<char*>(memalign(alignment, 100)); + ASSERT_TRUE(ptr != nullptr) << "Failed at alignment " << alignment; + ASSERT_LE(100U, malloc_usable_size(ptr)) << "Failed at alignment " << alignment; + ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(ptr) % ((1U << i))) + << "Failed at alignment " << alignment; + free(ptr); + } + } +} + +TEST(malloc, memalign_realloc) { + // Memalign and then realloc the pointer a couple of times. + for (size_t alignment = 1; alignment <= 4096; alignment <<= 1) { + char* ptr = (char*)memalign(alignment, 100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + ASSERT_EQ(0U, (intptr_t)ptr % alignment); + memset(ptr, 0x23, 100); + + ptr = (char*)realloc(ptr, 200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + ASSERT_TRUE(ptr != nullptr); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(0x23, ptr[i]); + } + memset(ptr, 0x45, 200); + + ptr = (char*)realloc(ptr, 300); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(300U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 200; i++) { + ASSERT_EQ(0x45, ptr[i]); + } + memset(ptr, 0x67, 300); + + ptr = (char*)realloc(ptr, 250); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(250U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 250; i++) { + ASSERT_EQ(0x67, ptr[i]); + } + free(ptr); + } +} + +TEST(malloc, malloc_realloc_larger) { + // Realloc to a larger size, malloc is used for the original allocation. + char* ptr = (char*)malloc(100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + memset(ptr, 67, 100); + + ptr = (char*)realloc(ptr, 200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(67, ptr[i]); + } + free(ptr); +} + +TEST(malloc, malloc_realloc_smaller) { + // Realloc to a smaller size, malloc is used for the original allocation. + char* ptr = (char*)malloc(200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + memset(ptr, 67, 200); + + ptr = (char*)realloc(ptr, 100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(67, ptr[i]); + } + free(ptr); +} + +TEST(malloc, malloc_multiple_realloc) { + // Multiple reallocs, malloc is used for the original allocation. + char* ptr = (char*)malloc(200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + memset(ptr, 0x23, 200); + + ptr = (char*)realloc(ptr, 100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(0x23, ptr[i]); + } + + ptr = (char*)realloc(ptr, 50); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(50U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 50; i++) { + ASSERT_EQ(0x23, ptr[i]); + } + + ptr = (char*)realloc(ptr, 150); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(150U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 50; i++) { + ASSERT_EQ(0x23, ptr[i]); + } + memset(ptr, 0x23, 150); + + ptr = (char*)realloc(ptr, 425); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(425U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 150; i++) { + ASSERT_EQ(0x23, ptr[i]); + } + free(ptr); +} + +TEST(malloc, calloc_realloc_larger) { + // Realloc to a larger size, calloc is used for the original allocation. + char* ptr = (char*)calloc(1, 100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + + ptr = (char*)realloc(ptr, 200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(0, ptr[i]); + } + free(ptr); +} + +TEST(malloc, calloc_realloc_smaller) { + // Realloc to a smaller size, calloc is used for the original allocation. + char* ptr = (char*)calloc(1, 200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + + ptr = (char*)realloc(ptr, 100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(0, ptr[i]); + } + free(ptr); +} + +TEST(malloc, calloc_multiple_realloc) { + // Multiple reallocs, calloc is used for the original allocation. + char* ptr = (char*)calloc(1, 200); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(200U, malloc_usable_size(ptr)); + + ptr = (char*)realloc(ptr, 100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(100U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 100; i++) { + ASSERT_EQ(0, ptr[i]); + } + + ptr = (char*)realloc(ptr, 50); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(50U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 50; i++) { + ASSERT_EQ(0, ptr[i]); + } + + ptr = (char*)realloc(ptr, 150); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(150U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 50; i++) { + ASSERT_EQ(0, ptr[i]); + } + memset(ptr, 0, 150); + + ptr = (char*)realloc(ptr, 425); + ASSERT_TRUE(ptr != nullptr); + ASSERT_LE(425U, malloc_usable_size(ptr)); + for (size_t i = 0; i < 150; i++) { + ASSERT_EQ(0, ptr[i]); + } + free(ptr); +} + +#if defined(HAVE_DEPRECATED_MALLOC_FUNCS) +extern "C" void* pvalloc(size_t); +#endif + +TEST(malloc, pvalloc_std) { +#if defined(HAVE_DEPRECATED_MALLOC_FUNCS) + size_t pagesize = sysconf(_SC_PAGESIZE); + void* ptr = pvalloc(100); + ASSERT_TRUE(ptr != nullptr); + ASSERT_TRUE((reinterpret_cast<uintptr_t>(ptr) & (pagesize - 1)) == 0); + ASSERT_LE(pagesize, malloc_usable_size(ptr)); + free(ptr); +#else + GTEST_SKIP() << "pvalloc not supported."; +#endif +} + +TEST(malloc, calloc_usable_size) { + for (size_t size = 1; size <= 2048; size++) { + void* pointer = malloc(size); + ASSERT_TRUE(pointer != nullptr); + memset(pointer, 0xeb, malloc_usable_size(pointer)); + free(pointer); + + // We should get a previous pointer that has been set to non-zero. + // If calloc does not zero out all of the data, this will fail. + uint8_t* zero_mem = reinterpret_cast<uint8_t*>(calloc(1, size)); + ASSERT_TRUE(pointer != nullptr); + size_t usable_size = malloc_usable_size(zero_mem); + for (size_t i = 0; i < usable_size; i++) { + ASSERT_EQ(0, zero_mem[i]) << "Failed at allocation size " << size << " at byte " << i; + } + free(zero_mem); + } +} + +TEST(malloc, reallocarray) { +#if HAVE_REALLOCARRAY + void* p = reallocarray(nullptr, 2, 32); + ASSERT_TRUE(p != nullptr); + ASSERT_GE(malloc_usable_size(p), 64U); +#else + GTEST_SKIP() << "reallocarray not available"; +#endif +} + +TEST(malloc, mallinfo) { +#if defined(__BIONIC__) || defined(ANDROID_HOST_MUSL) + SKIP_WITH_HWASAN << "hwasan does not implement mallinfo"; + static size_t sizes[] = {8, 32, 128, 4096, 32768, 131072, 1024000, 10240000, 20480000, 300000000}; + + static constexpr size_t kMaxAllocs = 50; + + for (size_t size : sizes) { + // If some of these allocations are stuck in a thread cache, then keep + // looping until we make an allocation that changes the total size of the + // memory allocated. + // jemalloc implementations counts the thread cache allocations against + // total memory allocated. + void* ptrs[kMaxAllocs] = {}; + bool pass = false; + for (size_t i = 0; i < kMaxAllocs; i++) { + size_t allocated = mallinfo().uordblks; + ptrs[i] = malloc(size); + ASSERT_TRUE(ptrs[i] != nullptr); + size_t new_allocated = mallinfo().uordblks; + if (allocated != new_allocated) { + size_t usable_size = malloc_usable_size(ptrs[i]); + // Only check if the total got bigger by at least allocation size. + // Sometimes the mallinfo numbers can go backwards due to compaction + // and/or freeing of cached data. + if (new_allocated >= allocated + usable_size) { + pass = true; + break; + } + } + } + for (void* ptr : ptrs) { + free(ptr); + } + ASSERT_TRUE(pass) << "For size " << size << " allocated bytes did not increase after " + << kMaxAllocs << " allocations."; + } +#else + GTEST_SKIP() << "glibc is broken"; +#endif +} + +TEST(malloc, mallinfo2) { +#if defined(__BIONIC__) || defined(ANDROID_HOST_MUSL) + SKIP_WITH_HWASAN << "hwasan does not implement mallinfo2"; + static size_t sizes[] = {8, 32, 128, 4096, 32768, 131072, 1024000, 10240000, 20480000, 300000000}; + + static constexpr size_t kMaxAllocs = 50; + + for (size_t size : sizes) { + // If some of these allocations are stuck in a thread cache, then keep + // looping until we make an allocation that changes the total size of the + // memory allocated. + // jemalloc implementations counts the thread cache allocations against + // total memory allocated. + void* ptrs[kMaxAllocs] = {}; + bool pass = false; + for (size_t i = 0; i < kMaxAllocs; i++) { + struct mallinfo info = mallinfo(); + struct mallinfo2 info2 = mallinfo2(); + // Verify that mallinfo and mallinfo2 are exactly the same. + ASSERT_EQ(static_cast<size_t>(info.arena), info2.arena); + ASSERT_EQ(static_cast<size_t>(info.ordblks), info2.ordblks); + ASSERT_EQ(static_cast<size_t>(info.smblks), info2.smblks); + ASSERT_EQ(static_cast<size_t>(info.hblks), info2.hblks); + ASSERT_EQ(static_cast<size_t>(info.hblkhd), info2.hblkhd); + ASSERT_EQ(static_cast<size_t>(info.usmblks), info2.usmblks); + ASSERT_EQ(static_cast<size_t>(info.fsmblks), info2.fsmblks); + ASSERT_EQ(static_cast<size_t>(info.uordblks), info2.uordblks); + ASSERT_EQ(static_cast<size_t>(info.fordblks), info2.fordblks); + ASSERT_EQ(static_cast<size_t>(info.keepcost), info2.keepcost); + + size_t allocated = info2.uordblks; + ptrs[i] = malloc(size); + ASSERT_TRUE(ptrs[i] != nullptr); + + info = mallinfo(); + info2 = mallinfo2(); + // Verify that mallinfo and mallinfo2 are exactly the same. + ASSERT_EQ(static_cast<size_t>(info.arena), info2.arena); + ASSERT_EQ(static_cast<size_t>(info.ordblks), info2.ordblks); + ASSERT_EQ(static_cast<size_t>(info.smblks), info2.smblks); + ASSERT_EQ(static_cast<size_t>(info.hblks), info2.hblks); + ASSERT_EQ(static_cast<size_t>(info.hblkhd), info2.hblkhd); + ASSERT_EQ(static_cast<size_t>(info.usmblks), info2.usmblks); + ASSERT_EQ(static_cast<size_t>(info.fsmblks), info2.fsmblks); + ASSERT_EQ(static_cast<size_t>(info.uordblks), info2.uordblks); + ASSERT_EQ(static_cast<size_t>(info.fordblks), info2.fordblks); + ASSERT_EQ(static_cast<size_t>(info.keepcost), info2.keepcost); + + size_t new_allocated = info2.uordblks; + if (allocated != new_allocated) { + size_t usable_size = malloc_usable_size(ptrs[i]); + // Only check if the total got bigger by at least allocation size. + // Sometimes the mallinfo2 numbers can go backwards due to compaction + // and/or freeing of cached data. + if (new_allocated >= allocated + usable_size) { + pass = true; + break; + } + } + } + for (void* ptr : ptrs) { + free(ptr); + } + ASSERT_TRUE(pass) << "For size " << size << " allocated bytes did not increase after " + << kMaxAllocs << " allocations."; + } +#else + GTEST_SKIP() << "glibc is broken"; +#endif +} + +// Jemalloc doesn't pass this test right now, so leave it as disabled. +TEST(malloc, DISABLED_alloc_after_fork) { + // Both of these need to be a power of 2. + static constexpr size_t kMinAllocationSize = 8; + static constexpr size_t kMaxAllocationSize = 2097152; + + static constexpr size_t kNumAllocatingThreads = 5; + static constexpr size_t kNumForkLoops = 100; + + std::atomic_bool stop; + + // Create threads that simply allocate and free different sizes. + std::vector<std::thread*> threads; + for (size_t i = 0; i < kNumAllocatingThreads; i++) { + std::thread* t = new std::thread([&stop] { + while (!stop) { + for (size_t size = kMinAllocationSize; size <= kMaxAllocationSize; size <<= 1) { + void* ptr; + DoNotOptimize(ptr = malloc(size)); + free(ptr); + } + } + }); + threads.push_back(t); + } + + // Create a thread to fork and allocate. + for (size_t i = 0; i < kNumForkLoops; i++) { + pid_t pid; + if ((pid = fork()) == 0) { + for (size_t size = kMinAllocationSize; size <= kMaxAllocationSize; size <<= 1) { + void* ptr; + DoNotOptimize(ptr = malloc(size)); + ASSERT_TRUE(ptr != nullptr); + // Make sure we can touch all of the allocation. + memset(ptr, 0x1, size); + ASSERT_LE(size, malloc_usable_size(ptr)); + free(ptr); + } + _exit(10); + } + ASSERT_NE(-1, pid); + AssertChildExited(pid, 10); + } + + stop = true; + for (auto thread : threads) { + thread->join(); + delete thread; + } +} + +void VerifyAllocationsAreZero(std::function<void*(size_t)> alloc_func, std::string function_name, + std::vector<size_t>& test_sizes, size_t max_allocations) { + // Vector of zero'd data used for comparisons. Make it twice the largest size. + std::vector<char> zero(test_sizes.back() * 2, 0); + + SCOPED_TRACE(testing::Message() << function_name << " failed to zero memory"); + + for (size_t test_size : test_sizes) { + std::vector<void*> ptrs(max_allocations); + for (size_t i = 0; i < ptrs.size(); i++) { + SCOPED_TRACE(testing::Message() << "size " << test_size << " at iteration " << i); + ptrs[i] = alloc_func(test_size); + ASSERT_TRUE(ptrs[i] != nullptr); + size_t alloc_size = malloc_usable_size(ptrs[i]); + ASSERT_LE(alloc_size, zero.size()); + ASSERT_EQ(0, memcmp(ptrs[i], zero.data(), alloc_size)); + + // Set the memory to non-zero to make sure if the pointer + // is reused it's still zero. + memset(ptrs[i], 0xab, alloc_size); + } + // Free the pointers. + for (size_t i = 0; i < ptrs.size(); i++) { + free(ptrs[i]); + } + for (size_t i = 0; i < ptrs.size(); i++) { + SCOPED_TRACE(testing::Message() << "size " << test_size << " at iteration " << i); + ptrs[i] = malloc(test_size); + ASSERT_TRUE(ptrs[i] != nullptr); + size_t alloc_size = malloc_usable_size(ptrs[i]); + ASSERT_LE(alloc_size, zero.size()); + ASSERT_EQ(0, memcmp(ptrs[i], zero.data(), alloc_size)); + } + // Free all of the pointers later to maximize the chance of reusing from + // the first loop. + for (size_t i = 0; i < ptrs.size(); i++) { + free(ptrs[i]); + } + } +} + +// Verify that small and medium allocations are always zero. +// @CddTest = 9.7/C-4-1 +TEST(malloc, zeroed_allocations_small_medium_sizes) { +#if !defined(__BIONIC__) + GTEST_SKIP() << "Only valid on bionic"; +#endif + SKIP_WITH_HWASAN << "Only test system allocator, not hwasan allocator."; + + if (IsLowRamDevice()) { + GTEST_SKIP() << "Skipped on low memory devices."; + } + + constexpr size_t kMaxAllocations = 1024; + std::vector<size_t> test_sizes = {16, 48, 128, 1024, 4096, 65536}; + VerifyAllocationsAreZero([](size_t size) -> void* { return malloc(size); }, "malloc", test_sizes, + kMaxAllocations); + + VerifyAllocationsAreZero([](size_t size) -> void* { return memalign(64, size); }, "memalign", + test_sizes, kMaxAllocations); + + VerifyAllocationsAreZero( + [](size_t size) -> void* { + void* ptr; + if (posix_memalign(&ptr, 64, size) == 0) { + return ptr; + } + return nullptr; + }, + "posix_memalign", test_sizes, kMaxAllocations); +} + +// Verify that large allocations are always zero. +// @CddTest = 9.7/C-4-1 +TEST(malloc, zeroed_allocations_large_sizes) { +#if !defined(__BIONIC__) + GTEST_SKIP() << "Only valid on bionic"; +#endif + SKIP_WITH_HWASAN << "Only test system allocator, not hwasan allocator."; + + if (IsLowRamDevice()) { + GTEST_SKIP() << "Skipped on low memory devices."; + } + + constexpr size_t kMaxAllocations = 20; + std::vector<size_t> test_sizes = {1000000, 2000000, 3000000, 4000000}; + VerifyAllocationsAreZero([](size_t size) -> void* { return malloc(size); }, "malloc", test_sizes, + kMaxAllocations); + + VerifyAllocationsAreZero([](size_t size) -> void* { return memalign(64, size); }, "memalign", + test_sizes, kMaxAllocations); + + VerifyAllocationsAreZero( + [](size_t size) -> void* { + void* ptr; + if (posix_memalign(&ptr, 64, size) == 0) { + return ptr; + } + return nullptr; + }, + "posix_memalign", test_sizes, kMaxAllocations); +} + +// Verify that reallocs are zeroed when expanded. +// @CddTest = 9.7/C-4-1 +TEST(malloc, zeroed_allocations_realloc) { +#if !defined(__BIONIC__) + GTEST_SKIP() << "Only valid on bionic"; +#endif + SKIP_WITH_HWASAN << "Only test system allocator, not hwasan allocator."; + + if (IsLowRamDevice()) { + GTEST_SKIP() << "Skipped on low memory devices."; + } + + // Vector of zero'd data used for comparisons. + constexpr size_t kMaxMemorySize = 131072; + std::vector<char> zero(kMaxMemorySize, 0); + + constexpr size_t kMaxAllocations = 1024; + std::vector<size_t> test_sizes = {16, 48, 128, 1024, 4096, 65536}; + // Do a number of allocations and set them to non-zero. + for (size_t test_size : test_sizes) { + std::vector<void*> ptrs(kMaxAllocations); + for (size_t i = 0; i < kMaxAllocations; i++) { + ptrs[i] = malloc(test_size); + ASSERT_TRUE(ptrs[i] != nullptr); + + // Set the memory to non-zero to make sure if the pointer + // is reused it's still zero. + memset(ptrs[i], 0xab, malloc_usable_size(ptrs[i])); + } + // Free the pointers. + for (size_t i = 0; i < kMaxAllocations; i++) { + free(ptrs[i]); + } + } + + // Do the reallocs to a larger size and verify the rest of the allocation + // is zero. + constexpr size_t kInitialSize = 8; + for (size_t test_size : test_sizes) { + std::vector<void*> ptrs(kMaxAllocations); + for (size_t i = 0; i < kMaxAllocations; i++) { + ptrs[i] = malloc(kInitialSize); + ASSERT_TRUE(ptrs[i] != nullptr); + size_t orig_alloc_size = malloc_usable_size(ptrs[i]); + + ptrs[i] = realloc(ptrs[i], test_size); + ASSERT_TRUE(ptrs[i] != nullptr); + size_t new_alloc_size = malloc_usable_size(ptrs[i]); + char* ptr = reinterpret_cast<char*>(ptrs[i]); + ASSERT_EQ(0, memcmp(&ptr[orig_alloc_size], zero.data(), new_alloc_size - orig_alloc_size)) + << "realloc from " << kInitialSize << " to size " << test_size << " at iteration " << i; + } + for (size_t i = 0; i < kMaxAllocations; i++) { + free(ptrs[i]); + } + } +}