summaryrefslogtreecommitdiffstats
path: root/memory/replace/phc/test/gtest/TestPHC.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'memory/replace/phc/test/gtest/TestPHC.cpp')
-rw-r--r--memory/replace/phc/test/gtest/TestPHC.cpp305
1 files changed, 305 insertions, 0 deletions
diff --git a/memory/replace/phc/test/gtest/TestPHC.cpp b/memory/replace/phc/test/gtest/TestPHC.cpp
new file mode 100644
index 0000000000..738a50eee2
--- /dev/null
+++ b/memory/replace/phc/test/gtest/TestPHC.cpp
@@ -0,0 +1,305 @@
+/* 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 "gtest/gtest.h"
+
+#include "mozmemory.h"
+#include "replace_malloc_bridge.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/mozalloc.h"
+#include "../../PHC.h"
+
+using namespace mozilla;
+
+bool PHCInfoEq(phc::AddrInfo& aInfo, phc::AddrInfo::Kind aKind, void* aBaseAddr,
+ size_t aUsableSize, bool aHasAllocStack, bool aHasFreeStack) {
+ return aInfo.mKind == aKind && aInfo.mBaseAddr == aBaseAddr &&
+ aInfo.mUsableSize == aUsableSize &&
+ // Proper stack traces will have at least 3 elements.
+ (aHasAllocStack ? (aInfo.mAllocStack->mLength > 2)
+ : (aInfo.mAllocStack.isNothing())) &&
+ (aHasFreeStack ? (aInfo.mFreeStack->mLength > 2)
+ : (aInfo.mFreeStack.isNothing()));
+}
+
+bool JeInfoEq(jemalloc_ptr_info_t& aInfo, PtrInfoTag aTag, void* aAddr,
+ size_t aSize, arena_id_t arenaId) {
+ return aInfo.tag == aTag && aInfo.addr == aAddr && aInfo.size == aSize
+#ifdef MOZ_DEBUG
+ && aInfo.arenaId == arenaId
+#endif
+ ;
+}
+
+uint8_t* GetPHCAllocation(size_t aSize, size_t aAlignment = 1) {
+ // A crude but effective way to get a PHC allocation.
+ for (int i = 0; i < 2000000; i++) {
+ void* p = (aAlignment == 1) ? moz_xmalloc(aSize)
+ : moz_xmemalign(aAlignment, aSize);
+ if (ReplaceMalloc::IsPHCAllocation(p, nullptr)) {
+ return (uint8_t*)p;
+ }
+ free(p);
+ }
+ return nullptr;
+}
+
+static const size_t kPageSize = 4096;
+
+TEST(PHC, TestPHCAllocations)
+{
+ // First, check that allocations of various sizes all get put at the end of
+ // their page as expected. Also, check their sizes are as expected.
+
+#define ASSERT_POS(n1, n2) \
+ p = (uint8_t*)moz_xrealloc(p, (n1)); \
+ ASSERT_EQ((reinterpret_cast<uintptr_t>(p) & (kPageSize - 1)), \
+ kPageSize - (n2)); \
+ ASSERT_EQ(moz_malloc_usable_size(p), (n2));
+
+ uint8_t* p = GetPHCAllocation(1);
+ if (!p) {
+ MOZ_CRASH("failed to get a PHC allocation");
+ }
+
+ // On Win64 the smallest possible allocation is 16 bytes. On other platforms
+ // it is 8 bytes.
+#if defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
+ ASSERT_POS(8U, 16U);
+#else
+ ASSERT_POS(8U, 8U);
+#endif
+ ASSERT_POS(16U, 16U);
+ ASSERT_POS(32U, 32U);
+ ASSERT_POS(64U, 64U);
+ ASSERT_POS(128U, 128U);
+ ASSERT_POS(256U, 256U);
+ ASSERT_POS(512U, 512U);
+ ASSERT_POS(1024U, 1024U);
+ ASSERT_POS(2048U, 2048U);
+ ASSERT_POS(4096U, 4096U);
+
+ free(p);
+
+#undef ASSERT_POS
+
+ // Second, do similar checking with allocations of various alignments. Also
+ // check that their sizes (which are different to allocations with normal
+ // alignment) are the same as the sizes of equivalent non-PHC allocations.
+
+#define ASSERT_ALIGN(a1, a2) \
+ p = (uint8_t*)GetPHCAllocation(8, (a1)); \
+ ASSERT_EQ((reinterpret_cast<uintptr_t>(p) & (kPageSize - 1)), \
+ kPageSize - (a2)); \
+ ASSERT_EQ(moz_malloc_usable_size(p), (a2)); \
+ free(p); \
+ p = (uint8_t*)moz_xmemalign((a1), 8); \
+ ASSERT_EQ(moz_malloc_usable_size(p), (a2)); \
+ free(p);
+
+ // On Win64 the smallest possible allocation is 16 bytes. On other platforms
+ // it is 8 bytes.
+#if defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
+ ASSERT_ALIGN(8U, 16U);
+#else
+ ASSERT_ALIGN(8U, 8U);
+#endif
+ ASSERT_ALIGN(16U, 16U);
+ ASSERT_ALIGN(32U, 32U);
+ ASSERT_ALIGN(64U, 64U);
+ ASSERT_ALIGN(128U, 128U);
+ ASSERT_ALIGN(256U, 256U);
+ ASSERT_ALIGN(512U, 512U);
+ ASSERT_ALIGN(1024U, 1024U);
+ ASSERT_ALIGN(2048U, 2048U);
+ ASSERT_ALIGN(4096U, 4096U);
+
+#undef ASSERT_ALIGN
+}
+
+TEST(PHC, TestPHCInfo)
+{
+ int stackVar;
+ phc::AddrInfo phcInfo;
+ jemalloc_ptr_info_t jeInfo;
+
+ // Test a default AddrInfo.
+ ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0ul,
+ false, false));
+
+ // Test some non-PHC allocation addresses.
+ ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(nullptr, &phcInfo));
+ ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
+ false, false));
+ ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(&stackVar, &phcInfo));
+ ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
+ false, false));
+
+ uint8_t* p = GetPHCAllocation(32);
+ if (!p) {
+ MOZ_CRASH("failed to get a PHC allocation");
+ }
+
+ // Test an in-use PHC allocation: first byte within it.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
+ ASSERT_EQ(moz_malloc_usable_size(p), 32ul);
+ jemalloc_ptr_info(p, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
+
+ // Test an in-use PHC allocation: last byte within it.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
+ ASSERT_EQ(moz_malloc_usable_size(p + 31), 32ul);
+ jemalloc_ptr_info(p + 31, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
+
+ // Test an in-use PHC allocation: last byte before it.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p - 1, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
+ ASSERT_EQ(moz_malloc_usable_size(p - 1), 0ul);
+ jemalloc_ptr_info(p - 1, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test an in-use PHC allocation: first byte on its allocation page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32 - kPageSize, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
+ jemalloc_ptr_info(p + 32 - kPageSize, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test an in-use PHC allocation: first byte in the following guard page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
+ jemalloc_ptr_info(p + 32, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test an in-use PHC allocation: last byte in the lower half of the
+ // following guard page.
+ ASSERT_TRUE(
+ ReplaceMalloc::IsPHCAllocation(p + 32 + (kPageSize / 2 - 1), &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
+ jemalloc_ptr_info(p + 32 + (kPageSize / 2 - 1), &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test an in-use PHC allocation: last byte in the preceding guard page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31 - kPageSize, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
+ jemalloc_ptr_info(p + 31 - kPageSize, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test an in-use PHC allocation: first byte in the upper half of the
+ // preceding guard page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(
+ p + 31 - kPageSize - (kPageSize / 2 - 1), &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, false));
+ jemalloc_ptr_info(p + 31 - kPageSize - (kPageSize / 2 - 1), &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ free(p);
+
+ // Test a freed PHC allocation: first byte within it.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
+
+ // Test a freed PHC allocation: last byte within it.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p + 31, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
+
+ // Test a freed PHC allocation: last byte before it.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p - 1, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p - 1, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test a freed PHC allocation: first byte on its allocation page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32 - kPageSize, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p + 32 - kPageSize, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test a freed PHC allocation: first byte in the following guard page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 32, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p + 32, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test a freed PHC allocation: last byte in the lower half of the following
+ // guard page.
+ ASSERT_TRUE(
+ ReplaceMalloc::IsPHCAllocation(p + 32 + (kPageSize / 2 - 1), &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p + 32 + (kPageSize / 2 - 1), &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test a freed PHC allocation: last byte in the preceding guard page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 31 - kPageSize, &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p + 31 - kPageSize, &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // Test a freed PHC allocation: first byte in the upper half of the preceding
+ // guard page.
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(
+ p + 31 - kPageSize - (kPageSize / 2 - 1), &phcInfo));
+ ASSERT_TRUE(
+ PHCInfoEq(phcInfo, phc::AddrInfo::Kind::GuardPage, p, 32ul, true, true));
+ jemalloc_ptr_info(p + 31 - kPageSize - (kPageSize / 2 - 1), &jeInfo);
+ ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
+
+ // There are no tests for `mKind == NeverAllocatedPage` because it's not
+ // possible to reliably get ahold of such a page.
+}
+
+TEST(PHC, TestPHCDisabling)
+{
+ uint8_t* p = GetPHCAllocation(32);
+ uint8_t* q = GetPHCAllocation(32);
+ if (!p || !q) {
+ MOZ_CRASH("failed to get a PHC allocation");
+ }
+
+ ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
+ ReplaceMalloc::DisablePHCOnCurrentThread();
+ ASSERT_FALSE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
+
+ // Test realloc() on a PHC allocation while PHC is disabled on the thread.
+ uint8_t* p2 = (uint8_t*)realloc(p, 128);
+ // The small realloc is fulfilled within the same page, but it does move.
+ ASSERT_TRUE(p2 == p - 96);
+ ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p2, nullptr));
+ uint8_t* p3 = (uint8_t*)realloc(p2, 8192);
+ // The big realloc is not in-place, and the result is not a PHC allocation.
+ ASSERT_TRUE(p3 != p2);
+ ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(p3, nullptr));
+ free(p3);
+
+ // Test free() on a PHC allocation while PHC is disabled on the thread.
+ free(q);
+
+ // These must not be PHC allocations.
+ uint8_t* r = GetPHCAllocation(32); // This will fail.
+ ASSERT_FALSE(!!r);
+
+ ReplaceMalloc::ReenablePHCOnCurrentThread();
+ ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
+}