more gc correctness

This commit is contained in:
2026-02-03 02:31:14 -06:00
parent a80557283a
commit f203278c3e
2 changed files with 270 additions and 181 deletions

View File

@@ -1413,20 +1413,32 @@ static JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) {
return JS_NULL;
}
/* Resize record by allocating a new larger record and copying all data.
Sets up a forwarding pointer from old record to new.
Updates *prec to point to the new record.
/* GC-SAFE: Resize record by allocating a new larger record and copying all data.
Uses GC ref to protect source during allocation.
Updates *pobj to point to the new record.
Returns 0 on success, -1 on failure. */
static int rec_resize (JSContext *ctx, JSRecord **prec, uint64_t new_mask) {
JSRecord *rec = *prec;
static int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask) {
/* Protect the source object with a GC ref in case js_malloc triggers GC */
JSGCRef obj_ref;
JS_AddGCRef (ctx, &obj_ref);
obj_ref.val = *pobj;
JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj);
uint64_t old_mask = objhdr_cap56 (rec->mist_hdr);
/* Allocate new record with larger capacity */
/* Allocate new record with larger capacity - may trigger GC! */
size_t slots_size = sizeof (slot) * (new_mask + 1);
size_t total_size = sizeof (JSRecord) + slots_size;
JSRecord *new_rec = js_malloc (ctx, total_size);
if (!new_rec) return -1;
if (!new_rec) {
JS_DeleteGCRef (ctx, &obj_ref);
return -1;
}
/* Re-get record from GC ref - it may have moved during GC */
rec = (JSRecord *)JS_VALUE_GET_OBJ (obj_ref.val);
old_mask = objhdr_cap56 (rec->mist_hdr);
/* Initialize new record */
new_rec->mist_hdr = objhdr_make (new_mask, OBJ_RECORD, false, false, false, false);
@@ -1463,22 +1475,22 @@ static int rec_resize (JSContext *ctx, JSRecord **prec, uint64_t new_mask) {
}
}
/* Set up forwarding pointer from old record to new */
rec->mist_hdr = objhdr_make_fwd (new_rec);
/* Update caller's pointer to new record */
*prec = new_rec;
/* Update caller's JSValue to point to new record */
*pobj = JS_MKPTR (new_rec);
JS_DeleteGCRef (ctx, &obj_ref);
return 0;
}
/* NOT GC-SAFE: May call rec_resize which allocates.
Caller must pass freshly-chased rec and handle potential staleness. */
static int rec_set_own (JSContext *ctx, JSRecord *rec, JSValue k, JSValue val) {
/* GC-SAFE: May call rec_resize which allocates.
Takes JSValue* so the object can be tracked through GC.
Updates *pobj if the record is resized. */
static int rec_set_own (JSContext *ctx, JSValue *pobj, JSValue k, JSValue val) {
if (rec_key_is_empty (k) || rec_key_is_tomb (k)) {
return -1;
}
JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj);
int slot = rec_find_slot (rec, k);
if (slot > 0) {
@@ -1490,12 +1502,29 @@ static int rec_set_own (JSContext *ctx, JSRecord *rec, JSValue k, JSValue val) {
/* New key - check if resize needed (75% load factor) */
uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr);
if ((rec->len + 1) * 4 > mask * 3) {
/* Over 75% load factor - resize */
/* Over 75% load factor - resize. Protect key and value in case GC runs. */
JSGCRef k_ref, val_ref;
JS_AddGCRef (ctx, &k_ref);
JS_AddGCRef (ctx, &val_ref);
k_ref.val = k;
val_ref.val = val;
uint32_t new_mask = (mask + 1) * 2 - 1;
if (rec_resize (ctx, &rec, new_mask) < 0) {
if (rec_resize (ctx, pobj, new_mask) < 0) {
JS_DeleteGCRef (ctx, &val_ref);
JS_DeleteGCRef (ctx, &k_ref);
return -1;
}
/* Re-find slot after resize (rec now points to new record) */
/* Re-get values after resize (they may have moved during GC) */
k = k_ref.val;
val = val_ref.val;
JS_DeleteGCRef (ctx, &val_ref);
JS_DeleteGCRef (ctx, &k_ref);
/* Re-get rec after resize (pobj now points to new record) */
rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj);
/* Re-find slot after resize */
slot = rec_find_slot (rec, k);
}
@@ -1776,14 +1805,15 @@ static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap);
/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */
#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx)
/* JS_SetPropertyInternal: same as JS_SetProperty but doesn't check stone.
Internal use only. */
/* GC-SAFE: JS_SetPropertyInternal: same as JS_SetProperty but doesn't check stone.
Internal use only. May trigger GC if record needs to resize. */
int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) {
if (!JS_IsRecord (this_obj)) {
return -1;
}
JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (this_obj);
return rec_set_own (ctx, rec, prop, val);
/* Use a local copy that rec_set_own can update if resize happens */
JSValue obj = this_obj;
return rec_set_own (ctx, &obj, prop, val);
}
static blob *js_get_blob (JSContext *ctx, JSValue val);
@@ -2202,6 +2232,32 @@ static void buddy_destroy (BuddyAllocator *b) {
}
}
/* ============================================================
Heap block allocation wrappers
In POISON_HEAP mode, use malloc so poisoned memory stays poisoned.
Otherwise use buddy allocator for efficiency.
============================================================ */
static void *heap_block_alloc(JSRuntime *rt, size_t size) {
#ifdef POISON_HEAP
(void)rt;
return malloc(size);
#else
return buddy_alloc(&rt->buddy, size);
#endif
}
static void heap_block_free(JSRuntime *rt, void *ptr, size_t size) {
#ifdef POISON_HEAP
(void)rt;
(void)size;
/* Don't free - leave it poisoned to catch stale accesses */
gc_poison_region(ptr, size);
#else
buddy_free(&rt->buddy, ptr, size);
#endif
}
/* ============================================================
Bump Allocator and Cheney GC
============================================================ */
@@ -2391,11 +2447,11 @@ static int ctx_gc (JSContext *ctx) {
/* Request new block from runtime */
size_t new_size = ctx->next_block_size;
uint8_t *new_block = buddy_alloc (&rt->buddy, new_size);
uint8_t *new_block = heap_block_alloc (rt, new_size);
if (!new_block) {
/* Try with same size */
new_size = ctx->current_block_size;
new_block = buddy_alloc (&rt->buddy, new_size);
new_block = heap_block_alloc (rt, new_size);
if (!new_block) return -1;
}
@@ -2505,11 +2561,8 @@ static int ctx_gc (JSContext *ctx) {
scan += obj_size;
}
/* Return old block to buddy allocator, then poison to catch stale accesses */
buddy_free (&rt->buddy, from_base, old_heap_size);
#ifdef POISON_HEAP
gc_poison_region(from_base, old_heap_size);
#endif
/* Return old block (in poison mode, just poison it and leak) */
heap_block_free (rt, from_base, old_heap_size);
/* Update context with new block */
size_t new_used = to_free - to_base;
@@ -2708,7 +2761,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
/* Allocate initial heap block for bump allocation */
ctx->current_block_size = heap_size;
ctx->next_block_size = ctx->current_block_size;
ctx->heap_base = buddy_alloc (&rt->buddy, ctx->current_block_size);
ctx->heap_base = heap_block_alloc (rt, ctx->current_block_size);
if (!ctx->heap_base) {
js_free_rt (ctx->st_text_hash);
js_free_rt (ctx->st_text_array);
@@ -2823,9 +2876,9 @@ void JS_FreeContext (JSContext *ctx) {
js_free_rt (ctx->st_text_hash);
js_free_rt (ctx->st_text_array);
/* Free heap block back to buddy allocator */
/* Free heap block */
if (ctx->heap_base) {
buddy_free (&rt->buddy, ctx->heap_base, ctx->current_block_size);
heap_block_free (rt, ctx->heap_base, ctx->current_block_size);
ctx->heap_base = NULL;
ctx->heap_free = NULL;
ctx->heap_end = NULL;
@@ -4516,6 +4569,7 @@ static int delete_property (JSContext *ctx, JSRecord *rec, JSValue key) {
return TRUE;
}
/* GC-SAFE: May trigger GC if record needs to resize */
int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) {
if (!JS_IsRecord (this_obj)) {
if (JS_IsNull (this_obj)) {
@@ -4534,7 +4588,9 @@ int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val)
return -1;
}
return rec_set_own (ctx, rec, prop, val);
/* Use a local copy that rec_set_own can update if resize happens */
JSValue obj = this_obj;
return rec_set_own (ctx, &obj, prop, val);
}
int JS_SetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx, JSValue val) {
@@ -4553,7 +4609,15 @@ int JS_SetPropertyInt64 (JSContext *ctx, JSValue this_obj, int64_t idx, JSValue
return 0;
}
/* GC-SAFE: Protects this_obj and val in case key creation triggers GC */
int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val) {
/* Protect this_obj and val in case key creation triggers GC */
JSGCRef obj_ref, val_ref;
JS_AddGCRef (ctx, &obj_ref);
JS_AddGCRef (ctx, &val_ref);
obj_ref.val = this_obj;
val_ref.val = val;
/* Create JSValue key from string - try immediate ASCII first */
int len = strlen (prop);
JSValue key;
@@ -4564,9 +4628,15 @@ int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSVal
key = js_new_string8_len (ctx, prop, len);
}
if (JS_IsException (key)) {
JS_DeleteGCRef (ctx, &val_ref);
JS_DeleteGCRef (ctx, &obj_ref);
return -1;
}
return JS_SetProperty (ctx, this_obj, key, val);
int ret = JS_SetProperty (ctx, obj_ref.val, key, val_ref.val);
JS_DeleteGCRef (ctx, &val_ref);
JS_DeleteGCRef (ctx, &obj_ref);
return ret;
}
/* Set property with JSValue prop/key - handles int, string, object keys */
@@ -18917,7 +18987,7 @@ static JSValue find_key (JSContext *ctx, const char *name) {
return js_key_new (ctx, name);
}
static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue obj, JSValue key, const JSCFunctionListEntry *e) {
static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue *obj_ptr, JSValue key, const JSCFunctionListEntry *e) {
JSValue val;
int prop_flags = e->prop_flags;
@@ -18927,7 +18997,7 @@ static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue obj, JSValue
JSValue key1 = find_key (ctx, e->u.alias.name);
switch (e->u.alias.base) {
case -1:
val = JS_GetProperty (ctx, obj, key1);
val = JS_GetProperty (ctx, *obj_ptr, key1);
break;
case 0:
val = JS_GetProperty (ctx, ctx->global_obj, key1);
@@ -18964,21 +19034,34 @@ static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue obj, JSValue
default:
abort ();
}
JS_SetPropertyInternal (ctx, obj, key, val);
JS_SetPropertyInternal (ctx, *obj_ptr, key, val);
return 0;
}
int JS_SetPropertyFunctionList (JSContext *ctx, JSValue obj, const JSCFunctionListEntry *tab, int len) {
int i, ret;
/* Root obj since allocations in the loop can trigger GC */
JSGCRef obj_ref;
obj_ref.val = obj;
obj_ref.prev = ctx->last_gc_ref;
ctx->last_gc_ref = &obj_ref;
for (i = 0; i < len; i++) {
const JSCFunctionListEntry *e = &tab[i];
JSValue key = find_key (ctx, e->name);
if (JS_IsNull (key)) return -1;
ret = JS_InstantiateFunctionListItem (ctx, obj, key, e);
if (JS_IsNull (key)) {
ctx->last_gc_ref = obj_ref.prev;
return -1;
}
ret = JS_InstantiateFunctionListItem (ctx, &obj_ref.val, key, e);
/* key is interned, no need to free */
if (ret) return -1;
if (ret) {
ctx->last_gc_ref = obj_ref.prev;
return -1;
}
}
ctx->last_gc_ref = obj_ref.prev;
return 0;
}
@@ -24861,6 +24944,13 @@ void JS_AddIntrinsicBasicObjects (JSContext *ctx) {
JS_PopGCRef (ctx, &proto_ref);
}
/* GC-SAFE: Helper to set a global function. Creates function first, then reads
ctx->global_obj to ensure it's not stale if GC ran during function creation. */
static void js_set_global_cfunc(JSContext *ctx, const char *name, JSCFunction *func, int length) {
JSValue fn = JS_NewCFunction(ctx, func, name, length);
JS_SetPropertyStr(ctx, ctx->global_obj, name, fn);
}
void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
int i;
JSValue obj, number_obj;
@@ -24922,108 +25012,68 @@ void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
JS_SetPropertyStr (ctx, ctx->global_obj, "blob", blob_ctor);
}
/* stone() function */
JS_SetPropertyStr (ctx, ctx->global_obj, "stone", JS_NewCFunction (ctx, js_cell_stone, "stone", 1));
/* length() function */
JS_SetPropertyStr (ctx, ctx->global_obj, "length", JS_NewCFunction (ctx, js_cell_length, "length", 1));
/* call() function */
JS_SetPropertyStr (ctx, ctx->global_obj, "call", JS_NewCFunction (ctx, js_cell_call, "call", 3));
/* Core functions - using GC-safe helper */
js_set_global_cfunc(ctx, "stone", js_cell_stone, 1);
js_set_global_cfunc(ctx, "length", js_cell_length, 1);
js_set_global_cfunc(ctx, "call", js_cell_call, 3);
/* is_* type checking functions */
JS_SetPropertyStr (ctx, ctx->global_obj, "is_array", JS_NewCFunction (ctx, js_cell_is_array, "is_array", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "is_blob", JS_NewCFunction (ctx, js_cell_is_blob, "is_blob", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "is_data", JS_NewCFunction (ctx, js_cell_is_data, "is_data", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "is_function", JS_NewCFunction (ctx, js_cell_is_function, "is_function", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "is_logical", JS_NewCFunction (ctx, js_cell_is_logical, "is_logical", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "is_integer", JS_NewCFunction (ctx, js_cell_is_integer, "is_integer", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "is_null", JS_NewCFunction (ctx, js_cell_is_null, "is_null", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "is_number", JS_NewCFunction (ctx, js_cell_is_number, "is_number", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "is_object", JS_NewCFunction (ctx, js_cell_is_object, "is_object", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "is_stone", JS_NewCFunction (ctx, js_cell_is_stone, "is_stone", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "is_text", JS_NewCFunction (ctx, js_cell_is_text, "is_text", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "is_proto", JS_NewCFunction (ctx, js_cell_is_proto, "is_proto", 2));
js_set_global_cfunc(ctx, "is_array", js_cell_is_array, 1);
js_set_global_cfunc(ctx, "is_blob", js_cell_is_blob, 1);
js_set_global_cfunc(ctx, "is_data", js_cell_is_data, 1);
js_set_global_cfunc(ctx, "is_function", js_cell_is_function, 1);
js_set_global_cfunc(ctx, "is_logical", js_cell_is_logical, 1);
js_set_global_cfunc(ctx, "is_integer", js_cell_is_integer, 1);
js_set_global_cfunc(ctx, "is_null", js_cell_is_null, 1);
js_set_global_cfunc(ctx, "is_number", js_cell_is_number, 1);
js_set_global_cfunc(ctx, "is_object", js_cell_is_object, 1);
js_set_global_cfunc(ctx, "is_stone", js_cell_is_stone, 1);
js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1);
js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2);
JS_SetPropertyStr (ctx, ctx->global_obj, "apply", JS_NewCFunction (ctx, js_cell_fn_apply, "apply", 2));
JS_SetPropertyStr (
ctx, ctx->global_obj, "replace", JS_NewCFunction (ctx, js_cell_text_replace, "replace", 4));
JS_SetPropertyStr (ctx, ctx->global_obj, "lower", JS_NewCFunction (ctx, js_cell_text_lower, "lower", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "upper", JS_NewCFunction (ctx, js_cell_text_upper, "upper", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "trim", JS_NewCFunction (ctx, js_cell_text_trim, "trim", 2));
JS_SetPropertyStr (
ctx, ctx->global_obj, "codepoint", JS_NewCFunction (ctx, js_cell_text_codepoint, "codepoint", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "search", JS_NewCFunction (ctx, js_cell_text_search, "search", 3));
JS_SetPropertyStr (
ctx, ctx->global_obj, "extract", JS_NewCFunction (ctx, js_cell_text_extract, "extract", 4));
JS_SetPropertyStr (
ctx, ctx->global_obj, "format", JS_NewCFunction (ctx, js_cell_text_format, "format", 3));
JS_SetPropertyStr (
ctx, ctx->global_obj, "reduce", JS_NewCFunction (ctx, js_cell_array_reduce, "reduce", 4));
JS_SetPropertyStr (ctx, ctx->global_obj, "arrfor", JS_NewCFunction (ctx, js_cell_array_for, "for", 4));
JS_SetPropertyStr (ctx, ctx->global_obj, "find", JS_NewCFunction (ctx, js_cell_array_find, "find", 4));
JS_SetPropertyStr (
ctx, ctx->global_obj, "filter", JS_NewCFunction (ctx, js_cell_array_filter, "filter", 2));
JS_SetPropertyStr (ctx, ctx->global_obj, "sort", JS_NewCFunction (ctx, js_cell_array_sort, "sort", 2));
/* Utility functions */
js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2);
js_set_global_cfunc(ctx, "replace", js_cell_text_replace, 4);
js_set_global_cfunc(ctx, "lower", js_cell_text_lower, 1);
js_set_global_cfunc(ctx, "upper", js_cell_text_upper, 1);
js_set_global_cfunc(ctx, "trim", js_cell_text_trim, 2);
js_set_global_cfunc(ctx, "codepoint", js_cell_text_codepoint, 1);
js_set_global_cfunc(ctx, "search", js_cell_text_search, 3);
js_set_global_cfunc(ctx, "extract", js_cell_text_extract, 4);
js_set_global_cfunc(ctx, "format", js_cell_text_format, 3);
js_set_global_cfunc(ctx, "reduce", js_cell_array_reduce, 4);
js_set_global_cfunc(ctx, "arrfor", js_cell_array_for, 4);
js_set_global_cfunc(ctx, "find", js_cell_array_find, 4);
js_set_global_cfunc(ctx, "filter", js_cell_array_filter, 2);
js_set_global_cfunc(ctx, "sort", js_cell_array_sort, 2);
/* Number utility functions as globals */
JS_SetPropertyStr (
ctx, ctx->global_obj, "whole", JS_NewCFunction (ctx, js_cell_number_whole, "whole", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "fraction", JS_NewCFunction (ctx, js_cell_number_fraction, "fraction", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "floor", JS_NewCFunction (ctx, js_cell_number_floor, "floor", 2));
JS_SetPropertyStr (
ctx, ctx->global_obj, "ceiling", JS_NewCFunction (ctx, js_cell_number_ceiling, "ceiling", 2));
JS_SetPropertyStr (ctx, ctx->global_obj, "abs", JS_NewCFunction (ctx, js_cell_number_abs, "abs", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "round", JS_NewCFunction (ctx, js_cell_number_round, "round", 2));
JS_SetPropertyStr (ctx, ctx->global_obj, "sign", JS_NewCFunction (ctx, js_cell_number_sign, "sign", 1));
JS_SetPropertyStr (
ctx, ctx->global_obj, "trunc", JS_NewCFunction (ctx, js_cell_number_trunc, "trunc", 2));
JS_SetPropertyStr (ctx, ctx->global_obj, "min", JS_NewCFunction (ctx, js_cell_number_min, "min", 2));
JS_SetPropertyStr (ctx, ctx->global_obj, "max", JS_NewCFunction (ctx, js_cell_number_max, "max", 2));
JS_SetPropertyStr (
ctx, ctx->global_obj, "remainder", JS_NewCFunction (ctx, js_cell_number_remainder, "remainder", 2));
JS_SetPropertyStr (
ctx, ctx->global_obj, "character", JS_NewCFunction (ctx, js_cell_character, "character", 2));
/* Number utility functions */
js_set_global_cfunc(ctx, "whole", js_cell_number_whole, 1);
js_set_global_cfunc(ctx, "fraction", js_cell_number_fraction, 1);
js_set_global_cfunc(ctx, "floor", js_cell_number_floor, 2);
js_set_global_cfunc(ctx, "ceiling", js_cell_number_ceiling, 2);
js_set_global_cfunc(ctx, "abs", js_cell_number_abs, 1);
js_set_global_cfunc(ctx, "round", js_cell_number_round, 2);
js_set_global_cfunc(ctx, "sign", js_cell_number_sign, 1);
js_set_global_cfunc(ctx, "trunc", js_cell_number_trunc, 2);
js_set_global_cfunc(ctx, "min", js_cell_number_min, 2);
js_set_global_cfunc(ctx, "max", js_cell_number_max, 2);
js_set_global_cfunc(ctx, "remainder", js_cell_number_remainder, 2);
js_set_global_cfunc(ctx, "character", js_cell_character, 2);
js_set_global_cfunc(ctx, "modulo", js_cell_modulo, 2);
js_set_global_cfunc(ctx, "neg", js_cell_neg, 1);
js_set_global_cfunc(ctx, "not", js_cell_not, 1);
js_set_global_cfunc(ctx, "reverse", js_cell_reverse, 1);
js_set_global_cfunc(ctx, "proto", js_cell_proto, 1);
js_set_global_cfunc(ctx, "splat", js_cell_splat, 1);
/* modulo(dividend, divisor) - dividend - (divisor * floor(dividend /
* divisor)) */
JS_SetPropertyStr (ctx, ctx->global_obj, "modulo", JS_NewCFunction (ctx, js_cell_modulo, "modulo", 2));
/* pi - mathematical constant (no GC concern for immediate float) */
JS_SetPropertyStr(ctx, ctx->global_obj, "pi",
JS_NewFloat64(ctx, 3.14159265358979323846264338327950288419716939937510));
/* neg(number) - negate a number */
JS_SetPropertyStr (ctx, ctx->global_obj, "neg", JS_NewCFunction (ctx, js_cell_neg, "neg", 1));
/* not(boolean) - logical not */
JS_SetPropertyStr (ctx, ctx->global_obj, "not", JS_NewCFunction (ctx, js_cell_not, "not", 1));
/* reverse() - reverse an array */
JS_SetPropertyStr (ctx, ctx->global_obj, "reverse", JS_NewCFunction (ctx, js_cell_reverse, "reverse", 1));
/* proto() - get prototype of an object */
JS_SetPropertyStr (ctx, ctx->global_obj, "proto", JS_NewCFunction (ctx, js_cell_proto, "proto", 1));
/* splat() - flatten object with prototype chain */
JS_SetPropertyStr (ctx, ctx->global_obj, "splat", JS_NewCFunction (ctx, js_cell_splat, "splat", 1));
/* pi - mathematical constant */
JS_SetPropertyStr (
ctx, ctx->global_obj, "pi", JS_NewFloat64 (ctx, 3.14159265358979323846264338327950288419716939937510));
/* push() - add element to end of array */
JS_SetPropertyStr (ctx, ctx->global_obj, "push", JS_NewCFunction (ctx, js_cell_push, "push", 2));
/* pop() - remove and return last element of array */
JS_SetPropertyStr (ctx, ctx->global_obj, "pop", JS_NewCFunction (ctx, js_cell_pop, "pop", 1));
JS_SetPropertyStr (ctx, ctx->global_obj, "meme", JS_NewCFunction (ctx, js_cell_meme, "meme", 2));
js_set_global_cfunc(ctx, "push", js_cell_push, 2);
js_set_global_cfunc(ctx, "pop", js_cell_pop, 1);
js_set_global_cfunc(ctx, "meme", js_cell_meme, 2);
}
}

View File

@@ -369,35 +369,50 @@ TEST(object_string_property) {
return 1;
}
/* GC-SAFE: Uses GC refs to protect objects across allocations */
TEST(object_nested) {
JSValue outer = JS_NewObject(ctx);
JSValue inner = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, inner, "value", JS_NewInt32(ctx, 99));
JS_SetPropertyStr(ctx, outer, "inner", inner);
JSGCRef outer_ref, inner_ref;
JS_AddGCRef(ctx, &outer_ref);
JS_AddGCRef(ctx, &inner_ref);
JSValue gotInner = JS_GetPropertyStr(ctx, outer, "inner");
outer_ref.val = JS_NewObject(ctx);
inner_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, inner_ref.val, "value", JS_NewInt32(ctx, 99));
JS_SetPropertyStr(ctx, outer_ref.val, "inner", inner_ref.val);
JSValue gotInner = JS_GetPropertyStr(ctx, outer_ref.val, "inner");
ASSERT(JS_IsRecord(gotInner));
JSValue val = JS_GetPropertyStr(ctx, gotInner, "value");
JS_DeleteGCRef(ctx, &inner_ref);
JS_DeleteGCRef(ctx, &outer_ref);
ASSERT_INT(val, 99);
return 1;
}
/* GC-SAFE: Uses GC ref to protect object across multiple property sets */
TEST(object_many_properties_resize) {
JSValue obj = JS_NewObject(ctx);
JSGCRef obj_ref;
JS_AddGCRef(ctx, &obj_ref);
obj_ref.val = JS_NewObject(ctx);
/* Add many properties to trigger resize */
for (int i = 0; i < 100; i++) {
char key[16];
snprintf(key, sizeof(key), "prop%d", i);
JS_SetPropertyStr(ctx, obj, key, JS_NewInt32(ctx, i * 10));
JS_SetPropertyStr(ctx, obj_ref.val, key, JS_NewInt32(ctx, i * 10));
}
/* Verify all properties */
for (int i = 0; i < 100; i++) {
char key[16];
snprintf(key, sizeof(key), "prop%d", i);
JSValue val = JS_GetPropertyStr(ctx, obj, key);
JSValue val = JS_GetPropertyStr(ctx, obj_ref.val, key);
ASSERT_INT(val, i * 10);
}
JS_DeleteGCRef(ctx, &obj_ref);
return 1;
}
@@ -412,27 +427,35 @@ TEST(array_create) {
return 1;
}
/* GC-SAFE: Uses GC ref for array operations */
TEST(array_push_and_length) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 30));
JSGCRef arr_ref;
JS_AddGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 30));
int64_t len;
JS_GetLength(ctx, arr, &len);
JS_GetLength(ctx, arr_ref.val, &len);
JS_DeleteGCRef(ctx, &arr_ref);
ASSERT(len == 3);
return 1;
}
/* GC-SAFE: Uses GC ref for array operations */
TEST(array_get_by_index) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 100));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 200));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 300));
JSGCRef arr_ref;
JS_AddGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 100));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 200));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 300));
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2);
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr_ref.val, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr_ref.val, 2);
JS_DeleteGCRef(ctx, &arr_ref);
ASSERT_INT(v0, 100);
ASSERT_INT(v1, 200);
@@ -440,57 +463,73 @@ TEST(array_get_by_index) {
return 1;
}
/* GC-SAFE: Uses GC ref for array operations */
TEST(array_set_by_index) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 0));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 0));
JSGCRef arr_ref;
JS_AddGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 0));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 0));
JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, 55));
JS_SetPropertyUint32(ctx, arr, 1, JS_NewInt32(ctx, 66));
JS_SetPropertyUint32(ctx, arr_ref.val, 0, JS_NewInt32(ctx, 55));
JS_SetPropertyUint32(ctx, arr_ref.val, 1, JS_NewInt32(ctx, 66));
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr_ref.val, 1);
JS_DeleteGCRef(ctx, &arr_ref);
ASSERT_INT(v0, 55);
ASSERT_INT(v1, 66);
return 1;
}
/* GC-SAFE: Uses GC ref for array operations */
TEST(array_pop) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 3));
JSGCRef arr_ref;
JS_AddGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 2));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 3));
JSValue popped = JS_ArrayPop(ctx, arr);
JSValue popped = JS_ArrayPop(ctx, arr_ref.val);
ASSERT_INT(popped, 3);
int64_t len;
JS_GetLength(ctx, arr, &len);
JS_GetLength(ctx, arr_ref.val, &len);
JS_DeleteGCRef(ctx, &arr_ref);
ASSERT(len == 2);
return 1;
}
/* GC-SAFE: Uses GC ref for array operations */
TEST(array_out_of_bounds_is_null) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 1));
JSGCRef arr_ref;
JS_AddGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1));
JSValue val = JS_GetPropertyUint32(ctx, arr, 999);
JSValue val = JS_GetPropertyUint32(ctx, arr_ref.val, 999);
JS_DeleteGCRef(ctx, &arr_ref);
ASSERT(JS_IsNull(val));
return 1;
}
/* GC-SAFE: Uses GC ref for array operations */
TEST(array_mixed_types) {
JSValue arr = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr, JS_NewInt32(ctx, 42));
JS_ArrayPush(ctx, &arr, JS_NewString(ctx, "hello"));
JS_ArrayPush(ctx, &arr, JS_TRUE);
JS_ArrayPush(ctx, &arr, JS_NULL);
JSGCRef arr_ref;
JS_AddGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 42));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewString(ctx, "hello"));
JS_ArrayPush(ctx, &arr_ref.val, JS_TRUE);
JS_ArrayPush(ctx, &arr_ref.val, JS_NULL);
JSValue v0 = JS_GetPropertyUint32(ctx, arr, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr, 2);
JSValue v3 = JS_GetPropertyUint32(ctx, arr, 3);
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr_ref.val, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr_ref.val, 2);
JSValue v3 = JS_GetPropertyUint32(ctx, arr_ref.val, 3);
JS_DeleteGCRef(ctx, &arr_ref);
ASSERT(JS_IsInt(v0));
ASSERT(JS_IsText(v1));