[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