From 360efa21231fe8c3f0163ac57940960c8096d402 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Thu, 2 Dec 2021 14:49:32 +0000 Subject: [PATCH] export netbsd's reallocarr proposal. (#433) * export netbsd's reallocarr proposal. acts subtly differently from reallocarray, returns an error code and first argument as receiver. * not export by default * ci tests * apply suggestions * doc addition * Apply suggestions from code review Co-authored-by: Matthew Parkinson --- CMakeLists.txt | 8 +++ docs/BUILDING.md | 13 +++++ src/override/malloc.cc | 41 ++++++++++++- src/test/func/malloc/malloc.cc | 103 +++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 872ca6f..354d9be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ option(USE_SNMALLOC_STATS "Track allocation stats" OFF) option(SNMALLOC_CI_BUILD "Disable features not sensible for CI" OFF) option(SNMALLOC_QEMU_WORKAROUND "Disable using madvise(DONT_NEED) to zero memory on Linux" Off) option(SNMALLOC_USE_CXX17 "Build as C++17 for legacy support." OFF) +option(SNMALLOC_NO_REALLOCARRAY "Build without reallocarray exported" ON) +option(SNMALLOC_NO_REALLOCARR "Build without reallocarr exported" ON) # Options that apply only if we're not building the header-only library cmake_dependent_option(SNMALLOC_RUST_SUPPORT "Build static library for rust" OFF "NOT SNMALLOC_HEADER_ONLY_LIBRARY" OFF) cmake_dependent_option(SNMALLOC_STATIC_LIBRARY "Build static libraries" ON "NOT SNMALLOC_HEADER_ONLY_LIBRARY" OFF) @@ -195,6 +197,12 @@ add_as_define(USE_SNMALLOC_STATS) add_as_define(SNMALLOC_QEMU_WORKAROUND) add_as_define(SNMALLOC_CI_BUILD) add_as_define(SNMALLOC_PLATFORM_HAS_GETENTROPY) +if (SNMALLOC_NO_REALLOCARRAY) + add_as_define(SNMALLOC_NO_REALLOCARRAY) +endif() +if (SNMALLOC_NO_REALLOCARR) + add_as_define(SNMALLOC_NO_REALLOCARR) +endif() target_compile_definitions(snmalloc INTERFACE $<$:MALLOC_USABLE_SIZE_QUALIFIER=const>) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index bad4b57..849a4c8 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -113,3 +113,16 @@ You will also need to compile the relevant parts of snmalloc itself. Create a ne #include "snmalloc/src/override/malloc.cc" #include "snmalloc/src/override/new.cc" ``` + +To enable the `reallocarray` symbol export, this can be added to your cmake command line. + +``` +-DSNMALLOC_NO_REALLOCARRAY=OFF +``` + +likewise for `reallocarr`. + +``` +-DSNMALLOC_NO_REALLOCARR=OFF +``` + diff --git a/src/override/malloc.cc b/src/override/malloc.cc index 658e2ba..f40b2d8 100644 --- a/src/override/malloc.cc +++ b/src/override/malloc.cc @@ -101,7 +101,7 @@ extern "C" return p; } -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) +#if !defined(SNMALLOC_NO_REALLOCARRAY) SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(reallocarray)(void* ptr, size_t nmemb, size_t size) { @@ -116,6 +116,45 @@ extern "C" } #endif +#if !defined(SNMALLOC_NO_REALLOCARR) + SNMALLOC_EXPORT int + SNMALLOC_NAME_MANGLE(reallocarr)(void* ptr_, size_t nmemb, size_t size) + { + int err = errno; + auto& a = ThreadAlloc::get(); + bool overflow = false; + size_t sz = bits::umul(size, nmemb, overflow); + if (sz == 0) + { + errno = err; + return 0; + } + if (overflow) + { + errno = err; + return EOVERFLOW; + } + + void** ptr = reinterpret_cast(ptr_); + void* p = a.alloc(sz); + if (p == nullptr) + { + errno = ENOMEM; + return ENOMEM; + } + + sz = bits::min(sz, a.alloc_size(*ptr)); + // Guard memcpy as GCC is assuming not nullptr for ptr after the memcpy + // otherwise. + if (sz != 0) + memcpy(p, *ptr, sz); + errno = err; + a.dealloc(*ptr); + *ptr = p; + return 0; + } +#endif + SNMALLOC_EXPORT void* SNMALLOC_NAME_MANGLE(memalign)(size_t alignment, size_t size) { diff --git a/src/test/func/malloc/malloc.cc b/src/test/func/malloc/malloc.cc index 80667f3..a672a25 100644 --- a/src/test/func/malloc/malloc.cc +++ b/src/test/func/malloc/malloc.cc @@ -2,6 +2,8 @@ #include #define SNMALLOC_NAME_MANGLE(a) our_##a +#undef SNMALLOC_NO_REALLOCARRAY +#undef SNMALLOC_NO_REALLOCARR #include "../../../override/malloc.cc" using namespace snmalloc; @@ -138,6 +140,60 @@ void test_memalign(size_t size, size_t align, int err, bool null) check_result(size, align, p, err, null); } +void test_reallocarray(void* p, size_t nmemb, size_t size, int err, bool null) +{ + size_t old_size = 0; + size_t tsize = nmemb * size; + if (p != nullptr) + old_size = our_malloc_usable_size(p); + + printf("reallocarray(%p(%zu), %zu)\n", p, old_size, tsize); + errno = SUCCESS; + auto new_p = our_reallocarray(p, nmemb, size); + if (new_p == nullptr && tsize != 0) + our_free(p); + check_result(tsize, 1, new_p, err, null); +} + +void test_reallocarr( + size_t size_old, size_t nmemb, size_t size, int err, bool null) +{ + void* p = nullptr; + + if (size_old != (size_t)~0) + p = our_malloc(size_old); + errno = SUCCESS; + int r = our_reallocarr(&p, nmemb, size); + if (r != err) + { + printf("reallocarr failed! expected %d got %d\n", err, r); + abort(); + } + + printf("reallocarr(%p(%zu), %zu)\n", p, nmemb, size); + check_result(nmemb * size, 1, p, err, null); + p = our_malloc(size); + if (!p) + { + return; + } + for (size_t i = 1; i < size; i++) + static_cast(p)[i] = 1; + our_reallocarr(&p, nmemb, size); + if (r != SUCCESS) + our_free(p); + + for (size_t i = 1; i < size; i++) + { + if (static_cast(p)[i] != 1) + { + printf("data consistency failed! at %zu", i); + abort(); + } + } + our_free(p); +} + int main(int argc, char** argv) { UNUSED(argc); @@ -229,6 +285,53 @@ int main(int argc, char** argv) test_posix_memalign(0, align + 1, EINVAL, true); } + test_reallocarray(nullptr, 1, 0, SUCCESS, false); + for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++) + { + const size_t size = bits::one_at_bit(sc); + test_reallocarray(our_malloc(size), 1, size, SUCCESS, false); + test_reallocarray(our_malloc(size), 1, 0, SUCCESS, false); + test_reallocarray(nullptr, 1, size, SUCCESS, false); + test_reallocarray(our_malloc(size), 1, ((size_t)-1) / 2, ENOMEM, true); + for (smallsizeclass_t sc2 = 0; sc2 < (MAX_SMALL_SIZECLASS_BITS + 4); sc2++) + { + const size_t size2 = bits::one_at_bit(sc2); + test_reallocarray(our_malloc(size), 1, size2, SUCCESS, false); + test_reallocarray(our_malloc(size + 1), 1, size2, SUCCESS, false); + } + } + + test_reallocarr((size_t)~0, 1, 0, SUCCESS, false); + test_reallocarr((size_t)~0, 1, 16, SUCCESS, false); + + for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++) + { + const size_t size = bits::one_at_bit(sc); + test_reallocarr(size, 1, size, SUCCESS, false); + test_reallocarr(size, 1, 0, SUCCESS, false); + test_reallocarr(size, 2, size, SUCCESS, false); + void* p = our_malloc(size); + if (p == nullptr) + { + printf("realloc alloc failed with %zu\n", size); + abort(); + } + int r = our_reallocarr(&p, 1, ((size_t)-1) / 2); + if (r != ENOMEM) + { + printf("expected failure on allocation\n"); + abort(); + } + our_free(p); + + for (smallsizeclass_t sc2 = 0; sc2 < (MAX_SMALL_SIZECLASS_BITS + 4); sc2++) + { + const size_t size2 = bits::one_at_bit(sc2); + printf("size1: %zu, size2:%zu\n", size, size2); + test_reallocarr(size, 1, size2, SUCCESS, false); + } + } + if (our_malloc_usable_size(nullptr) != 0) { printf("malloc_usable_size(nullptr) should be zero");