From 0a4539468953eb4de2b4668298cbc11697338a66 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 6 Feb 2026 12:43:19 -0600 Subject: [PATCH] fix crash related to allocating in context heap --- source/quickjs.c | 82 ++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index 5b433fb7..a9832f19 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -30730,6 +30730,17 @@ typedef struct MachVarInfo { int is_const; /* 1 for def, function args; 0 for var */ } MachVarInfo; +/* ---- Compile-time constant pool entry ---- */ +/* Stores raw data during compilation; converted to JSValues when building JSCodeRegister */ +typedef enum { MACH_CP_IMM, MACH_CP_STR } MachCPType; +typedef struct { + MachCPType type; + union { + JSValue val; /* immediate value (int, float) - no GC alloc */ + char *str; /* owned C string */ + }; +} MachCPoolEntry; + /* ---- Compiler state ---- */ typedef struct MachCompState { @@ -30740,8 +30751,8 @@ typedef struct MachCompState { int code_count; int code_capacity; - /* Constant pool */ - JSValue *cpool; + /* Constant pool (raw entries, no GC objects) */ + MachCPoolEntry *cpool; int cpool_count; int cpool_capacity; @@ -30811,54 +30822,64 @@ static void mach_free_reg_to(MachCompState *cs, int saved) { /* Add a constant to the pool, return its index */ static int mach_cpool_add(MachCompState *cs, JSValue val) { - /* Check for duplicates (simple linear scan) */ + /* Check for duplicates (immediate values only) */ for (int i = 0; i < cs->cpool_count; i++) { - JSValue existing = cs->cpool[i]; - /* Compare by tag and value */ - if (JS_IsInt(val) && JS_IsInt(existing)) { - if (JS_VALUE_GET_INT(val) == JS_VALUE_GET_INT(existing)) + MachCPoolEntry *e = &cs->cpool[i]; + if (e->type != MACH_CP_IMM) continue; + if (JS_IsInt(val) && JS_IsInt(e->val)) { + if (JS_VALUE_GET_INT(val) == JS_VALUE_GET_INT(e->val)) return i; - } else if (JS_VALUE_IS_TEXT(val) && JS_VALUE_IS_TEXT(existing)) { - const char *a = JS_ToCString(cs->ctx, val); - const char *b = JS_ToCString(cs->ctx, existing); - int eq = a && b && strcmp(a, b) == 0; - JS_FreeCString(cs->ctx, a); - JS_FreeCString(cs->ctx, b); - if (eq) return i; + } else if (e->val == val) { + return i; } } if (cs->cpool_count >= cs->cpool_capacity) { int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; - cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(JSValue)); + cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); cs->cpool_capacity = new_cap; } - cs->cpool[cs->cpool_count] = val; + cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_IMM, .val = val }; return cs->cpool_count++; } /* Add a string constant, return its cpool index */ static int mach_cpool_add_str(MachCompState *cs, const char *str) { - /* Check for existing identical string first (before allocating) */ + /* Check for existing identical string */ for (int i = 0; i < cs->cpool_count; i++) { - JSValue existing = cs->cpool[i]; - if (JS_VALUE_IS_TEXT(existing)) { - const char *s = JS_ToCString(cs->ctx, existing); - int eq = s && strcmp(s, str) == 0; - JS_FreeCString(cs->ctx, s); - if (eq) return i; - } + MachCPoolEntry *e = &cs->cpool[i]; + if (e->type == MACH_CP_STR && strcmp(e->str, str) == 0) + return i; } - JSValue val = JS_NewString(cs->ctx, str); if (cs->cpool_count >= cs->cpool_capacity) { int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; - cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(JSValue)); + cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); cs->cpool_capacity = new_cap; } - cs->cpool[cs->cpool_count] = val; + char *dup = sys_malloc(strlen(str) + 1); + memcpy(dup, str, strlen(str) + 1); + cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_STR, .str = dup }; return cs->cpool_count++; } +/* Convert compile-time cpool entries to JSValue array for JSCodeRegister. + Caller takes ownership of the returned array. Frees the raw entries. + Strings are interned into stone memory (no GC allocation). */ +static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries, int count) { + if (count == 0) { sys_free(entries); return NULL; } + JSValue *cpool = js_malloc_rt(count * sizeof(JSValue)); + for (int i = 0; i < count; i++) { + if (entries[i].type == MACH_CP_STR) { + cpool[i] = js_key_new(ctx, entries[i].str); + sys_free(entries[i].str); + } else { + cpool[i] = entries[i].val; + } + } + sys_free(entries); + return cpool; +} + /* Add a variable */ static void mach_add_var(MachCompState *cs, const char *name, int slot, int is_const) { if (cs->var_count >= cs->var_capacity) { @@ -31450,14 +31471,13 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { fn_code->instr_count = child.code_count; fn_code->instructions = child.code; fn_code->cpool_count = child.cpool_count; - fn_code->cpool = child.cpool; + fn_code->cpool = mach_materialize_cpool(cs->ctx, child.cpool, child.cpool_count); fn_code->func_count = child.func_count; fn_code->functions = child.functions; cJSON *fname = cJSON_GetObjectItem(node, "name"); if (fname && cJSON_IsString(fname)) { - fn_code->name = JS_NewString(cs->ctx, cJSON_GetStringValue(fname)); - fn_code->name = JS_CellStone(cs->ctx, fn_code->name); + fn_code->name = js_key_new(cs->ctx, cJSON_GetStringValue(fname)); } else { fn_code->name = JS_NULL; } @@ -31765,7 +31785,7 @@ static JSCodeRegister *mach_compile_program(MachCompState *cs, cJSON *ast, JSVal code->instr_count = cs->code_count; code->instructions = cs->code; code->cpool_count = cs->cpool_count; - code->cpool = cs->cpool; + code->cpool = mach_materialize_cpool(cs->ctx, cs->cpool, cs->cpool_count); code->func_count = cs->func_count; code->functions = cs->functions; code->name = JS_NULL;