[llvm-branch-commits] [compiler-rt] [CopyProf] Add standalone allocator. (PR #235453)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Oct 25 07:43:38 PDT 2026
https://github.com/newgre updated https://github.com/llvm/llvm-project/pull/235453
>From 5e83b606d52de2d94e11d91840512bcf50ecdb0e Mon Sep 17 00:00:00 2001
From: jannewger <jannewger at gmail.com>
Date: Tue, 20 Oct 2026 22:22:39 +0200
Subject: [PATCH] [CopyProf] Add standalone allocator.
Detecting modifications of heap memory owned by a copy requires hooking
all (de)allocations and knowing allocation sizes. This adds a CopyProf
specific allocator based on the sanitizer_common allocator.
It provides surrogates for the libc heap functions, which are hooked up
to interceptors in a subsequent patch.
RFC: https://discourse.llvm.org/t/rfc-copysanitizer-csan-detecting-unneccessary-object-copies-at-runtime/91038
Previous PR: #232851
---
compiler-rt/lib/copyprof/CMakeLists.txt | 2 +
compiler-rt/lib/copyprof/copyprof.cpp | 2 +
.../lib/copyprof/copyprof_allocator.cpp | 312 ++++++++++++++++++
compiler-rt/lib/copyprof/copyprof_allocator.h | 45 +++
compiler-rt/lib/copyprof/tests/CMakeLists.txt | 1 +
.../tests/copyprof_allocator_test.cpp | 227 +++++++++++++
6 files changed, 589 insertions(+)
create mode 100644 compiler-rt/lib/copyprof/copyprof_allocator.cpp
create mode 100644 compiler-rt/lib/copyprof/copyprof_allocator.h
create mode 100644 compiler-rt/lib/copyprof/tests/copyprof_allocator_test.cpp
diff --git a/compiler-rt/lib/copyprof/CMakeLists.txt b/compiler-rt/lib/copyprof/CMakeLists.txt
index 16c3df30d8c8ed..c0ded0392c0b9a 100644
--- a/compiler-rt/lib/copyprof/CMakeLists.txt
+++ b/compiler-rt/lib/copyprof/CMakeLists.txt
@@ -2,6 +2,7 @@
set(COPYPROF_SOURCES
copyprof.cpp
+ copyprof_allocator.cpp
copyprof_interceptors.cpp
copyprof_interceptors_memintrinsics.cpp
copyprof_preinit.cpp
@@ -12,6 +13,7 @@ set(COPYPROF_SOURCES
)
set(COPYPROF_HEADERS
+ copyprof_allocator.h
copyprof_interceptors.h
copyprof_interceptors_memintrinsics.h
copyprof_interface_internal.h
diff --git a/compiler-rt/lib/copyprof/copyprof.cpp b/compiler-rt/lib/copyprof/copyprof.cpp
index 7dd5350440c1d5..0dddf05af70aa2 100644
--- a/compiler-rt/lib/copyprof/copyprof.cpp
+++ b/compiler-rt/lib/copyprof/copyprof.cpp
@@ -11,6 +11,7 @@
///
//===----------------------------------------------------------------------===//
+#include "copyprof_allocator.h"
#include "copyprof_interceptors.h"
#include "copyprof_interface_internal.h"
#include "copyprof_reporting.h"
@@ -52,6 +53,7 @@ static void Initialize() {
InitializeCommonFlags();
InitializeInterceptors();
InitializeShadowMemory();
+ InitializeCopyProfAllocator();
copyprof_init_is_running = false;
copyprof_is_initialized = true;
}
diff --git a/compiler-rt/lib/copyprof/copyprof_allocator.cpp b/compiler-rt/lib/copyprof/copyprof_allocator.cpp
new file mode 100644
index 00000000000000..f5baedb67ed9a8
--- /dev/null
+++ b/compiler-rt/lib/copyprof/copyprof_allocator.cpp
@@ -0,0 +1,312 @@
+//===-- copyprof_allocator.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// This file implements the standalone memory allocator for CopyProf, managing
+/// internal dynamic allocations required during profiling.
+///
+//===----------------------------------------------------------------------===//
+
+#include "copyprof_allocator.h"
+
+#include <pthread.h>
+
+#include "copyprof_interceptors.h"
+#include "copyprof_interface_internal.h"
+#include "copyprof_shadow.h"
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
+#include "sanitizer_common/sanitizer_allocator_interface.h"
+#include "sanitizer_common/sanitizer_allocator_report.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_platform_limits_posix.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+namespace __copyprof {
+namespace {
+
+using namespace __sanitizer;
+
+// Callbacks invoked by the sanitizer allocator whenever it maps or unmaps
+// memory. They are no-ops, as CopyProf does not need to track the allocator's
+// mappings.
+struct CopyProfMapUnmapCallback {
+ // Called when the primary allocator maps memory for a region from which it
+ // serves small and medium sized allocations.
+ void OnMap(uptr p, uptr size) const {}
+ // Called when the secondary allocator maps memory for an allocation that is
+ // too large for the primary allocator. Each such allocation is mmap()ed
+ // separately.
+ void OnMapSecondary(uptr p, uptr size, uptr user_begin,
+ uptr user_size) const {}
+ // Called when either allocator unmaps memory.
+ void OnUnmap(uptr p, uptr size) const {}
+};
+
+struct CopyProfMetadata {
+ // The requested allocation size in bytes.
+ uptr requested_size;
+};
+
+struct AllocatorParameters {
+ static constexpr uptr kSpaceBeg = ~(uptr)0;
+ static constexpr uptr kSpaceSize = 0x40000000000ULL;
+ static constexpr uptr kMetadataSize = sizeof(CopyProfMetadata);
+ static constexpr uptr kFlags = 0;
+ using SizeClassMap = DefaultSizeClassMap;
+ using MapUnmapCallback = CopyProfMapUnmapCallback;
+ using AddressSpaceView = LocalAddressSpaceView;
+};
+
+using PrimaryAllocator = SizeClassAllocator64<AllocatorParameters>;
+using Allocator = CombinedAllocator<PrimaryAllocator>;
+using AllocatorCache = Allocator::AllocatorCache;
+
+// Per-thread allocator cache. It is mmap()ed, and thus zero-initialized as
+// `AllocatorCache` requires, and committed back to `allocator` when its thread
+// exits. As CopyProf does not intercept thread creation, the cache is created
+// by the thread's first (de)allocation, which also arms the TSD destructor that
+// commits it back.
+pthread_key_t thread_cache_key;
+__attribute__((
+ tls_model("initial-exec"))) THREADLOCAL AllocatorCache* thread_cache;
+// Set once the exiting thread's cache has been committed back. Later TSD
+// destructors may still (de)allocate on this thread so they use the fallback
+// cache, as a re-created cache could leak.
+__attribute__((
+ tls_model("initial-exec"))) THREADLOCAL bool thread_cache_committed_back;
+
+uptr ThreadCacheSize() {
+ return RoundUpTo(sizeof(AllocatorCache), GetPageSizeCached());
+}
+
+// Returns the calling thread's allocator cache, or `nullptr` if it has already
+// been committed back.
+AllocatorCache* GetThreadCache() {
+ if (LIKELY(thread_cache))
+ return thread_cache;
+ if (UNLIKELY(thread_cache_committed_back))
+ return nullptr;
+ thread_cache =
+ static_cast<AllocatorCache*>(MmapOrDie(ThreadCacheSize(), __func__));
+ CHECK_EQ(0, pthread_setspecific(thread_cache_key, thread_cache));
+ return thread_cache;
+}
+
+// Custom allocator that keeps track of allocation sizes.
+struct CopyProfAllocator {
+ // Heap allocations must be at least 8-byte aligned so independent
+ // allocations never share a shadow byte (avoiding data races).
+ static_assert(kMinAlignment == 1 << kShadowScale);
+
+ Allocator allocator;
+ // Fallback allocator cache and mutex used in the slow path when no per-thread
+ // allocator cache is available.
+ StaticSpinMutex fallback_mutex;
+ AllocatorCache fallback_allocator_cache;
+
+ void Init() {
+ SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
+ allocator.Init(common_flags()->allocator_release_to_os_interval_ms);
+ }
+
+ CopyProfMetadata* GetMetadata(const void* ptr) {
+ return reinterpret_cast<CopyProfMetadata*>(allocator.GetMetaData(ptr));
+ }
+
+ // `align` must be a non-zero power of two; callers are responsible for
+ // validating it.
+ void* Allocate(uptr size, uptr align = kMinAlignment) {
+ if (UNLIKELY(!copyprof_is_initialized))
+ __copyprof_init_once();
+ align = (align < kMinAlignment) ? kMinAlignment : align;
+ CHECK(IsPowerOfTwo(align));
+ // C++ standard requires zero-sized allocations to return a non-null
+ // pointer.
+ if (size == 0)
+ size = 1;
+ void* allocated;
+ if (AllocatorCache* cache = GetThreadCache()) {
+ allocated = allocator.Allocate(cache, size, align);
+ } else {
+ SpinMutexLock lock(&fallback_mutex);
+ allocated = allocator.Allocate(&fallback_allocator_cache, size, align);
+ }
+
+ if (UNLIKELY(!allocated)) {
+ SetAllocatorOutOfMemory();
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ UNINITIALIZED BufferedStackTrace stack;
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
+ /*context=*/nullptr, common_flags()->fast_unwind_on_fatal);
+ ReportOutOfMemory(size, &stack);
+ }
+ GetMetadata(allocated)->requested_size = size;
+ return allocated;
+ }
+
+ void Deallocate(void* ptr) {
+ if (UNLIKELY(!ptr))
+ return;
+ if (AllocatorCache* cache = GetThreadCache()) {
+ allocator.Deallocate(cache, ptr);
+ } else {
+ SpinMutexLock lock(&fallback_mutex);
+ allocator.Deallocate(&fallback_allocator_cache, ptr);
+ }
+ }
+
+ void* Calloc(uptr num, uptr size) {
+ if (UNLIKELY(CheckForCallocOverflow(size, num))) {
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ UNINITIALIZED BufferedStackTrace stack;
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
+ /*context=*/nullptr, common_flags()->fast_unwind_on_fatal);
+ ReportCallocOverflow(num, size, &stack);
+ }
+ void* ptr = Allocate(num * size);
+ // Memory from the secondary allocator is freshly mmap()ed, and thus already
+ // zeroed.
+ if (ptr && allocator.FromPrimary(ptr))
+ REAL(memset)(ptr, 0, num * size);
+ return ptr;
+ }
+
+ // Implemented via `Allocate` and `Deallocate` rather than
+ // `CombinedAllocator::Reallocate`, s.t. metadata is updated as for any other
+ // (de)allocation.
+ void* Reallocate(void* old_ptr, uptr new_size) {
+ CHECK(old_ptr && new_size);
+ void* new_ptr = Allocate(new_size);
+ if (new_ptr) {
+ CHECK_NE(REAL(memcpy), nullptr);
+ uptr memcpy_size = Min(new_size, GetMetadata(old_ptr)->requested_size);
+ REAL(memcpy)(new_ptr, old_ptr, memcpy_size);
+ Deallocate(old_ptr);
+ }
+ return new_ptr;
+ }
+};
+
+CopyProfAllocator instance;
+
+// TSD destructor that commits the exiting thread's cache back to `allocator`.
+void CommitBackThreadCache(void* cache) {
+ thread_cache = nullptr;
+ thread_cache_committed_back = true;
+ instance.allocator.SwallowCache(static_cast<AllocatorCache*>(cache));
+ UnmapOrDie(cache, ThreadCacheSize());
+}
+
+} // namespace
+
+void InitializeCopyProfAllocator() {
+ instance.Init();
+ CHECK_EQ(0, pthread_key_create(&thread_cache_key, CommitBackThreadCache));
+}
+
+void* CopyProfAllocate(uptr size, uptr align) {
+ if (UNLIKELY(align == 0 || !IsPowerOfTwo(align))) {
+ errno = errno_EINVAL;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ UNINITIALIZED BufferedStackTrace stack;
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
+ /*context=*/nullptr, common_flags()->fast_unwind_on_fatal);
+ ReportInvalidAllocationAlignment(align, &stack);
+ }
+ return SetErrnoOnNull(instance.Allocate(size, align));
+}
+
+void CopyProfDeallocate(void* ptr, uptr size, uptr align) {
+ instance.Deallocate(ptr);
+}
+
+void* CopyProfMalloc(uptr size) {
+ return SetErrnoOnNull(instance.Allocate(size));
+}
+
+void CopyProfFree(void* ptr) { instance.Deallocate(ptr); }
+
+void* CopyProfCalloc(uptr num, uptr size) {
+ return SetErrnoOnNull(instance.Calloc(num, size));
+}
+
+void* CopyProfRealloc(void* ptr, uptr size) {
+ if (!ptr)
+ return SetErrnoOnNull(instance.Allocate(size));
+ if (size == 0) {
+ instance.Deallocate(ptr);
+ return nullptr;
+ }
+ return SetErrnoOnNull(instance.Reallocate(ptr, size));
+}
+
+void* CopyProfReallocarray(void* ptr, uptr num, uptr size) {
+ if (UNLIKELY(CheckForCallocOverflow(size, num))) {
+ errno = errno_ENOMEM;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ UNINITIALIZED BufferedStackTrace stack;
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
+ /*context=*/nullptr, common_flags()->fast_unwind_on_fatal);
+ ReportReallocArrayOverflow(num, size, &stack);
+ }
+ return CopyProfRealloc(ptr, num * size);
+}
+
+void* CopyProfValloc(uptr size) {
+ return SetErrnoOnNull(instance.Allocate(size, GetPageSizeCached()));
+}
+
+void* CopyProfPvalloc(uptr size) {
+ uptr page_size = GetPageSizeCached();
+ if (UNLIKELY(CheckForPvallocOverflow(size, page_size))) {
+ errno = errno_ENOMEM;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ UNINITIALIZED BufferedStackTrace stack;
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
+ /*context=*/nullptr, common_flags()->fast_unwind_on_fatal);
+ ReportPvallocOverflow(size, &stack);
+ }
+ // `pvalloc(0)` allocates one page.
+ size = size ? RoundUpTo(size, page_size) : page_size;
+ return SetErrnoOnNull(instance.Allocate(size, page_size));
+}
+
+int CopyProfPosixMemalign(void** memptr, uptr alignment, uptr size) {
+ if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
+ if (AllocatorMayReturnNull())
+ return errno_EINVAL;
+ UNINITIALIZED BufferedStackTrace stack;
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(),
+ /*context=*/nullptr, common_flags()->fast_unwind_on_fatal);
+ ReportInvalidPosixMemalignAlignment(alignment, &stack);
+ }
+ void* ptr = instance.Allocate(size, alignment);
+ // Running out of memory has already been handled by `Allocate`.
+ if (UNLIKELY(!ptr))
+ return errno_ENOMEM;
+ *memptr = ptr;
+ return 0;
+}
+
+uptr CopyProfMallocUsableSize(const void* ptr) {
+ // Return the requested size, and 0 for memory that was not allocated by
+ // CopyProf.
+ if (!ptr || instance.allocator.GetBlockBegin(ptr) != ptr)
+ return 0;
+ return instance.GetMetadata(ptr)->requested_size;
+}
+
+} // namespace __copyprof
diff --git a/compiler-rt/lib/copyprof/copyprof_allocator.h b/compiler-rt/lib/copyprof/copyprof_allocator.h
new file mode 100644
index 00000000000000..9daa8deb750ce6
--- /dev/null
+++ b/compiler-rt/lib/copyprof/copyprof_allocator.h
@@ -0,0 +1,45 @@
+//===-- copyprof_allocator.h ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// This file declares the standalone memory allocator interface for CopyProf,
+/// enabling isolated memory management for internal runtime structures.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef COPYPROF_ALLOCATOR_H
+#define COPYPROF_ALLOCATOR_H
+
+#include "copyprof_internal.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+namespace __copyprof {
+
+// Minimum alignment (in bytes) of allocations returned by the CopyProf
+// allocator.
+constexpr uptr kMinAlignment = 8;
+
+// Initializes the copyprof allocator at startup.
+void InitializeCopyProfAllocator();
+
+// CopyProf specific memory allocation / deallocation routines and libc
+// surrogates.
+void* CopyProfAllocate(uptr size, uptr align);
+void CopyProfDeallocate(void* ptr, uptr size, uptr align);
+void* CopyProfMalloc(uptr size);
+void CopyProfFree(void* ptr);
+void* CopyProfCalloc(uptr num, uptr size);
+void* CopyProfRealloc(void* ptr, uptr size);
+void* CopyProfReallocarray(void* ptr, uptr num, uptr size);
+void* CopyProfValloc(uptr size);
+void* CopyProfPvalloc(uptr size);
+int CopyProfPosixMemalign(void** memptr, uptr alignment, uptr size);
+uptr CopyProfMallocUsableSize(const void* ptr);
+
+} // namespace __copyprof
+
+#endif // COPYPROF_ALLOCATOR_H
diff --git a/compiler-rt/lib/copyprof/tests/CMakeLists.txt b/compiler-rt/lib/copyprof/tests/CMakeLists.txt
index 948d7a1cb88a7b..9d4536bdb025a0 100644
--- a/compiler-rt/lib/copyprof/tests/CMakeLists.txt
+++ b/compiler-rt/lib/copyprof/tests/CMakeLists.txt
@@ -19,6 +19,7 @@ file(GLOB COPYPROF_HEADERS ../*.h)
set(COPYPROF_NOINST_TEST_SOURCES
driver.cpp
+ copyprof_allocator_test.cpp
copyprof_shadow_test.cpp
copyprof_state_test.cpp)
diff --git a/compiler-rt/lib/copyprof/tests/copyprof_allocator_test.cpp b/compiler-rt/lib/copyprof/tests/copyprof_allocator_test.cpp
new file mode 100644
index 00000000000000..90471788689545
--- /dev/null
+++ b/compiler-rt/lib/copyprof/tests/copyprof_allocator_test.cpp
@@ -0,0 +1,227 @@
+//===-- copyprof_allocator_test.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "copyprof/copyprof_allocator.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "copyprof/copyprof_interface_internal.h"
+#include "gtest/gtest.h"
+#include "sanitizer_common/sanitizer_allocator.h"
+
+namespace __copyprof {
+namespace {
+
+class CopyProfAllocatorTest : public testing::Test {
+ protected:
+ void SetUp() override { __copyprof_init_once(); }
+};
+
+TEST_F(CopyProfAllocatorTest, ZeroSizedAllocation) {
+ void* zero_buf = CopyProfAllocate(/*size=*/0, /*align=*/8);
+ ASSERT_NE(zero_buf, nullptr);
+ CopyProfDeallocate(zero_buf, /*size=*/0, /*align=*/8);
+}
+
+TEST_F(CopyProfAllocatorTest, ThreadLocalAllocatorCacheAndDrainOnExit) {
+ auto worker = +[](void* /*arg*/) -> void* {
+ void* local_buf = CopyProfAllocate(/*size=*/64, /*align=*/8);
+ CopyProfDeallocate(local_buf, /*size=*/64, /*align=*/8);
+ return CopyProfAllocate(/*size=*/128, /*align=*/8);
+ };
+ pthread_t thread;
+ ASSERT_EQ(pthread_create(&thread, /*attr=*/nullptr, worker, /*arg=*/nullptr),
+ 0);
+ void* cross_thread_buf = nullptr;
+ ASSERT_EQ(pthread_join(thread, &cross_thread_buf), 0);
+ ASSERT_NE(cross_thread_buf, nullptr);
+ CopyProfDeallocate(cross_thread_buf, /*size=*/128, /*align=*/8);
+}
+
+// The exiting thread may still (de)allocate after its cache has been committed
+// back, e.g. in TSD destructors that run later.
+TEST_F(CopyProfAllocatorTest, AllocationAfterThreadCacheCommitBackSucceeds) {
+ static pthread_key_t key;
+ static void* late_buf;
+ late_buf = nullptr;
+ auto destructor = +[](void* value) {
+ if (value == &key) {
+ // Invoking `pthread_setspecific` forces `pthread_exit` to perform another
+ // pass over TSD destructors, resulting in execution of the code path
+ // below afterwards.
+ pthread_setspecific(key, &late_buf);
+ return;
+ }
+ late_buf = CopyProfMalloc(/*size=*/16);
+ CopyProfFree(late_buf);
+ };
+ ASSERT_EQ(pthread_key_create(&key, destructor), 0);
+ auto worker = +[](void* /*arg*/) -> void* {
+ // Allocate once to create the thread cache and arm its TSD destructor.
+ CopyProfFree(CopyProfMalloc(/*size=*/16));
+ pthread_setspecific(key, &key);
+ return nullptr;
+ };
+ pthread_t thread;
+ ASSERT_EQ(pthread_create(&thread, /*attr=*/nullptr, worker, /*arg=*/nullptr),
+ 0);
+ ASSERT_EQ(pthread_join(thread, /*thread_return=*/nullptr), 0);
+ pthread_key_delete(key);
+ EXPECT_NE(late_buf, nullptr);
+}
+
+TEST_F(CopyProfAllocatorTest, CallocReturnsZeroedMemory) {
+ constexpr uptr kSize = 32;
+ // Dirty a chunk and free it again, s.t. `CopyProfCalloc` likely reuses it.
+ void* dirty_buf = CopyProfMalloc(kSize);
+ ASSERT_NE(dirty_buf, nullptr);
+ memset(dirty_buf, /*c=*/0xab, kSize);
+ CopyProfFree(dirty_buf);
+
+ char* buf = static_cast<char*>(CopyProfCalloc(/*num=*/1, kSize));
+ ASSERT_NE(buf, nullptr);
+ for (uptr i = 0; i < kSize; ++i) EXPECT_EQ(buf[i], 0) << "at offset " << i;
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, CallocOverflowReturnsNull) {
+ const bool may_return_null = AllocatorMayReturnNull();
+ SetAllocatorMayReturnNull(true);
+ errno = 0;
+
+ // `num * size` overflows.
+ void* buf = CopyProfCalloc(/*num=*/2, /*size=*/~uptr{0});
+ const int calloc_errno = errno;
+ SetAllocatorMayReturnNull(may_return_null);
+
+ EXPECT_EQ(buf, nullptr);
+ EXPECT_EQ(calloc_errno, ENOMEM);
+}
+
+TEST_F(CopyProfAllocatorTest, ReallocPreservesContents) {
+ constexpr uptr kSize = 16;
+ char* buf = static_cast<char*>(CopyProfMalloc(kSize));
+ ASSERT_NE(buf, nullptr);
+ for (uptr i = 0; i < kSize; ++i) buf[i] = static_cast<char>(i);
+
+ char* resized_buf =
+ static_cast<char*>(CopyProfRealloc(buf, /*size=*/2 * kSize));
+ ASSERT_NE(resized_buf, nullptr);
+ for (uptr i = 0; i < kSize; ++i)
+ EXPECT_EQ(resized_buf[i], static_cast<char>(i)) << "at offset " << i;
+ CopyProfFree(resized_buf);
+}
+
+TEST_F(CopyProfAllocatorTest, ReallocOfNullptrAllocates) {
+ void* buf = CopyProfRealloc(/*ptr=*/nullptr, /*size=*/32);
+ EXPECT_NE(buf, nullptr);
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, ReallocToZeroSizeReturnsNull) {
+ void* buf = CopyProfMalloc(/*size=*/32);
+ ASSERT_NE(buf, nullptr);
+ EXPECT_EQ(CopyProfRealloc(buf, /*size=*/0), nullptr);
+}
+
+TEST_F(CopyProfAllocatorTest, AllocateWithInvalidAlignmentSetsEinval) {
+ const bool may_return_null = AllocatorMayReturnNull();
+ SetAllocatorMayReturnNull(true);
+
+ for (uptr align : {uptr{0}, uptr{3}}) {
+ errno = 0;
+ void* buf = CopyProfAllocate(/*size=*/32, align);
+ EXPECT_EQ(buf, nullptr) << "for align " << align;
+ EXPECT_EQ(errno, EINVAL) << "for align " << align;
+ }
+
+ SetAllocatorMayReturnNull(may_return_null);
+}
+
+TEST_F(CopyProfAllocatorTest, AllocateReturnsMemoryWithRequestedAlignment) {
+ constexpr uptr kAlignment = 256;
+ void* buf = CopyProfAllocate(/*size=*/16, kAlignment);
+ ASSERT_NE(buf, nullptr);
+ EXPECT_TRUE(IsAligned(reinterpret_cast<uptr>(buf), kAlignment));
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest,
+ PosixMemalignReturnsMemoryWithRequestedAlignment) {
+ constexpr uptr kAlignment = 64;
+ void* buf = nullptr;
+ ASSERT_EQ(CopyProfPosixMemalign(&buf, kAlignment, /*size=*/100), 0);
+ EXPECT_TRUE(IsAligned(reinterpret_cast<uptr>(buf), kAlignment));
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, PosixMemalignWithInvalidAlignmentReturnsEinval) {
+ const bool may_return_null = AllocatorMayReturnNull();
+ SetAllocatorMayReturnNull(true);
+ void* buf = nullptr;
+
+ // Not a multiple of `sizeof(void*)`.
+ const int result = CopyProfPosixMemalign(&buf, /*alignment=*/4, /*size=*/16);
+ SetAllocatorMayReturnNull(may_return_null);
+
+ EXPECT_EQ(result, EINVAL);
+ EXPECT_EQ(buf, nullptr);
+}
+
+TEST_F(CopyProfAllocatorTest, VallocReturnsPageAlignedMemory) {
+ void* buf = CopyProfValloc(/*size=*/16);
+ ASSERT_NE(buf, nullptr);
+ EXPECT_TRUE(IsAligned(reinterpret_cast<uptr>(buf), GetPageSizeCached()));
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, PvallocRoundsSizeUpToPageSize) {
+ void* buf = CopyProfPvalloc(/*size=*/1);
+ ASSERT_NE(buf, nullptr);
+ EXPECT_EQ(CopyProfMallocUsableSize(buf), GetPageSizeCached());
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, ReallocarrayAllocatesNumTimesSize) {
+ void* buf = CopyProfReallocarray(/*ptr=*/nullptr, /*num=*/4, /*size=*/8);
+ ASSERT_NE(buf, nullptr);
+ EXPECT_EQ(CopyProfMallocUsableSize(buf), 32u);
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, ReallocarrayOverflowReturnsNull) {
+ const bool may_return_null = AllocatorMayReturnNull();
+ SetAllocatorMayReturnNull(true);
+ errno = 0;
+
+ // `num * size` overflows.
+ void* buf =
+ CopyProfReallocarray(/*ptr=*/nullptr, /*num=*/2, /*size=*/~uptr{0});
+ const int reallocarray_errno = errno;
+ SetAllocatorMayReturnNull(may_return_null);
+
+ EXPECT_EQ(buf, nullptr);
+ EXPECT_EQ(reallocarray_errno, ENOMEM);
+}
+
+TEST_F(CopyProfAllocatorTest, MallocUsableSizeReturnsRequestedSize) {
+ void* buf = CopyProfMalloc(/*size=*/10);
+ ASSERT_NE(buf, nullptr);
+ EXPECT_EQ(CopyProfMallocUsableSize(buf), 10u);
+ CopyProfFree(buf);
+}
+
+TEST_F(CopyProfAllocatorTest, MallocUsableSizeOfUnownedMemoryIsZero) {
+ char stack_buf[16] = {0};
+ EXPECT_EQ(CopyProfMallocUsableSize(stack_buf), 0u);
+}
+
+} // namespace
+} // namespace __copyprof
More information about the llvm-branch-commits
mailing list