/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArenaAllocator.h" #include "mozilla/ArenaAllocatorExtensions.h" #include "nsIMemoryReporter.h" // MOZ_MALLOC_SIZE_OF #include "gtest/gtest.h" using mozilla::ArenaAllocator; TEST(ArenaAllocator, Constructor) { ArenaAllocator<4096, 4> a; } TEST(ArenaAllocator, DefaultAllocate) { // Test default 1-byte alignment. ArenaAllocator<1024> a; void* x = a.Allocate(101); void* y = a.Allocate(101); // Given 1-byte aligment, we expect the allocations to follow // each other exactly. EXPECT_EQ(uintptr_t(x) + 101, uintptr_t(y)); } TEST(ArenaAllocator, AllocateAlignment) { // Test non-default 8-byte alignment. static const size_t kAlignment = 8; ArenaAllocator<1024, kAlignment> a; // Make sure aligment is correct for 1-8. for (size_t i = 1; i <= kAlignment; i++) { // All of these should be 8 bytes void* x = a.Allocate(i); void* y = a.Allocate(i); EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y)); } // Test with slightly larger than specified alignment. void* x = a.Allocate(kAlignment + 1); void* y = a.Allocate(kAlignment + 1); // Given 8-byte aligment, and a non-8-byte aligned request we expect the // allocations to be padded. EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y)); // We expect 7 bytes of padding to have been added. EXPECT_EQ(uintptr_t(x) + kAlignment * 2, uintptr_t(y)); } #if 0 TEST(ArenaAllocator, AllocateZeroBytes) { // This would have to be a death test. Since we chose to provide an // infallible allocator we can't just return nullptr in the 0 case as // there's no way to differentiate that from the OOM case. ArenaAllocator<1024> a; void* x = a.Allocate(0); EXPECT_FALSE(x); } TEST(ArenaAllocator, BadAlignment) { // This test causes build failures by triggering the static assert enforcing // a power-of-two alignment. ArenaAllocator<256, 3> a; ArenaAllocator<256, 7> b; ArenaAllocator<256, 17> c; } #endif TEST(ArenaAllocator, AllocateMultipleSizes) { // Test non-default 4-byte alignment. ArenaAllocator<4096, 4> a; for (int i = 1; i < 50; i++) { void* x = a.Allocate(i); // All the allocations should be aligned properly. EXPECT_EQ(uintptr_t(x) % 4, uintptr_t(0)); } // Test a large 64-byte alignment ArenaAllocator<8192, 64> b; for (int i = 1; i < 100; i++) { void* x = b.Allocate(i); // All the allocations should be aligned properly. EXPECT_EQ(uintptr_t(x) % 64, uintptr_t(0)); } } TEST(ArenaAllocator, AllocateInDifferentChunks) { // Test default 1-byte alignment. ArenaAllocator<4096> a; void* x = a.Allocate(4000); void* y = a.Allocate(4000); EXPECT_NE(uintptr_t(x) + 4000, uintptr_t(y)); } TEST(ArenaAllocator, AllocateLargerThanArenaSize) { // Test default 1-byte alignment. ArenaAllocator<256> a; void* x = a.Allocate(4000); void* y = a.Allocate(4000); EXPECT_TRUE(x); EXPECT_TRUE(y); // Now try a normal allocation, it should behave as expected. x = a.Allocate(8); y = a.Allocate(8); EXPECT_EQ(uintptr_t(x) + 8, uintptr_t(y)); } TEST(ArenaAllocator, AllocationsPerChunk) { // Test that expected number of allocations fit in one chunk. // We use an alignment of 64-bytes to avoid worrying about differences in // the header size on 32 and 64-bit platforms. const size_t kArenaSize = 1024; const size_t kAlignment = 64; ArenaAllocator a; // With an alignment of 64 bytes we expect the header to take up the first // alignment sized slot leaving bytes leaving the rest available for // allocation. const size_t kAllocationsPerChunk = (kArenaSize / kAlignment) - 1; void* x = nullptr; void* y = a.Allocate(kAlignment); EXPECT_TRUE(y); for (size_t i = 1; i < kAllocationsPerChunk; i++) { x = y; y = a.Allocate(kAlignment); EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y)); } // The next allocation should be in a different chunk. x = y; y = a.Allocate(kAlignment); EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y)); } TEST(ArenaAllocator, MemoryIsValid) { // Make multiple allocations and actually access the memory. This is // expected to trip up ASAN or valgrind if out of bounds memory is // accessed. static const size_t kArenaSize = 1024; static const size_t kAlignment = 64; static const char kMark = char(0xBC); ArenaAllocator a; // Single allocation that should fill the arena. size_t sz = kArenaSize - kAlignment; char* x = (char*)a.Allocate(sz); EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); memset(x, kMark, sz); for (size_t i = 0; i < sz; i++) { EXPECT_EQ(x[i], kMark); } // Allocation over arena size. sz = kArenaSize * 2; x = (char*)a.Allocate(sz); EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); memset(x, kMark, sz); for (size_t i = 0; i < sz; i++) { EXPECT_EQ(x[i], kMark); } // Allocation half the arena size. sz = kArenaSize / 2; x = (char*)a.Allocate(sz); EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); memset(x, kMark, sz); for (size_t i = 0; i < sz; i++) { EXPECT_EQ(x[i], kMark); } // Repeat, this should actually end up in a new chunk. x = (char*)a.Allocate(sz); EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); memset(x, kMark, sz); for (size_t i = 0; i < sz; i++) { EXPECT_EQ(x[i], kMark); } } MOZ_DEFINE_MALLOC_SIZE_OF(TestSizeOf); TEST(ArenaAllocator, SizeOf) { // This tests the sizeof functionality. We can't test for equality as we // can't reliably guarantee what sizes the underlying allocator is going to // choose, so we just test that things grow (or not) as expected. static const size_t kArenaSize = 4096; ArenaAllocator a; // Excluding *this we expect an empty arena allocator to have no overhead. size_t sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_EQ(sz, size_t(0)); // Cause one chunk to be allocated. (void)a.Allocate(kArenaSize / 2); size_t prev_sz = sz; sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_GT(sz, prev_sz); // Allocate within the current chunk. (void)a.Allocate(kArenaSize / 4); prev_sz = sz; sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_EQ(sz, prev_sz); // Overflow to a new chunk. (void)a.Allocate(kArenaSize / 2); prev_sz = sz; sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_GT(sz, prev_sz); // Allocate an oversized chunk with enough room for a header to fit in page // size. We expect the underlying allocator to round up to page alignment. (void)a.Allocate((kArenaSize * 2) - 64); sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_GT(sz, prev_sz); } TEST(ArenaAllocator, Clear) { // Tests that the Clear function works as expected. The best proxy for // checking if a clear is successful is to measure the size. If it's empty we // expect the size to be 0. static const size_t kArenaSize = 128; ArenaAllocator a; // Clearing an empty arena should work. a.Clear(); size_t sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_EQ(sz, size_t(0)); // Allocating should work after clearing an empty arena. void* x = a.Allocate(10); EXPECT_TRUE(x); size_t prev_sz = sz; sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_GT(sz, prev_sz); // Allocate enough for a few arena chunks to be necessary. for (size_t i = 0; i < kArenaSize * 2; i++) { x = a.Allocate(1); EXPECT_TRUE(x); } prev_sz = sz; sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_GT(sz, prev_sz); // Clearing should reduce the size back to zero. a.Clear(); sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_EQ(sz, size_t(0)); // Allocating should work after clearing an arena with allocations. x = a.Allocate(10); EXPECT_TRUE(x); prev_sz = sz; sz = a.SizeOfExcludingThis(TestSizeOf); EXPECT_GT(sz, prev_sz); } TEST(ArenaAllocator, Extensions) { ArenaAllocator<4096, 8> a; // Test with raw strings. const char* const kTestCStr = "This is a test string."; char* c_dup = mozilla::ArenaStrdup(kTestCStr, a); EXPECT_STREQ(c_dup, kTestCStr); const char16_t* const kTestStr = u"This is a wide test string."; char16_t* dup = mozilla::ArenaStrdup(kTestStr, a); EXPECT_TRUE(nsString(dup).Equals(kTestStr)); // Make sure it works with literal strings. constexpr auto wideStr = u"A wide string."_ns; nsLiteralString::char_type* wide = mozilla::ArenaStrdup(wideStr, a); EXPECT_TRUE(wideStr.Equals(wide)); constexpr auto cStr = "A c-string."_ns; nsLiteralCString::char_type* cstr = mozilla::ArenaStrdup(cStr, a); EXPECT_TRUE(cStr.Equals(cstr)); // Make sure it works with normal strings. nsAutoString x(u"testing wide"); nsAutoString::char_type* x_copy = mozilla::ArenaStrdup(x, a); EXPECT_TRUE(x.Equals(x_copy)); nsAutoCString y("testing c-string"); nsAutoCString::char_type* y_copy = mozilla::ArenaStrdup(y, a); EXPECT_TRUE(y.Equals(y_copy)); }