- wo_arena_free stamps the header: class_id = WO_CLS_FREED (0xFFFFFFFF), shard_id = 0xFFFF, flags/pad = 0 - freelist link moves from offset 0 to offset 8 so the poison survives on the list; wo_arena_alloc pops from offset 8 - wo_drop_obj aborts first on a poisoned header: a double free is a diagnostic, not a catchable state - WO_CLS_FREED defined in wob.h beside the builtin id space - test_arena: test_poison_on_free (poison stamped, LIFO chain through the relocated link, class drains to a fresh bump) — 17/0 - full suite SUITE_ALL_ZERO, wovm + wovm_asan rebuilt, just db-actor 10/0 (lang-41 5x marshal gate unchanged) - story 44 status: done; board row + dependency graph L44 (41 -.follow-up.-> 44) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 78ae3be403ba533db6f0e181bff201717f789a30)
72 lines
2.6 KiB
C
72 lines
2.6 KiB
C
/* test_arena — size-class reuse, region OOM, malloc fallback above 1024. */
|
|
#include "obj.h"
|
|
#include "t.h"
|
|
|
|
static void test_size_class_reuse(void) {
|
|
wo_arena a;
|
|
T_EQ(wo_arena_init(&a, 4096), 0);
|
|
void *p = wo_arena_alloc(&a, 48);
|
|
T_CHECK(p != NULL);
|
|
wo_arena_free(&a, p, 48);
|
|
/* same size class: the freed block must come straight back */
|
|
void *q = wo_arena_alloc(&a, 48);
|
|
T_CHECK(q == p);
|
|
wo_arena_free(&a, q, 48);
|
|
wo_arena_destroy(&a);
|
|
}
|
|
|
|
static void test_region_oom_returns_null(void) {
|
|
wo_arena a;
|
|
T_EQ(wo_arena_init(&a, 256), 0);
|
|
void *p1 = wo_arena_alloc(&a, 128);
|
|
void *p2 = wo_arena_alloc(&a, 128);
|
|
T_CHECK(p1 != NULL && p2 != NULL);
|
|
/* region exhausted and nothing on the free lists: null, never abort */
|
|
T_CHECK(wo_arena_alloc(&a, 128) == NULL);
|
|
/* freeing one block makes its class allocatable again */
|
|
wo_arena_free(&a, p1, 128);
|
|
T_CHECK(wo_arena_alloc(&a, 128) == p1);
|
|
wo_arena_destroy(&a);
|
|
}
|
|
|
|
static void test_large_bypasses_region(void) {
|
|
wo_arena a;
|
|
T_EQ(wo_arena_init(&a, 64), 0); /* tiny region on purpose */
|
|
/* >1024 goes to plain malloc/free regardless of region capacity */
|
|
void *big = wo_arena_alloc(&a, 4096);
|
|
T_CHECK(big != NULL);
|
|
wo_arena_free(&a, big, 4096);
|
|
wo_arena_destroy(&a);
|
|
}
|
|
|
|
/* language 44: a freed block's header is poisoned (class WO_CLS_FREED, shard
|
|
* 0xFFFF) so it can never pass for a live object; the freelist link lives at
|
|
* offset 8 and must still chain in LIFO order. */
|
|
static void test_poison_on_free(void) {
|
|
wo_arena a;
|
|
T_EQ(wo_arena_init(&a, 4096), 0);
|
|
wo_hdr *p = wo_arena_alloc(&a, 48);
|
|
wo_hdr *q = wo_arena_alloc(&a, 48);
|
|
T_CHECK(p != NULL && q != NULL && p != q);
|
|
p->class_id = 7; p->shard_id = 3; p->flags = 0; p->pad = 0; /* "live" */
|
|
wo_arena_free(&a, p, 48);
|
|
T_CHECK(p->class_id == WO_CLS_FREED); /* poisoned on free */
|
|
T_CHECK(p->shard_id == 0xFFFFu);
|
|
wo_arena_free(&a, q, 48); /* q now heads the list, links to p */
|
|
T_CHECK(q->class_id == WO_CLS_FREED);
|
|
/* LIFO through the relocated (offset-8) link: q first, then p */
|
|
T_CHECK(wo_arena_alloc(&a, 48) == q);
|
|
T_CHECK(wo_arena_alloc(&a, 48) == p);
|
|
/* the class is drained: the next allocation is a fresh bump, not a stale block */
|
|
void *r = wo_arena_alloc(&a, 48);
|
|
T_CHECK(r != NULL && r != p && r != q);
|
|
wo_arena_destroy(&a);
|
|
}
|
|
|
|
int main(void) {
|
|
test_size_class_reuse();
|
|
test_region_oom_returns_null();
|
|
test_large_bypasses_region();
|
|
test_poison_on_free();
|
|
return t_report("test_arena");
|
|
}
|