mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-07-07 22:37:16 +03:00
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).
This commit is contained in:
parent
e8a0d2b477
commit
7ce8b9165d
4 changed files with 67 additions and 10 deletions
|
|
@ -331,6 +331,7 @@ endif
|
|||
TESTS_INTEGRATION := $(srcroot)test/integration/aligned_alloc.c \
|
||||
$(srcroot)test/integration/allocated.c \
|
||||
$(srcroot)test/integration/extent.c \
|
||||
$(srcroot)test/integration/free_sized.c \
|
||||
$(srcroot)test/integration/malloc.c \
|
||||
$(srcroot)test/integration/mallocx.c \
|
||||
$(srcroot)test/integration/MALLOCX_ARENA.c \
|
||||
|
|
|
|||
|
|
@ -1072,14 +1072,35 @@ JEMALLOC_EXPORT void JEMALLOC_NOTHROW
|
|||
je_free_sized(void *ptr, size_t size) {
|
||||
LOG("core.free_sized.entry", "ptr: %p, size: %zu", ptr, size);
|
||||
|
||||
je_sdallocx_noflags(ptr, size);
|
||||
/*
|
||||
* free_sized(NULL, size) is a no-op, like free(NULL). Do the NULL
|
||||
* check here rather than deferring to the je_free_impl() fallback:
|
||||
* without a size hint, free_fastpath() does an emap lookup that NULL
|
||||
* fails, so it reaches free_default(), which handles NULL. With a
|
||||
* size hint, the fast path skips that lookup and falls through to
|
||||
* sdallocx_default(), which expects a non-NULL pointer.
|
||||
*/
|
||||
if (likely(ptr != NULL)) {
|
||||
je_sdallocx_noflags(ptr, size);
|
||||
}
|
||||
|
||||
LOG("core.free_sized.exit", "");
|
||||
}
|
||||
|
||||
JEMALLOC_EXPORT void JEMALLOC_NOTHROW
|
||||
je_free_aligned_sized(void *ptr, size_t alignment, size_t size) {
|
||||
je_sdallocx(ptr, size, /* flags */ MALLOCX_ALIGN(alignment));
|
||||
LOG("core.free_aligned_sized.entry",
|
||||
"ptr: %p, alignment: %zu, size: %zu", ptr, alignment, size);
|
||||
|
||||
/*
|
||||
* Same as je_free_sized() above: the sdallocx path expects a non-NULL
|
||||
* pointer, so handle the C23 free_aligned_sized(NULL, ...) no-op here.
|
||||
*/
|
||||
if (likely(ptr != NULL)) {
|
||||
je_sdallocx_impl(ptr, size, /* flags */ MALLOCX_ALIGN(alignment));
|
||||
}
|
||||
|
||||
LOG("core.free_aligned_sized.exit", "");
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -231,12 +231,11 @@ operator delete[](void *ptr, const std::nothrow_t &) noexcept {
|
|||
JEMALLOC_ALWAYS_INLINE
|
||||
void
|
||||
sizedDeleteImpl(void *ptr, std::size_t size) noexcept {
|
||||
if (unlikely(ptr == nullptr)) {
|
||||
return;
|
||||
}
|
||||
LOG("core.operator_delete.entry", "ptr: %p, size: %zu", ptr, size);
|
||||
|
||||
je_sdallocx_noflags(ptr, size);
|
||||
if (likely(ptr != nullptr)) {
|
||||
je_sdallocx_noflags(ptr, size);
|
||||
}
|
||||
|
||||
LOG("core.operator_delete.exit", "");
|
||||
}
|
||||
|
|
@ -262,13 +261,12 @@ alignedSizedDeleteImpl(
|
|||
if (config_debug) {
|
||||
assert(((size_t)alignment & ((size_t)alignment - 1)) == 0);
|
||||
}
|
||||
if (unlikely(ptr == nullptr)) {
|
||||
return;
|
||||
}
|
||||
LOG("core.operator_delete.entry", "ptr: %p, size: %zu, alignment: %zu",
|
||||
ptr, size, alignment);
|
||||
|
||||
je_sdallocx_impl(ptr, size, MALLOCX_ALIGN(alignment));
|
||||
if (likely(ptr != nullptr)) {
|
||||
je_sdallocx_impl(ptr, size, MALLOCX_ALIGN(alignment));
|
||||
}
|
||||
|
||||
LOG("core.operator_delete.exit", "");
|
||||
}
|
||||
|
|
|
|||
37
test/integration/free_sized.c
Normal file
37
test/integration/free_sized.c
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include "test/jemalloc_test.h"
|
||||
|
||||
TEST_BEGIN(test_free_sized) {
|
||||
void *p = mallocx(42, 0);
|
||||
expect_ptr_not_null(p, "Unexpected mallocx() failure");
|
||||
free_sized(p, 42);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
TEST_BEGIN(test_free_aligned_sized) {
|
||||
size_t alignment = 0x100;
|
||||
void *p = mallocx(42, MALLOCX_ALIGN(alignment));
|
||||
expect_ptr_not_null(p, "Unexpected mallocx() failure");
|
||||
free_aligned_sized(p, alignment, 42);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
TEST_BEGIN(test_free_sized_null) {
|
||||
/*
|
||||
* C23 specifies that free_sized(NULL, size) and
|
||||
* free_aligned_sized(NULL, alignment, size) do nothing, just as
|
||||
* free(NULL) does. The size argument is ignored for a NULL pointer.
|
||||
*/
|
||||
free_sized(NULL, 0);
|
||||
free_sized(NULL, 42);
|
||||
free_aligned_sized(NULL, 0x100, 0);
|
||||
free_aligned_sized(NULL, 0x100, 42);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int
|
||||
main(void) {
|
||||
return test(
|
||||
test_free_sized,
|
||||
test_free_aligned_sized,
|
||||
test_free_sized_null);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue