diff --git a/source/cell.c b/source/cell.c index fe69f642..4a07dbd5 100644 --- a/source/cell.c +++ b/source/cell.c @@ -19,8 +19,9 @@ #include #include -/* Test suite declaration */ +/* Test suite declarations */ int run_c_test_suite(JSContext *ctx); +static int run_test_suite(size_t heap_size); cell_rt *root_cell = NULL; static char *core_path = NULL; @@ -204,7 +205,7 @@ static void signal_handler(int sig) } /* Run the C test suite with minimal runtime setup */ -static int run_test_suite(void) +static int run_test_suite(size_t heap_size) { JSRuntime *rt = JS_NewRuntime(); if (!rt) { @@ -212,7 +213,7 @@ static int run_test_suite(void) return 1; } - JSContext *ctx = JS_NewContextRaw(rt); + JSContext *ctx = JS_NewContextRawWithHeapSize(rt, heap_size); if (!ctx) { printf("Failed to create JS context\n"); JS_FreeRuntime(rt); @@ -233,7 +234,15 @@ int cell_init(int argc, char **argv) { /* Check for --test flag to run C test suite */ if (argc >= 2 && strcmp(argv[1], "--test") == 0) { - return run_test_suite(); + size_t heap_size = 1024; /* default */ + if (argc >= 3) { + heap_size = strtoull(argv[2], NULL, 0); + /* Round up to power of 2 for buddy allocator */ + size_t p = 1; + while (p < heap_size) p <<= 1; + heap_size = p; + } + return run_test_suite(heap_size); } int script_start = 1; diff --git a/source/quickjs.c b/source/quickjs.c index 4a83bf09..34e63885 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -82,7 +82,7 @@ */ // #define DUMP_BYTECODE (1) /* dump the occurence of the automatic GC */ -// #define DUMP_GC +#define DUMP_GC /* dump objects freed by the garbage collector */ // #define DUMP_GC_FREE /* dump memory usage before running the garbage collector */ @@ -94,6 +94,27 @@ /* test the GC by forcing it before each object allocation */ // #define FORCE_GC_AT_MALLOC +/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ +#ifdef POISON_HEAP +#if defined(__has_feature) + #if __has_feature(address_sanitizer) + #define HAVE_ASAN 1 + #endif +#elif defined(__SANITIZE_ADDRESS__) + #define HAVE_ASAN 1 +#endif + +#ifdef HAVE_ASAN +#include +#define gc_poison_region(addr, size) __asan_poison_memory_region((addr), (size)) +#define gc_unpoison_region(addr, size) __asan_unpoison_memory_region((addr), (size)) +#else +/* Fallback: no-op when not building with ASan */ +#define gc_poison_region(addr, size) ((void)0) +#define gc_unpoison_region(addr, size) ((void)0) +#endif +#endif /* POISON_HEAP */ + /* Forward declarations for heap object types */ typedef struct JSArray JSArray; typedef struct JSBlob JSBlob; @@ -310,7 +331,12 @@ typedef enum OPCodeEnum OPCodeEnum; Buddy Allocator for Actor Memory Blocks ============================================================ */ -#define BUDDY_MIN_ORDER 28 /* 64KB minimum block */ +/* Platform-specific minimum block size */ +#if defined(__LP64__) || defined(_WIN64) + #define BUDDY_MIN_ORDER 10 /* 1KB minimum on 64-bit */ +#else + #define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */ +#endif #define BUDDY_MAX_ORDER 28 /* 256MB maximum */ #define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1) #define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER) @@ -2033,17 +2059,26 @@ static void buddy_list_remove (BuddyBlock *block) { block->prev = NULL; } -/* Add block to front of free list */ +/* Add block to END of free list (FIFO policy). + This ensures recently freed blocks are reused last, making stale pointer + bugs crash deterministically instead of accidentally working. */ static void buddy_list_add (BuddyAllocator *b, BuddyBlock *block, int order) { int level = order - BUDDY_MIN_ORDER; - block->next = b->free_lists[level]; block->prev = NULL; - if (b->free_lists[level]) { - b->free_lists[level]->prev = block; - } - b->free_lists[level] = block; + block->next = NULL; block->order = order; block->is_free = 1; + + if (!b->free_lists[level]) { + b->free_lists[level] = block; + return; + } + + /* FIFO: append to end */ + BuddyBlock *tail = b->free_lists[level]; + while (tail->next) tail = tail->next; + tail->next = block; + block->prev = tail; } /* Initialize buddy allocator with 256MB pool */ @@ -2226,6 +2261,11 @@ static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *to_base, while (objhdr_type (hdr) == OBJ_FORWARD) { void *fwd_target = objhdr_fwd_ptr (hdr); + /* Stale pointer bug: forward target is NULL (should not happen with proper rooting) */ + if (!fwd_target) { + return v; /* Return original value and let caller handle it */ + } + /* Check if forward target is in new space */ if ((uint8_t *)fwd_target >= to_base && (uint8_t *)fwd_target < *to_free) { /* Target is in new space - update chain and return */ @@ -2371,6 +2411,9 @@ static int ctx_gc (JSContext *ctx) { } /* Return old block to buddy allocator */ +#ifdef POISON_HEAP + gc_poison_region(ctx->heap_base, ctx->current_block_size); +#endif buddy_free (&rt->buddy, ctx->heap_base, ctx->current_block_size); /* Update context with new block */ @@ -3333,7 +3376,15 @@ static JSRecord *get_proto_obj (JSValue proto_val) { /* WARNING: proto must be an object or JS_NULL */ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val, JSClassID class_id) { + JSGCRef proto_ref; + JS_PushGCRef (ctx, &proto_ref); + proto_ref.val = proto_val; + JSRecord *rec = js_new_record_class (ctx, 0, class_id); + + proto_val = proto_ref.val; /* Get potentially-updated value after GC */ + JS_PopGCRef (ctx, &proto_ref); + if (!rec) return JS_EXCEPTION; /* Set prototype if provided */ @@ -24673,19 +24724,23 @@ static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = { /* Minimum amount of objects to be able to compile code and display error messages. No JSAtom should be allocated by this function. */ void JS_AddIntrinsicBasicObjects (JSContext *ctx) { - JSValue proto; + JSGCRef proto_ref; int i; + JS_PushGCRef (ctx, &proto_ref); + ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto (ctx, JS_NULL); ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject (ctx); for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - proto = JS_NewObjectProto (ctx, ctx->class_proto[JS_CLASS_ERROR]); - JS_SetPropertyInternal (ctx, proto, JS_KEY_name, JS_NewAtomString (ctx, native_error_name[i])); - JS_SetPropertyInternal (ctx, proto, JS_KEY_message, JS_KEY_empty); - ctx->native_error_proto[i] = proto; + proto_ref.val = JS_NewObjectProto (ctx, ctx->class_proto[JS_CLASS_ERROR]); + JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_name, JS_NewAtomString (ctx, native_error_name[i])); + JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_message, JS_KEY_empty); + ctx->native_error_proto[i] = proto_ref.val; } + + JS_PopGCRef (ctx, &proto_ref); } void JS_AddIntrinsicBaseObjects (JSContext *ctx) {