From 791b45075d1dea4d195d886b6b306853f8fb3606 Mon Sep 17 00:00:00 2001 From: Cristian Gora Date: Wed, 28 Oct 2026 22:52:20 +0100 Subject: [PATCH] v1.2.5 - Fix alignment above alignof(max_align_t) and stale marker rewinds --- .gitignore | 1 + Makefile | 6 +++- arena.h | 93 +++++++++++++++++++++++++++++++++++------------------- test.c | 40 +++++++++++++++++++++++ 4 files changed, 107 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 9daeafb..ad872e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ test +test_align64 diff --git a/Makefile b/Makefile index 2d67a57..00e654d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ +all: test test_align64 test: test.c arena.h clang -Wall -Wextra -pedantic -std=c11 -o test test.c +test_align64: test.c arena.h + clang -Wall -Wextra -pedantic -std=c11 -DARENA_ALIGNMENT=64 -o test_align64 test.c + clean: - rm -rf test + rm -rf test test_align64 diff --git a/arena.h b/arena.h index 7d392e0..55a6c21 100644 --- a/arena.h +++ b/arena.h @@ -1,4 +1,4 @@ -// arena.h - v1.2.4 - MIT License +// arena.h - v1.2.5 - MIT License // single header library for region-based memory management. // // [License and changelog] @@ -69,7 +69,8 @@ // #define ARENA_ALIGNMENT alignment (alignof(max_align_t)) // // This macro defines the memory alignment used for allocations. -// Must be a power of two. +// Must be a power of two. Values greater than alignof(max_align_t) +// are supported. // // #define ARENA_REGION_CAPACITY new_region_capacity_in_bytes (8*1024) // @@ -97,8 +98,9 @@ // // These macros define alternative functions for dynamic allocation // and deallocation. They are only used for managing Arena__Region -// structures. If redefined ensure that your functions consider memory -// alignment. +// structures. If redefined ensure that your functions return memory +// aligned at least like malloc() does. ARENA_ALIGNMENT is handled by +// the library itself. // // [Function documentation] // @@ -177,6 +179,11 @@ // Note: this function does not free any newly allocated regions internally. // See arena_trim() // +// Warning: a marker is invalidated when its region is freed by arena_trim() or +// arena_free(). Rewinding to a marker whose region is no longer part of the +// arena is treated as an error and leaves the arena untouched. This takes +// linear time in the number of regions. +// // [Notes] // // This library does not account for thread safety. @@ -225,9 +232,10 @@ typedef struct Arena_Region { struct Arena_Region *next; + uint8_t *data; // Start of user buffers, aligned to ARENA_ALIGNMENT size_t count; size_t capacity; - alignas(ARENA_ALIGNMENT) uint8_t data[]; + uint8_t storage[]; } Arena__Region; typedef struct { @@ -272,6 +280,7 @@ void arena_rewind(Arena *arena, ArenaMarker marker); // Private functions static size_t arena__align(size_t n); +static Arena__Region *arena__region_create(size_t capacity); Arena arena_create(size_t region_capacity) { @@ -351,21 +360,10 @@ void *arena_alloc(Arena *arena, size_t size) ? aligned_size : default_capacity); - if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) { - ARENA_ASSERT(false && "Region allocation size overflow"); - return NULL; - } + arena->head = arena->tail = arena__region_create(alloc_capacity); + if (arena->head == NULL) { return NULL; } - arena->head = arena->tail = (Arena__Region *)ARENA_MALLOC( - sizeof(*arena->head) + alloc_capacity); - if (arena->head == NULL) { - ARENA_ASSERT(false && "Memory allocation failed"); - return NULL; - } - - arena->head->next = NULL; arena->head->count = aligned_size; - arena->head->capacity = alloc_capacity; return arena->head->data; } @@ -396,22 +394,11 @@ void *arena_alloc(Arena *arena, size_t size) ? aligned_size : next_capacity); - if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) { - ARENA_ASSERT(false && "Region allocation size overflow"); - return NULL; - } - - last_region->next = (Arena__Region *)ARENA_MALLOC( - sizeof(*last_region->next) + alloc_capacity); - if (last_region->next == NULL) { - ARENA_ASSERT(false && "Memory allocation failed"); - return NULL; - } + last_region->next = arena__region_create(alloc_capacity); + if (last_region->next == NULL) { return NULL; } arena->tail = last_region->next; - arena->tail->next = NULL; arena->tail->count = aligned_size; - arena->tail->capacity = alloc_capacity; return arena->tail->data; } @@ -531,7 +518,19 @@ void arena_rewind(Arena *arena, ArenaMarker marker) return; } - arena->tail = marker.region; + // Reject markers whose region is not part of this arena anymore, e.g. + // freed by arena_trim() or arena_free(), or taken from another arena + Arena__Region *region = arena->head; + while (region != NULL && region != marker.region) { + region = region->next; + } + + if (region == NULL || marker.count > region->capacity) { + ARENA_ASSERT(false && "Invalid marker"); + return; + } + + arena->tail = region; arena->tail->count = marker.count; // Reset any new regions that were appended @@ -545,11 +544,41 @@ static size_t arena__align(size_t n) return (n + (ARENA_ALIGNMENT - 1)) & ~(ARENA_ALIGNMENT - 1); } +static Arena__Region *arena__region_create(size_t capacity) +{ + // Extra space to align data ourselves, since ARENA_MALLOC only guarantees + // alignof(max_align_t), which may be less than ARENA_ALIGNMENT + size_t padding = ARENA_ALIGNMENT - 1; + if (capacity > SIZE_MAX - sizeof(Arena__Region) - padding) { + ARENA_ASSERT(false && "Region allocation size overflow"); + return NULL; + } + + Arena__Region *region = (Arena__Region *)ARENA_MALLOC( + sizeof(*region) + padding + capacity); + if (region == NULL) { + ARENA_ASSERT(false && "Memory allocation failed"); + return NULL; + } + + uintptr_t misalignment = (uintptr_t)region->storage + & ((uintptr_t)ARENA_ALIGNMENT - 1); + region->data = region->storage + (misalignment == 0 + ? 0 + : ARENA_ALIGNMENT - misalignment); + region->next = NULL; + region->count = 0; + region->capacity = capacity; + return region; +} + #endif // ARENA_IMPLEMENTATION /* * Revision history: * + * 1.2.5 (2026-10-28) Support ARENA_ALIGNMENT > alignof(max_align_t); + * reject invalid markers in arena_rewind() * 1.2.4 (2026-09-20) Major rewrite * 1.2.3 (2026-07-07) New function: arena_calloc() * 1.2.2 (2026-07-07) Fix various memory problems; diff --git a/test.c b/test.c index 4db627e..ce5f8b2 100644 --- a/test.c +++ b/test.c @@ -160,6 +160,45 @@ static void test_snapshot_rewind_trim_logic(void) arena_free(&arena); } +static void test_invalid_markers(void) +{ + printf("Running invalid marker tests...\n"); + Arena arena = arena_create(128); + + void *first = arena_alloc(&arena, 100); // Region 1 (Cap: 128) + assert(is_aligned(first)); + ArenaMarker mark_r1 = arena_snapshot(&arena); + + arena_alloc(&arena, 200); // Region 2 (Cap: 256) + ArenaMarker mark_r2 = arena_snapshot(&arena); + + // Free Region 2, invalidating mark_r2 + arena_rewind(&arena, mark_r1); + arena_trim(&arena); + assert(count_regions(&arena) == 1); + + // Stale marker must be rejected without touching freed memory + arena_rewind(&arena, mark_r2); + assert(arena.tail == mark_r1.region); + assert(arena.tail->count == mark_r1.count); + + // Marker from another arena + Arena other = arena_create(128); + arena_alloc(&other, 10); + arena_rewind(&arena, arena_snapshot(&other)); + assert(arena.tail == mark_r1.region); + assert(arena.tail->count == mark_r1.count); + arena_free(&other); + + // Marker with a count beyond the region capacity + ArenaMarker forged = mark_r1; + forged.count = mark_r1.region->capacity + 1; + arena_rewind(&arena, forged); + assert(arena.tail->count == mark_r1.count); + + arena_free(&arena); +} + static void test_massive_string_formatting(void) { printf("Running massive string formatting tests...\n"); @@ -204,6 +243,7 @@ int main(void) test_overflows(); test_geometric_growth_and_stress(); test_snapshot_rewind_trim_logic(); + test_invalid_markers(); test_massive_string_formatting(); test_zero_capacity_fallback();