From 2900cfa50ab159d75c40a3700c22c460664adbfe Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 5 Mar 2025 19:04:09 +0100 Subject: [PATCH] MEM_guardedalloc: Add template 'type-safe' versions of MEM_mallocN. Same thing as for `MEM_callocN` and `MEM_freeN` in dd168a35c5, allows to reduce type verbosity, and increase type safety. --- intern/guardedalloc/MEM_guardedalloc.h | 57 +++++++++++++++++-- intern/guardedalloc/intern/mallocn.cc | 24 ++++++-- .../intern/mallocn_guarded_impl.cc | 2 +- .../mallocn_intern_function_pointers.hh | 16 ++++++ .../intern/mallocn_lockfree_impl.cc | 2 +- 5 files changed, 89 insertions(+), 12 deletions(-) diff --git a/intern/guardedalloc/MEM_guardedalloc.h b/intern/guardedalloc/MEM_guardedalloc.h index 36eee8f8ef5..80cb8d27a4f 100644 --- a/intern/guardedalloc/MEM_guardedalloc.h +++ b/intern/guardedalloc/MEM_guardedalloc.h @@ -114,7 +114,7 @@ void *MEM_calloc_arrayN(size_t len, * Allocate a block of memory of size len, with tag name str. The * name must be a static, because only a pointer to it is stored! */ -extern void *(*MEM_mallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT +void *MEM_mallocN(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); /** @@ -122,9 +122,9 @@ extern void *(*MEM_mallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_ * aborting in case of integer overflow to prevent vulnerabilities. The * name must be a static, because only a pointer to it is stored! */ -extern void *(*MEM_malloc_arrayN)(size_t len, - size_t size, - const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT +void *MEM_malloc_arrayN(size_t len, + size_t size, + const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3); /** @@ -403,6 +403,55 @@ template inline T *MEM_calloc_arrayN(const size_t length, const char MEM_calloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name)); } +/** + * Allocate uninitialized memory for an object of type #T. The constructor of #T is not called, + * therefore this must only be used with trivial types (like all C types). + * + * When allocating an enforced specific amount of bytes, the C version of this function should be + * used instead. While this should be avoided in C++ code, it is still required in some cases, e.g. + * for ID allocation based on #IDTypeInfo::struct_size. + * + * #MEM_freeN must be used to free a pointer returned by this call. Calling #MEM_delete on it is + * illegal. + */ +template inline T *MEM_mallocN(const char *allocation_name) +{ +# ifdef _MSC_VER + /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more + * specifically, non-trivially copyable, likely because the default copy constructors are + * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. + * + * So for now, use a more restricted check on MSVC, should still catch most of actual invalid + * cases. */ + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + return static_cast(MEM_malloc_arrayN_aligned(1, sizeof(T), alignof(T), allocation_name)); +} + +/** + * Type-safe version of #MEM_malloc_arrayN/#MEM_malloc_array_alignedN. + */ +template inline T *MEM_malloc_arrayN(const size_t length, const char *allocation_name) +{ +# ifdef _MSC_VER + /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more + * specifically, non-trivially copyable, likely because the default copy constructors are + * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. + * + * So for now, use a more restricted check on MSVC, should still catch most of actual invalid + * cases. */ + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + return static_cast( + MEM_malloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name)); +} + /** * Allocate memory for an object of type #T and memory-copy `other` into it. * Only applicable for trivial types. diff --git a/intern/guardedalloc/intern/mallocn.cc b/intern/guardedalloc/intern/mallocn.cc index 6394ee62411..d09e908b918 100644 --- a/intern/guardedalloc/intern/mallocn.cc +++ b/intern/guardedalloc/intern/mallocn.cc @@ -43,8 +43,10 @@ void *(*mem_guarded::internal::mem_callocN)(size_t len, const char *str) = MEM_l void *(*mem_guarded::internal::mem_calloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_calloc_arrayN; -void *(*MEM_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN; -void *(*MEM_malloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_malloc_arrayN; +void *(*mem_guarded::internal::mem_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN; +void *(*mem_guarded::internal::mem_malloc_arrayN)(size_t len, + size_t size, + const char *str) = MEM_lockfree_malloc_arrayN; void *(*mem_guarded::internal::mem_mallocN_aligned_ex)(size_t len, size_t alignment, const char *str, @@ -123,6 +125,16 @@ void *MEM_calloc_arrayN(size_t len, size_t size, const char *str) return mem_calloc_arrayN(len, size, str); } +void *MEM_mallocN(size_t len, const char *str) +{ + return mem_mallocN(len, str); +} + +void *MEM_malloc_arrayN(size_t len, size_t size, const char *str) +{ + return mem_malloc_arrayN(len, size, str); +} + void *MEM_mallocN_aligned(size_t len, size_t alignment, const char *str) { return mem_mallocN_aligned_ex(len, alignment, str, AllocationType::ALLOC_FREE); @@ -164,8 +176,8 @@ void MEM_use_lockfree_allocator() MEM_recallocN_id = MEM_lockfree_recallocN_id; mem_callocN = MEM_lockfree_callocN; mem_calloc_arrayN = MEM_lockfree_calloc_arrayN; - MEM_mallocN = MEM_lockfree_mallocN; - MEM_malloc_arrayN = MEM_lockfree_malloc_arrayN; + mem_mallocN = MEM_lockfree_mallocN; + mem_malloc_arrayN = MEM_lockfree_malloc_arrayN; mem_mallocN_aligned_ex = MEM_lockfree_mallocN_aligned; MEM_malloc_arrayN_aligned = MEM_lockfree_malloc_arrayN_aligned; MEM_calloc_arrayN_aligned = MEM_lockfree_calloc_arrayN_aligned; @@ -200,8 +212,8 @@ void MEM_use_guarded_allocator() MEM_recallocN_id = MEM_guarded_recallocN_id; mem_callocN = MEM_guarded_callocN; mem_calloc_arrayN = MEM_guarded_calloc_arrayN; - MEM_mallocN = MEM_guarded_mallocN; - MEM_malloc_arrayN = MEM_guarded_malloc_arrayN; + mem_mallocN = MEM_guarded_mallocN; + mem_malloc_arrayN = MEM_guarded_malloc_arrayN; mem_mallocN_aligned_ex = MEM_guarded_mallocN_aligned; MEM_malloc_arrayN_aligned = MEM_guarded_malloc_arrayN_aligned; MEM_calloc_arrayN_aligned = MEM_guarded_calloc_arrayN_aligned; diff --git a/intern/guardedalloc/intern/mallocn_guarded_impl.cc b/intern/guardedalloc/intern/mallocn_guarded_impl.cc index d8208ada944..2aaea362764 100644 --- a/intern/guardedalloc/intern/mallocn_guarded_impl.cc +++ b/intern/guardedalloc/intern/mallocn_guarded_impl.cc @@ -720,7 +720,7 @@ static void *mem_guarded_malloc_arrayN_aligned(const size_t len, return nullptr; } if (alignment <= MEM_MIN_CPP_ALIGNMENT) { - return mem_callocN(r_bytes_num, str); + return mem_mallocN(r_bytes_num, str); } return MEM_mallocN_aligned(r_bytes_num, alignment, str); } diff --git a/intern/guardedalloc/intern/mallocn_intern_function_pointers.hh b/intern/guardedalloc/intern/mallocn_intern_function_pointers.hh index f91b74c425d..b1a07294c86 100644 --- a/intern/guardedalloc/intern/mallocn_intern_function_pointers.hh +++ b/intern/guardedalloc/intern/mallocn_intern_function_pointers.hh @@ -36,6 +36,22 @@ extern void *(*mem_calloc_arrayN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3); +/** + * Internal implementation of #MEM_mallocN, exposed because public #MEM_mallocN cannot be a + * function pointer, to allow its overload by C++ template version. + */ +extern void *(*mem_mallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT + ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); + +/** + * Internal implementation of #MEM_malloc_arrayN, exposed because public #MEM_malloc_arrayN cannot + * be a function pointer, to allow its overload by C++ template version. + */ +extern void *(*mem_malloc_arrayN)(size_t len, + size_t size, + const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT + ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(3); + /** Internal implementation of #MEM_mallocN_aligned, exposed because #MEM_new needs access to it. */ extern void *(*mem_mallocN_aligned_ex)(size_t len, diff --git a/intern/guardedalloc/intern/mallocn_lockfree_impl.cc b/intern/guardedalloc/intern/mallocn_lockfree_impl.cc index e6057e8eec4..8d0ae31df98 100644 --- a/intern/guardedalloc/intern/mallocn_lockfree_impl.cc +++ b/intern/guardedalloc/intern/mallocn_lockfree_impl.cc @@ -474,7 +474,7 @@ static void *mem_lockfree_malloc_arrayN_aligned(const size_t len, return nullptr; } if (alignment <= MEM_MIN_CPP_ALIGNMENT) { - return mem_callocN(r_bytes_num, str); + return mem_mallocN(r_bytes_num, str); } void *ptr = MEM_mallocN_aligned(r_bytes_num, alignment, str); return ptr;