| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdio.h> |
| |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/process/process_metrics.h" |
| #include "base/sys_info.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if defined(USE_TCMALLOC) |
| namespace { |
| |
| using std::min; |
| |
| #ifdef NDEBUG |
| // We wrap malloc and free in noinline functions to ensure that we test the real |
| // implementation of the allocator. Otherwise, the compiler may specifically |
| // recognize the calls to malloc and free in our tests and optimize them away. |
| NOINLINE void* TCMallocDoMallocForTest(size_t size) { |
| return malloc(size); |
| } |
| |
| NOINLINE void TCMallocDoFreeForTest(void* ptr) { |
| free(ptr); |
| } |
| #endif |
| |
| // Fill a buffer of the specified size with a predetermined pattern |
| static void Fill(unsigned char* buffer, int n) { |
| for (int i = 0; i < n; i++) { |
| buffer[i] = (i & 0xff); |
| } |
| } |
| |
| // Check that the specified buffer has the predetermined pattern |
| // generated by Fill() |
| static bool Valid(unsigned char* buffer, int n) { |
| for (int i = 0; i < n; i++) { |
| if (buffer[i] != (i & 0xff)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Return the next interesting size/delta to check. Returns -1 if no more. |
| static int NextSize(int size) { |
| if (size < 100) |
| return size + 1; |
| |
| if (size < 100000) { |
| // Find next power of two |
| int power = 1; |
| while (power < size) |
| power <<= 1; |
| |
| // Yield (power-1, power, power+1) |
| if (size < power - 1) |
| return power - 1; |
| |
| if (size == power - 1) |
| return power; |
| |
| CHECK_EQ(size, power); |
| return power + 1; |
| } else { |
| return -1; |
| } |
| } |
| |
| static void TestCalloc(size_t n, size_t s, bool ok) { |
| char* p = reinterpret_cast<char*>(calloc(n, s)); |
| if (!ok) { |
| EXPECT_EQ(nullptr, p) << "calloc(n, s) should not succeed"; |
| } else { |
| EXPECT_NE(reinterpret_cast<void*>(NULL), p) |
| << "calloc(n, s) should succeed"; |
| for (size_t i = 0; i < n * s; i++) { |
| EXPECT_EQ('\0', p[i]); |
| } |
| free(p); |
| } |
| } |
| |
| bool IsLowMemoryDevice() { |
| return base::SysInfo::AmountOfPhysicalMemory() <= 256LL * 1024 * 1024; |
| } |
| |
| } // namespace |
| |
| TEST(TCMallocTest, Malloc) { |
| // Try allocating data with a bunch of alignments and sizes |
| for (int size = 1; size < 1048576; size *= 2) { |
| unsigned char* ptr = reinterpret_cast<unsigned char*>(malloc(size)); |
| // Should be 2 byte aligned |
| EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & 1); |
| Fill(ptr, size); |
| EXPECT_TRUE(Valid(ptr, size)); |
| free(ptr); |
| } |
| } |
| |
| TEST(TCMallocTest, Calloc) { |
| TestCalloc(0, 0, true); |
| TestCalloc(0, 1, true); |
| TestCalloc(1, 1, true); |
| TestCalloc(1 << 10, 0, true); |
| TestCalloc(1 << 20, 0, true); |
| TestCalloc(0, 1 << 10, true); |
| TestCalloc(0, 1 << 20, true); |
| TestCalloc(1 << 20, 2, true); |
| TestCalloc(2, 1 << 20, true); |
| TestCalloc(1000, 1000, true); |
| } |
| |
| #ifdef NDEBUG |
| // This makes sure that reallocing a small number of bytes in either |
| // direction doesn't cause us to allocate new memory. Tcmalloc in debug mode |
| // does not follow this. |
| TEST(TCMallocTest, ReallocSmallDelta) { |
| int start_sizes[] = {100, 1000, 10000, 100000}; |
| int deltas[] = {1, -2, 4, -8, 16, -32, 64, -128}; |
| |
| for (unsigned s = 0; s < sizeof(start_sizes) / sizeof(*start_sizes); ++s) { |
| void* p = malloc(start_sizes[s]); |
| ASSERT_TRUE(p); |
| // The larger the start-size, the larger the non-reallocing delta. |
| for (unsigned d = 0; d < s * 2; ++d) { |
| void* new_p = realloc(p, start_sizes[s] + deltas[d]); |
| ASSERT_EQ(p, new_p); // realloc should not allocate new memory |
| } |
| // Test again, but this time reallocing smaller first. |
| for (unsigned d = 0; d < s * 2; ++d) { |
| void* new_p = realloc(p, start_sizes[s] - deltas[d]); |
| ASSERT_EQ(p, new_p); // realloc should not allocate new memory |
| } |
| free(p); |
| } |
| } |
| #endif |
| |
| TEST(TCMallocTest, Realloc) { |
| for (int src_size = 0; src_size >= 0; src_size = NextSize(src_size)) { |
| for (int dst_size = 0; dst_size >= 0; dst_size = NextSize(dst_size)) { |
| unsigned char* src = reinterpret_cast<unsigned char*>(malloc(src_size)); |
| Fill(src, src_size); |
| unsigned char* dst = |
| reinterpret_cast<unsigned char*>(realloc(src, dst_size)); |
| EXPECT_TRUE(Valid(dst, min(src_size, dst_size))); |
| Fill(dst, dst_size); |
| EXPECT_TRUE(Valid(dst, dst_size)); |
| if (dst != nullptr) |
| free(dst); |
| } |
| } |
| |
| // The logic below tries to allocate kNumEntries * 9000 ~= 130 MB of memory. |
| // This would cause the test to crash on low memory devices with no VM |
| // overcommit (e.g., chromecast). |
| if (IsLowMemoryDevice()) |
| return; |
| |
| // Now make sure realloc works correctly even when we overflow the |
| // packed cache, so some entries are evicted from the cache. |
| // The cache has 2^12 entries, keyed by page number. |
| const int kNumEntries = 1 << 14; |
| int** p = reinterpret_cast<int**>(malloc(sizeof(*p) * kNumEntries)); |
| int sum = 0; |
| for (int i = 0; i < kNumEntries; i++) { |
| // no page size is likely to be bigger than 8192? |
| p[i] = reinterpret_cast<int*>(malloc(8192)); |
| p[i][1000] = i; // use memory deep in the heart of p |
| } |
| for (int i = 0; i < kNumEntries; i++) { |
| p[i] = reinterpret_cast<int*>(realloc(p[i], 9000)); |
| } |
| for (int i = 0; i < kNumEntries; i++) { |
| sum += p[i][1000]; |
| free(p[i]); |
| } |
| EXPECT_EQ(kNumEntries / 2 * (kNumEntries - 1), sum); // assume kNE is even |
| free(p); |
| } |
| |
| #ifdef NDEBUG |
| TEST(TCMallocFreeTest, BadPointerInFirstPageOfTheLargeObject) { |
| const size_t kPageSize = base::GetPageSize(); |
| char* p = |
| reinterpret_cast<char*>(TCMallocDoMallocForTest(10 * kPageSize + 1)); |
| for (unsigned offset = 1; offset < kPageSize; offset <<= 1) { |
| ASSERT_DEATH(TCMallocDoFreeForTest(p + offset), |
| "Pointer is not pointing to the start of a span"); |
| } |
| TCMallocDoFreeForTest(p); |
| } |
| |
| // TODO(ssid): Fix flakiness and enable the test, crbug.com/571549. |
| TEST(TCMallocFreeTest, DISABLED_BadPageAlignedPointerInsideLargeObject) { |
| const size_t kPageSize = base::GetPageSize(); |
| const size_t kMaxSize = 10 * kPageSize; |
| char* p = reinterpret_cast<char*>(TCMallocDoMallocForTest(kMaxSize + 1)); |
| |
| for (unsigned offset = kPageSize; offset < kMaxSize; offset += kPageSize) { |
| // Only the first and last page of a span are in heap map. So for others |
| // tcmalloc will give a general error of invalid pointer. |
| ASSERT_DEATH(TCMallocDoFreeForTest(p + offset), ""); |
| } |
| ASSERT_DEATH(TCMallocDoFreeForTest(p + kMaxSize), |
| "Pointer is not pointing to the start of a span"); |
| TCMallocDoFreeForTest(p); |
| } |
| |
| TEST(TCMallocFreeTest, DoubleFreeLargeObject) { |
| const size_t kMaxSize = 10 * base::GetPageSize(); |
| char* p = reinterpret_cast<char*>(TCMallocDoMallocForTest(kMaxSize + 1)); |
| ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), |
| "Object was not in-use"); |
| } |
| |
| TEST(TCMallocFreeTest, DoubleFreeSmallObject) { |
| const size_t kPageSize = base::GetPageSize(); |
| for (size_t size = 1; size <= kPageSize; size <<= 1) { |
| char* p = reinterpret_cast<char*>(TCMallocDoMallocForTest(size)); |
| ASSERT_DEATH(TCMallocDoFreeForTest(p); TCMallocDoFreeForTest(p), |
| "Circular loop in list detected"); |
| } |
| } |
| #endif // NDEBUG |
| |
| #endif |