Files
jemalloc/src/jemalloc_cpp.cpp
Bruno Gonçalves 7ce8b9165d Accept NULL in free_sized() and free_aligned_sized()
free_sized() and free_aligned_sized() forward straight to sdallocx(), which
expects a non-NULL pointer and asserts on it in debug builds. C23 says both
should accept NULL and do nothing, like free(NULL) does, so a NULL argument
either trips that assert or feeds NULL into the dealloc path in release builds.

It is not hard to hit. glibc 2.41 ships free_sized()/free_aligned_sized(), and
a C++ sized delete of a null pointer compiles down to a free_sized() call. Once
jemalloc is preloaded its versions take over, and that NULL call takes down the
process. I ran into it with GTK4/GLib apps under LD_PRELOAD.

Check for NULL first, the way free() already does, and add an integration test
covering the NULL case for both functions.

While here, give free_aligned_sized() its own core.free_aligned_sized.entry
and .exit logging and call je_sdallocx_impl() directly rather than the
je_sdallocx() wrapper, so it mirrors free_sized() and no longer logs under
sdallocx. The C++ sized-delete paths (sizedDeleteImpl, alignedSizedDeleteImpl)
get the same treatment: log entry/exit unconditionally and guard the call with
likely(ptr != nullptr).
2026-06-29 16:35:49 -04:00

325 lines
7.7 KiB
C++

#include <exception>
#include <new>
// NOLINTBEGIN(misc-use-anonymous-namespace)
#ifdef __cplusplus
extern "C" {
#endif
#include "jemalloc/internal/jemalloc_preamble.h"
#include "jemalloc/internal/arena.h"
#include "jemalloc/internal/jemalloc_internal_externs.h"
#include "jemalloc/internal/jemalloc_internal_inlines_c.h"
#include "jemalloc/internal/prof.h"
#include "jemalloc/internal/tcache.h"
#ifdef __cplusplus
}
#endif
// All operators in this file are exported.
// Possibly alias hidden versions of malloc and sdallocx to avoid an extra plt
// thunk?
//
// extern __typeof (sdallocx) sdallocx_int
// __attribute ((alias ("sdallocx"),
// visibility ("hidden")));
//
// ... but it needs to work with jemalloc namespaces.
void *operator new(std::size_t size);
void *operator new[](std::size_t size);
void *operator new(std::size_t size, const std::nothrow_t &) noexcept;
void *operator new[](std::size_t size, const std::nothrow_t &) noexcept;
void operator delete(void *ptr) noexcept;
void operator delete[](void *ptr) noexcept;
void operator delete(void *ptr, const std::nothrow_t &) noexcept;
void operator delete[](void *ptr, const std::nothrow_t &) noexcept;
#if __cpp_sized_deallocation >= 201309
/* C++14's sized-delete operators. */
void operator delete(void *ptr, std::size_t size) noexcept;
void operator delete[](void *ptr, std::size_t size) noexcept;
#endif
#if __cpp_aligned_new >= 201606
/* C++17's over-aligned operators. */
void *operator new(std::size_t size, std::align_val_t);
void *operator new(
std::size_t size, std::align_val_t, const std::nothrow_t &) noexcept;
void *operator new[](std::size_t size, std::align_val_t);
void *operator new[](
std::size_t size, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete(void *ptr, std::align_val_t) noexcept;
void operator delete(
void *ptr, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete(void *ptr, std::size_t size, std::align_val_t al) noexcept;
void operator delete[](void *ptr, std::align_val_t) noexcept;
void operator delete[](
void *ptr, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete[](
void *ptr, std::size_t size, std::align_val_t al) noexcept;
#endif
JEMALLOC_NOINLINE
static void *
handleOOM(std::size_t size, bool nothrow) {
#ifdef JEMALLOC_INFALLIBLE_NEW
if (nothrow) {
return nullptr;
}
const char *huge_warning = (size >= ((std::size_t)1 << 30))
? "This may be caused by heap corruption, if the large size "
"is unexpected (suggest building with sanitizers for "
"debugging). "
: "";
safety_check_fail(
"<jemalloc>: Allocation of size %zu failed. %sAborting.\n",
size, huge_warning);
return nullptr;
#else
void *ptr = nullptr;
while (ptr == nullptr) {
std::new_handler handler = std::get_new_handler();
if (handler == nullptr)
break;
#ifdef JEMALLOC_HAVE_CXX_EXCEPTIONS
try {
handler();
} catch (const std::bad_alloc &) {
break;
}
#else
handler();
#endif
ptr = je_malloc(size);
}
if (ptr == nullptr && !nothrow) {
#ifdef JEMALLOC_HAVE_CXX_EXCEPTIONS
throw std::bad_alloc();
#else
std::terminate();
#endif
}
return ptr;
#endif
}
template <bool IsNoExcept>
JEMALLOC_NOINLINE static void *
fallbackNewImpl(std::size_t size) noexcept(IsNoExcept) {
void *ptr = malloc_default(size);
if (likely(ptr != nullptr)) {
return ptr;
}
return handleOOM(size, IsNoExcept);
}
template <bool IsNoExcept>
JEMALLOC_ALWAYS_INLINE void *
newImpl(std::size_t size) noexcept(IsNoExcept) {
LOG("core.operator_new.entry", "size: %zu", size);
void *ret = imalloc_fastpath(size, &fallbackNewImpl<IsNoExcept>);
LOG("core.operator_new.exit", "result: %p", ret);
return ret;
}
void *
operator new(std::size_t size) {
return newImpl<false>(size);
}
void *
operator new[](std::size_t size) {
return newImpl<false>(size);
}
void *
operator new(std::size_t size, const std::nothrow_t &) noexcept {
return newImpl<true>(size);
}
void *
operator new[](std::size_t size, const std::nothrow_t &) noexcept {
return newImpl<true>(size);
}
#if __cpp_aligned_new >= 201606
template <bool IsNoExcept>
JEMALLOC_ALWAYS_INLINE void *
alignedNewImpl(std::size_t size, std::align_val_t alignment) noexcept(
IsNoExcept) {
void *ptr = je_aligned_alloc(static_cast<std::size_t>(alignment), size);
if (likely(ptr != nullptr)) {
return ptr;
}
return handleOOM(size, IsNoExcept);
}
void *
operator new(std::size_t size, std::align_val_t alignment) {
return alignedNewImpl<false>(size, alignment);
}
void *
operator new[](std::size_t size, std::align_val_t alignment) {
return alignedNewImpl<false>(size, alignment);
}
void *
operator new(std::size_t size, std::align_val_t alignment,
const std::nothrow_t &) noexcept {
return alignedNewImpl<true>(size, alignment);
}
void *
operator new[](std::size_t size, std::align_val_t alignment,
const std::nothrow_t &) noexcept {
return alignedNewImpl<true>(size, alignment);
}
#endif // __cpp_aligned_new
void
operator delete(void *ptr) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete[](void *ptr) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete(void *ptr, const std::nothrow_t &) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete[](void *ptr, const std::nothrow_t &) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
#if __cpp_sized_deallocation >= 201309
JEMALLOC_ALWAYS_INLINE
void
sizedDeleteImpl(void *ptr, std::size_t size) noexcept {
LOG("core.operator_delete.entry", "ptr: %p, size: %zu", ptr, size);
if (likely(ptr != nullptr)) {
je_sdallocx_noflags(ptr, size);
}
LOG("core.operator_delete.exit", "");
}
void
operator delete(void *ptr, std::size_t size) noexcept {
sizedDeleteImpl(ptr, size);
}
void
operator delete[](void *ptr, std::size_t size) noexcept {
sizedDeleteImpl(ptr, size);
}
#endif // __cpp_sized_deallocation
#if __cpp_aligned_new >= 201606
JEMALLOC_ALWAYS_INLINE
void
alignedSizedDeleteImpl(
void *ptr, std::size_t size, std::align_val_t alignment) noexcept {
if (config_debug) {
assert(((size_t)alignment & ((size_t)alignment - 1)) == 0);
}
LOG("core.operator_delete.entry", "ptr: %p, size: %zu, alignment: %zu",
ptr, size, alignment);
if (likely(ptr != nullptr)) {
je_sdallocx_impl(ptr, size, MALLOCX_ALIGN(alignment));
}
LOG("core.operator_delete.exit", "");
}
void
operator delete(void *ptr, std::align_val_t) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete[](void *ptr, std::align_val_t) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete(void *ptr, std::align_val_t, const std::nothrow_t &) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete[](
void *ptr, std::align_val_t, const std::nothrow_t &) noexcept {
LOG("core.operator_delete.entry", "ptr: %p", ptr);
je_free_impl(ptr);
LOG("core.operator_delete.exit", "");
}
void
operator delete(
void *ptr, std::size_t size, std::align_val_t alignment) noexcept {
alignedSizedDeleteImpl(ptr, size, alignment);
}
void
operator delete[](
void *ptr, std::size_t size, std::align_val_t alignment) noexcept {
alignedSizedDeleteImpl(ptr, size, alignment);
}
#endif // __cpp_aligned_new
// NOLINTEND(misc-use-anonymous-namespace)