MEM_guardedalloc: Refactor to add more type-safety.
The main goal of these changes are to improve static (i.e. build-time) checks on whether a given data can be allocated and freed with `malloc` and `free` (C-style), or requires proper C++-style construction and destruction (`new` and `delete`). * Add new `MEM_malloc_arrayN_aligned` API. * Make `MEM_freeN` a template function in C++, which does static assert on type triviality. * Add `MEM_SAFE_DELETE`, similar to `MEM_SAFE_FREE` but calling `MEM_delete`. The changes to `MEM_freeN` was painful and useful, as it allowed to fix a bunch of invalid calls in existing codebase already. It also highlighted a fair amount of places where it is called to free incomplete type pointers, which is likely a sign of badly designed code (there should rather be an API to destroy and free these data then, if the data type is not fully publicly exposed). For now, these are 'worked around' by explicitly casting the freed pointers to `void *` in these cases - which also makes them easy to search for. Some of these will be addressed separately (see blender/blender!134765). Finally, MSVC seems to consider structs defining new/delete operators (e.g. by using the `MEM_CXX_CLASS_ALLOC_FUNCS` macro) as non-trivial. This does not seem to follow the definition of type triviality, so for now static type checking in `MEM_freeN` has been disabled for Windows. We'll likely have to do the same with type-safe `MEM_[cm]allocN` API being worked on in blender/blender!134771 Based on ideas from Brecht in blender/blender!134452 Pull Request: https://projects.blender.org/blender/blender/pulls/134463
This commit is contained in:
committed by
Bastien Montagne
parent
6be8dd16e7
commit
48e26c3afe
@@ -136,6 +136,16 @@ void *MEM_mallocN_aligned(size_t len,
|
||||
const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
|
||||
|
||||
/**
|
||||
* Allocate an aligned block of memory that remains uninitialized.
|
||||
*/
|
||||
extern void *(*MEM_malloc_arrayN_aligned)(
|
||||
size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2)
|
||||
ATTR_NONNULL(4);
|
||||
|
||||
/**
|
||||
* Allocate an aligned block of memory that is initialized with zeros.
|
||||
*/
|
||||
@@ -190,11 +200,9 @@ extern size_t (*MEM_get_peak_memory)(void) ATTR_WARN_UNUSED_RESULT;
|
||||
#ifdef __cplusplus
|
||||
# define MEM_SAFE_FREE(v) \
|
||||
do { \
|
||||
static_assert(std::is_pointer_v<std::decay_t<decltype(v)>>); \
|
||||
void **_v = (void **)&(v); \
|
||||
if (*_v) { \
|
||||
MEM_freeN(*_v); \
|
||||
*_v = NULL; \
|
||||
if (v) { \
|
||||
MEM_freeN<std::remove_pointer_t<std::decay_t<decltype(v)>>>(v); \
|
||||
(v) = nullptr; \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
@@ -334,6 +342,18 @@ template<typename T> inline void MEM_delete(const T *ptr)
|
||||
mem_guarded::internal::AllocationType::NEW_DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper shortcut to #MEM_delete, that also ensures that the target pointer is set to nullptr
|
||||
* after deleting it.
|
||||
*/
|
||||
# define MEM_SAFE_DELETE(v) \
|
||||
do { \
|
||||
if (v) { \
|
||||
MEM_delete(v); \
|
||||
(v) = nullptr; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Allocate zero-initialized memory for an object of type #T. The constructor of #T is not called,
|
||||
* therefore this should only be used with trivial types (like all C types).
|
||||
@@ -377,6 +397,25 @@ template<typename T> inline T *MEM_cnew(const char *allocation_name, const T &ot
|
||||
return new_object;
|
||||
}
|
||||
|
||||
template<typename T> inline void MEM_freeN(T *ptr)
|
||||
{
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
mem_guarded::internal::mem_freeN_ex(const_cast<void *>(ptr),
|
||||
mem_guarded::internal::AllocationType::ALLOC_FREE);
|
||||
}
|
||||
else {
|
||||
# ifndef _WIN32
|
||||
/* MSVC seems to consider C-style types using the MEM_CXX_CLASS_ALLOC_FUNCS as non-trivial. GCC
|
||||
* and clang (both on linux and OSX) do not.
|
||||
*
|
||||
* So for now, disable the triviality check on Windows. */
|
||||
static_assert(std::is_trivial_v<T>, "For non-trivial types, MEM_delete must be used.");
|
||||
# endif
|
||||
mem_guarded::internal::mem_freeN_ex(const_cast<void *>(static_cast<const void *>(ptr)),
|
||||
mem_guarded::internal::AllocationType::ALLOC_FREE);
|
||||
}
|
||||
}
|
||||
|
||||
/** Allocation functions (for C++ only). */
|
||||
# define MEM_CXX_CLASS_ALLOC_FUNCS(_id) \
|
||||
public: \
|
||||
|
||||
@@ -48,6 +48,10 @@ void *(*mem_guarded::internal::mem_mallocN_aligned_ex)(size_t len,
|
||||
const char *str,
|
||||
AllocationType allocation_type) =
|
||||
MEM_lockfree_mallocN_aligned;
|
||||
void *(*MEM_malloc_arrayN_aligned)(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) = MEM_lockfree_malloc_arrayN_aligned;
|
||||
void *(*MEM_calloc_arrayN_aligned)(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
@@ -146,6 +150,7 @@ void MEM_use_lockfree_allocator()
|
||||
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;
|
||||
MEM_printmemlist_pydict = MEM_lockfree_printmemlist_pydict;
|
||||
MEM_printmemlist = MEM_lockfree_printmemlist;
|
||||
@@ -181,6 +186,7 @@ void MEM_use_guarded_allocator()
|
||||
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;
|
||||
MEM_printmemlist_pydict = MEM_guarded_printmemlist_pydict;
|
||||
MEM_printmemlist = MEM_guarded_printmemlist;
|
||||
|
||||
@@ -702,13 +702,13 @@ void *MEM_guarded_calloc_arrayN(size_t len, size_t size, const char *str)
|
||||
return MEM_guarded_callocN(total_size, str);
|
||||
}
|
||||
|
||||
void *MEM_guarded_calloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
static void *mem_guarded_malloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str,
|
||||
size_t &r_bytes_num)
|
||||
{
|
||||
size_t bytes_num;
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &bytes_num))) {
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &r_bytes_num))) {
|
||||
print_error(
|
||||
"Calloc array aborted due to integer overflow: "
|
||||
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
@@ -720,11 +720,29 @@ void *MEM_guarded_calloc_arrayN_aligned(const size_t len,
|
||||
return nullptr;
|
||||
}
|
||||
if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
|
||||
return MEM_callocN(bytes_num, str);
|
||||
return MEM_callocN(r_bytes_num, str);
|
||||
}
|
||||
return MEM_mallocN_aligned(r_bytes_num, alignment, str);
|
||||
}
|
||||
|
||||
void *MEM_guarded_malloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
{
|
||||
size_t bytes_num;
|
||||
return mem_guarded_malloc_arrayN_aligned(len, size, alignment, str, bytes_num);
|
||||
}
|
||||
|
||||
void *MEM_guarded_calloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
{
|
||||
size_t bytes_num;
|
||||
/* There is no lower level #calloc with an alignment parameter, so we have to fallback to using
|
||||
* #memset unfortunately. */
|
||||
void *ptr = MEM_mallocN_aligned(bytes_num, alignment, str);
|
||||
void *ptr = mem_guarded_malloc_arrayN_aligned(len, size, alignment, str, bytes_num);
|
||||
if (!ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,11 @@ void *MEM_lockfree_mallocN_aligned(size_t len,
|
||||
const char *str,
|
||||
mem_guarded::internal::AllocationType allocation_type)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
|
||||
void *MEM_lockfree_malloc_arrayN_aligned(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
const char *str) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
|
||||
ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
void *MEM_lockfree_calloc_arrayN_aligned(size_t len,
|
||||
size_t size,
|
||||
size_t alignment,
|
||||
@@ -188,6 +193,8 @@ void *MEM_guarded_mallocN_aligned(size_t len,
|
||||
const char *str,
|
||||
mem_guarded::internal::AllocationType allocation_type)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(3);
|
||||
void *MEM_guarded_malloc_arrayN_aligned(size_t len, size_t size, size_t alignment, const char *str)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
void *MEM_guarded_calloc_arrayN_aligned(size_t len, size_t size, size_t alignment, const char *str)
|
||||
ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4);
|
||||
void MEM_guarded_printmemlist_pydict(void);
|
||||
|
||||
@@ -456,13 +456,13 @@ void *MEM_lockfree_mallocN_aligned(size_t len,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *MEM_lockfree_calloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
static void *mem_lockfree_malloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str,
|
||||
size_t &r_bytes_num)
|
||||
{
|
||||
size_t bytes_num;
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &bytes_num))) {
|
||||
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &r_bytes_num))) {
|
||||
print_error(
|
||||
"Calloc array aborted due to integer overflow: "
|
||||
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
|
||||
@@ -474,11 +474,30 @@ void *MEM_lockfree_calloc_arrayN_aligned(const size_t len,
|
||||
return nullptr;
|
||||
}
|
||||
if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
|
||||
return MEM_callocN(bytes_num, str);
|
||||
return MEM_mallocN(r_bytes_num, str);
|
||||
}
|
||||
void *ptr = MEM_mallocN_aligned(r_bytes_num, alignment, str);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *MEM_lockfree_malloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
{
|
||||
size_t bytes_num;
|
||||
return mem_lockfree_malloc_arrayN_aligned(len, size, alignment, str, bytes_num);
|
||||
}
|
||||
|
||||
void *MEM_lockfree_calloc_arrayN_aligned(const size_t len,
|
||||
const size_t size,
|
||||
const size_t alignment,
|
||||
const char *str)
|
||||
{
|
||||
size_t bytes_num;
|
||||
/* There is no lower level #calloc with an alignment parameter, so we have to fallback to using
|
||||
* #memset unfortunately. */
|
||||
void *ptr = MEM_mallocN_aligned(bytes_num, alignment, str);
|
||||
void *ptr = mem_lockfree_malloc_arrayN_aligned(len, size, alignment, str, bytes_num);
|
||||
if (!ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user