Add memory allocation wrappers

Provides a consistent interface to allocate, resize and free heap
memory.
This commit is contained in:
2025-10-18 23:53:20 +02:00
parent 72ffaf0119
commit 10a69ba23d
2 changed files with 177 additions and 0 deletions

109
src/common/memory.c Normal file
View File

@@ -0,0 +1,109 @@
#include "memory.h"
#include "error.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
constexpr uint32_t static_canary_value = 0xFF000A00;
/**
* Describes a block of memory and its meta data. The user will only ever see
* the data pointer.
*
* FIXME: It might be possible to ensure alignment of data here so that assuming
* the underlying malloc() call is aligned to 16 bytes we also guarantee
* alignment to 16 bytes. This is not currently implemented.
*/
typedef struct memory {
uint32_t canary;
size_t size;
uint8_t data[];
} mem_t;
static bool would_overflow_mul(size_t a, size_t b) {
if (a == 0 || b == 0)
return false;
return a > SIZE_MAX / b;
}
static bool would_overflow_add(size_t a, size_t b) {
return a > SIZE_MAX - b;
}
static inline mem_t *mem_get_metadata(void *user_ptr) {
return (mem_t *)((char *)user_ptr - sizeof(mem_t));
}
[[noreturn]] void die_with_ptr_msg(void *ptr, uint32_t canary) {
fprintf(stderr, "*** HEAP CORRUPTION at %p ***\n", ptr);
fprintf(stderr, "Unexpected heap canary value %08x\n", canary);
fflush(stderr);
abort();
}
error_t *mem_alloc(void **ptr, size_t n, size_t size) {
if (ptr == nullptr)
return err_invalid_parameters;
*ptr = nullptr;
if (n == 0 || size == 0)
return err_invalid_parameters;
if (would_overflow_mul(n, size))
return err_integer_overflow;
size_t total = n * size;
if (would_overflow_add(total, sizeof(mem_t)))
return err_integer_overflow;
mem_t *mem = malloc(total + sizeof(mem_t));
if (mem == nullptr)
return err_allocation_failed;
mem->canary = static_canary_value;
mem->size = total;
memset(&mem->data[0], 0, total);
*ptr = &mem->data[0];
return nullptr;
}
error_t *mem_resize(void **ptr, size_t n, size_t size) {
if (ptr == nullptr || *ptr == nullptr)
return err_invalid_parameters;
if (n == 0 || size == 0)
return err_invalid_parameters;
mem_t *mem = mem_get_metadata(*ptr);
if (mem->canary != static_canary_value)
die_with_ptr_msg(&mem->data[0], mem->canary);
if (would_overflow_mul(n, size))
return err_integer_overflow;
size_t total = n * size;
if (would_overflow_add(total, sizeof(mem_t)))
return err_integer_overflow;
mem_t *new = realloc(mem, total + sizeof(mem_t));
if (new == nullptr)
return err_allocation_failed;
if (new != mem) {
*ptr = &new->data[0];
}
if (total > new->size)
memset(&new->data[new->size], 0, total - new->size);
new->size = total;
return nullptr;
}
void mem_free(void **ptr) {
if (ptr == nullptr || *ptr == nullptr) {
return;
}
mem_t *mem = mem_get_metadata(*ptr);
if (mem->canary != static_canary_value)
die_with_ptr_msg(&mem->data[0], mem->canary);
mem->canary = 0;
free(mem);
*ptr = nullptr;
}

68
src/common/memory.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef INCLUDE_COMMON_MEMORY_H_
#define INCLUDE_COMMON_MEMORY_H_
#include "error.h"
/**
* Allocates n*size bytes of memory and initializes them to 0.
*
* Note that the allocated memory is not guaranteed to be 16 byte aligned.
*
* ptr (out):
* If successful, *ptr will contain the pointer to the allocated memory. Must
* be a valid pointer and specifically must not be nullptr.
* n:
* The amount of consecutive objects to allocate. Must not be 0.
* size:
* The size of objects to allocate. Must not be 0.
* return:
* - nullptr on success
* - err_allocation_failed when the memory cannot be allocated.
* - err_invalid_parameter when either n or size are 0 or ptr is nullptr.
* - err_integer_overflow when n*size overflows size_t.
*/
error_t *mem_alloc(void **ptr, size_t n, size_t size);
/**
* Resizes previously allocated memory to n*size bytes.
*
* When resizing existing data is preserved but may be truncated if the new size
* is smaller. If the memory grows the newly allocated memory is initialized to
* 0. Note that the allocated memory is not guaranteed to be 16 byte aligned.
*
* If memory overflows happened or if invalid pointers are passed then this
* function may exit with abort()
*
* ptr (in, out):
* Pointer to the memory to resize. If successful, *ptr will contain the
* pointer to the resized memory (which may differ from the original).
* *ptr must have been allocated with mem_alloc or mem_resize.
* If the new size is larger, the additional bytes are initialized to 0.
* If this function returns an error, *ptr remains unchanged and points to
* the original, valid allocation.
* n:
* The amount of consecutive objects to allocate. Must not be 0.
* size:
* The size of objects to allocate. Must not be 0.
* return:
* - nullptr on success
* - err_allocation_failed when the memory cannot be allocated.
* - err_invalid_parameter when either n or size are 0, or *ptr is nullptr.
* - err_integer_overflow when n*size overflows size_t.
*/
error_t *mem_resize(void **ptr, size_t n, size_t size);
/**
* Frees memory previously allocated with mem_alloc or mem_resize.
*
* If memory overflows happened or if invalid pointers are passed then this
* function may exit with abort()
*
* ptr (in, out)
* Pointer to the memory to free. If ptr and *ptr are not nullptr, *ptr will
* be set to nullptr after freeing. Both ptr and *ptr can be nullptr (no-op).
* *ptr must have been allocated with mem_alloc or mem_resize.
*/
void mem_free(void **ptr);
#endif // INCLUDE_COMMON_MEMORY_H_