more gc correctness
This commit is contained in:
322
source/quickjs.c
322
source/quickjs.c
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
129
source/suite.c
129
source/suite.c
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user