Verify that allocated memory is always zeroed.
If this is a low ram device, then do not run these tests. Otherwise,
verify that memory returned from the allocator is zero.
Bug: 171429763
Bug: 264539505
Bug: 265431478
Test: New unit tests pass on scudo.
Test: New unit tests fail on original jemalloc.
Test: New unit tests pass on jemalloc that always zeroes memory.
Test: New unit tests are skipped on low ram device.
Test: Ran unit tests atest CtsBionicTestCases in above scenarios.
Change-Id: Id62ab74f649c0f7c14a951235fe79972926ecec0
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index e90a824..63ad99d 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -35,6 +35,7 @@
#include <algorithm>
#include <atomic>
+#include <functional>
#include <thread>
#include <vector>
@@ -1528,3 +1529,157 @@
}
}
}
+
+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.
+TEST(malloc, zeroed_allocations_small_medium_sizes) {
+#if !defined(__BIONIC__)
+ GTEST_SKIP() << "Only valid on bionic";
+#endif
+
+ 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.
+TEST(malloc, zeroed_allocations_large_sizes) {
+#if !defined(__BIONIC__)
+ GTEST_SKIP() << "Only valid on bionic";
+#endif
+
+ 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);
+}
+
+TEST(malloc, zeroed_allocations_realloc) {
+#if !defined(__BIONIC__)
+ GTEST_SKIP() << "Only valid on bionic";
+#endif
+
+ 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]);
+ }
+ }
+}