From 3c842912a104b828233e559392ecb43473ec3d3e Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 7 Feb 2026 14:24:49 -0600 Subject: [PATCH] gc fixing in mach vm --- source/quickjs.c | 517 +++++++++++++++++++++++++++++++---------------- 1 file changed, 339 insertions(+), 178 deletions(-) diff --git a/source/quickjs.c b/source/quickjs.c index 041d0bd0..f65e2419 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -104,7 +104,7 @@ // #define DUMP_ROPE_REBALANCE /* test the GC by forcing it before each object allocation */ -/* #define FORCE_GC_AT_MALLOC */ +#define FORCE_GC_AT_MALLOC #define POISON_HEAP /* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ @@ -4055,7 +4055,18 @@ static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char * f->u.cfunc.cproto = cproto; f->u.cfunc.magic = magic; f->length = length; - f->name = name ? js_key_new (ctx, name) : JS_KEY_empty; + if (name) { + JSGCRef fobj_ref; + JS_PushGCRef (ctx, &fobj_ref); + fobj_ref.val = func_obj; + JSValue key = js_key_new (ctx, name); + func_obj = fobj_ref.val; + JS_PopGCRef (ctx, &fobj_ref); + f = JS_VALUE_GET_FUNCTION (func_obj); /* re-chase after allocation */ + f->name = key; + } else { + f->name = JS_KEY_empty; + } return func_obj; } @@ -4141,8 +4152,16 @@ static int js_intrinsic_array_set (JSContext *ctx, JSValue *arr_ptr, word_t idx, } if (idx >= js_array_cap (arr)) { - if (js_array_grow (ctx, arr_ptr, idx + 1) < 0) + /* Root val across js_array_grow which can trigger GC */ + JSGCRef val_ref; + JS_PushGCRef (ctx, &val_ref); + val_ref.val = val; + if (js_array_grow (ctx, arr_ptr, idx + 1) < 0) { + JS_PopGCRef (ctx, &val_ref); return -1; + } + val = val_ref.val; + JS_PopGCRef (ctx, &val_ref); arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ } @@ -4863,12 +4882,17 @@ JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop) { /* JS_Invoke - invoke a method on an object by name */ static JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv) { - JSValue func = JS_GetProperty (ctx, this_val, method); - if (JS_IsException (func)) return JS_EXCEPTION; + JSGCRef this_ref; + JS_PushGCRef (ctx, &this_ref); + this_ref.val = this_val; + JSValue func = JS_GetProperty (ctx, this_ref.val, method); + if (JS_IsException (func)) { JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } if (!JS_IsFunction (func)) { + JS_PopGCRef (ctx, &this_ref); return JS_NULL; /* Method not found or not callable */ } - JSValue ret = JS_Call (ctx, func, this_val, argc, argv); + JSValue ret = JS_Call (ctx, func, this_ref.val, argc, argv); + JS_PopGCRef (ctx, &this_ref); return ret; } @@ -21890,35 +21914,49 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue return JS_EXCEPTION; } + /* Root b across allocating calls (JS_GetPropertyInt64, JS_ToString) */ + JSGCRef b_ref; + JS_AddGCRef (ctx, &b_ref); + b_ref.val = JS_MKPTR (b); + for (int64_t i = 0; i < len; i++) { if (i > 0 && separator[0]) { + b = (JSText *)chase (b_ref.val); b = pretext_puts8 (ctx, b, separator); if (!b) goto array_fail; + b_ref.val = JS_MKPTR (b); } + b = (JSText *)chase (b_ref.val); /* re-chase before use */ JSValue item = JS_GetPropertyInt64 (ctx, arg_ref.val, i); if (JS_IsException (item)) goto array_fail; if (!JS_VALUE_IS_TEXT (item)) { if (sep_alloc) JS_FreeCString (ctx, separator); - JS_DeleteGCRef(ctx, &arg_ref); + JS_DeleteGCRef (ctx, &b_ref); + JS_DeleteGCRef (ctx, &arg_ref); return JS_ThrowTypeError (ctx, "text: array element is not a string"); } JSValue item_str = JS_ToString (ctx, item); if (JS_IsException (item_str)) goto array_fail; + b = (JSText *)chase (b_ref.val); /* re-chase after JS_ToString */ b = pretext_concat_value (ctx, b, item_str); if (!b) goto array_fail; + b_ref.val = JS_MKPTR (b); } + b = (JSText *)chase (b_ref.val); if (sep_alloc) JS_FreeCString (ctx, separator); - JS_DeleteGCRef(ctx, &arg_ref); + JS_DeleteGCRef (ctx, &b_ref); + JS_DeleteGCRef (ctx, &arg_ref); return pretext_end (ctx, b); array_fail: if (sep_alloc) JS_FreeCString (ctx, separator); - JS_DeleteGCRef(ctx, &arg_ref); + JS_DeleteGCRef (ctx, &b_ref); + JS_DeleteGCRef (ctx, &arg_ref); return JS_EXCEPTION; } @@ -22213,10 +22251,23 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; + /* Root b across all allocating calls */ + JSGCRef b_ref; + JS_PushGCRef (ctx, &b_ref); + b_ref.val = JS_MKPTR (b); + +/* Macro to re-chase b from GC ref before use */ +#define B_RECHASE() b = (JSText *)chase (b_ref.val) +/* Macro to update b_ref after b changes */ +#define B_UPDATE(new_b) do { b = (new_b); b_ref.val = JS_MKPTR (b); } while(0) +#define B_CLEANUP() JS_PopGCRef (ctx, &b_ref) + if (!target_is_regex) { - if (!JS_VALUE_IS_TEXT (argv[1])) + if (!JS_VALUE_IS_TEXT (argv[1])) { + B_CLEANUP (); return JS_ThrowInternalError ( ctx, "Second arg of replace must be pattern or text."); + } int t_len = js_string_value_len (argv[1]); @@ -22227,27 +22278,30 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (limit >= 0 && count >= limit) break; JSValue match = JS_KEY_empty; - if (JS_IsException (match)) goto fail_str_target; + if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } JSValue rep = make_replacement (ctx, argc, argv, boundary, match); - if (JS_IsException (rep)) goto fail_str_target; + if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } count++; if (!JS_IsNull (rep)) { - b = pt_concat_value_to_string_free (ctx, b, rep); - if (!b) goto fail_str_target; - } else { + B_RECHASE (); + B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); + if (!b) { B_CLEANUP (); goto fail_str_target; } } if (boundary < len) { JSValue ch = js_sub_string_val (ctx, argv[0], boundary, boundary + 1); - if (JS_IsException (ch)) goto fail_str_target; - b = pretext_concat_value (ctx, b, ch); - if (!b) goto fail_str_target; + if (JS_IsException (ch)) { B_CLEANUP (); goto fail_str_target; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, ch)); + if (!b) { B_CLEANUP (); goto fail_str_target; } } } + B_RECHASE (); + B_CLEANUP (); return pretext_end (ctx, b); } @@ -22274,39 +22328,39 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (found < 0) break; if (found > pos) { - /* For substring extraction, need to build manually if immediate string */ int sub_len = found - pos; JSText *sub_str = js_alloc_string (ctx, sub_len); - if (!sub_str) goto fail_str_target; + if (!sub_str) { B_CLEANUP (); goto fail_str_target; } for (int i = 0; i < sub_len; i++) { string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); } sub_str->length = sub_len; JSValue sub = pretext_end (ctx, sub_str); - if (JS_IsException (sub)) goto fail_str_target; - b = pretext_concat_value (ctx, b, sub); - if (!b) goto fail_str_target; + if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, sub)); + if (!b) { B_CLEANUP (); goto fail_str_target; } } /* Build match substring manually */ JSText *match_str = js_alloc_string (ctx, t_len); - if (!match_str) goto fail_str_target; + if (!match_str) { B_CLEANUP (); goto fail_str_target; } for (int i = 0; i < t_len; i++) { string_put (match_str, i, js_string_value_get (argv[0], found + i)); } match_str->length = t_len; JSValue match = pretext_end (ctx, match_str); - if (JS_IsException (match)) goto fail_str_target; + if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } JSValue rep = make_replacement (ctx, argc, argv, found, match); - if (JS_IsException (rep)) goto fail_str_target; + if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } count++; if (!JS_IsNull (rep)) { - b = pt_concat_value_to_string_free (ctx, b, rep); - if (!b) goto fail_str_target; - } else { + B_RECHASE (); + B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); + if (!b) { B_CLEANUP (); goto fail_str_target; } } pos = found + t_len; @@ -22315,17 +22369,20 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (pos < len) { int sub_len = len - pos; JSText *sub_str = js_alloc_string (ctx, sub_len); - if (!sub_str) goto fail_str_target; + if (!sub_str) { B_CLEANUP (); goto fail_str_target; } for (int i = 0; i < sub_len; i++) { string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); } sub_str->length = sub_len; JSValue sub = pretext_end (ctx, sub_str); - if (JS_IsException (sub)) goto fail_str_target; - b = pretext_concat_value (ctx, b, sub); - if (!b) goto fail_str_target; + if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, sub)); + if (!b) { B_CLEANUP (); goto fail_str_target; } } + B_RECHASE (); + B_CLEANUP (); return pretext_end (ctx, b); fail_str_target: @@ -22335,36 +22392,33 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, /* Regex target */ JSValue rx = argv[1]; JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); - if (JS_IsException (orig_last_index)) goto fail_rx; + if (JS_IsException (orig_last_index)) { B_CLEANUP (); goto fail_rx; } int have_orig_last_index = 1; int pos = 0; int32_t count = 0; while (pos <= len && (limit < 0 || count < limit)) { - if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) - goto fail_rx; + if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) { + B_CLEANUP (); goto fail_rx; + } JSValue sub_str = js_sub_string_val (ctx, argv[0], pos, len); - if (JS_IsException (sub_str)) goto fail_rx; + if (JS_IsException (sub_str)) { B_CLEANUP (); goto fail_rx; } JSValue exec_res = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_res)) goto fail_rx; + if (JS_IsException (exec_res)) { B_CLEANUP (); goto fail_rx; } if (JS_IsNull (exec_res)) { break; } JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); - if (JS_IsException (idx_val)) { - goto fail_rx; - } + if (JS_IsException (idx_val)) { B_CLEANUP (); goto fail_rx; } int32_t local_index = 0; - if (JS_ToInt32 (ctx, &local_index, idx_val)) { - goto fail_rx; - } + if (JS_ToInt32 (ctx, &local_index, idx_val)) { B_CLEANUP (); goto fail_rx; } if (local_index < 0) local_index = 0; int found = pos + local_index; @@ -22374,39 +22428,34 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, } JSValue match = JS_GetPropertyStr (ctx, exec_res, "match"); - if (JS_IsException (match)) { - goto fail_rx; - } + if (JS_IsException (match)) { B_CLEANUP (); goto fail_rx; } JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); - if (JS_IsException (end_val)) { - goto fail_rx; - } + if (JS_IsException (end_val)) { B_CLEANUP (); goto fail_rx; } int32_t end = 0; - if (JS_ToInt32 (ctx, &end, end_val)) { - goto fail_rx; - } + if (JS_ToInt32 (ctx, &end, end_val)) { B_CLEANUP (); goto fail_rx; } int match_len = end - local_index; if (match_len < 0) match_len = 0; if (found > pos) { JSValue prefix = js_sub_string_val (ctx, argv[0], pos, found); - if (JS_IsException (prefix)) goto fail_rx; - b = pretext_concat_value (ctx, b, prefix); - if (!b) goto fail_rx; + if (JS_IsException (prefix)) { B_CLEANUP (); goto fail_rx; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, prefix)); + if (!b) { B_CLEANUP (); goto fail_rx; } } JSValue rep = make_replacement (ctx, argc, argv, found, match); - if (JS_IsException (rep)) goto fail_rx; + if (JS_IsException (rep)) { B_CLEANUP (); goto fail_rx; } count++; if (!JS_IsNull (rep)) { - b = pt_concat_value_to_string_free (ctx, b, rep); - if (!b) goto fail_rx; - } else { + B_RECHASE (); + B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); + if (!b) { B_CLEANUP (); goto fail_rx; } } pos = found + match_len; @@ -22420,14 +22469,17 @@ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, if (pos < len) { JSValue tail = js_sub_string_val (ctx, argv[0], pos, len); - if (JS_IsException (tail)) goto fail_rx; - b = pretext_concat_value (ctx, b, tail); - if (!b) goto fail_rx; + if (JS_IsException (tail)) { B_CLEANUP (); goto fail_rx; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, tail)); + if (!b) { B_CLEANUP (); goto fail_rx; } } if (have_orig_last_index) JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); + B_RECHASE (); + B_CLEANUP (); return pretext_end (ctx, b); fail_rx: @@ -22438,6 +22490,10 @@ fail_rx: return JS_EXCEPTION; } +#undef B_RECHASE +#undef B_UPDATE +#undef B_CLEANUP + /* text.search(str, target, from) - find substring or regex match */ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; @@ -22733,11 +22789,31 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, int len = js_string_value_len (text_val); + /* Root text_val, collection, transformer BEFORE any allocation */ + JSGCRef res_ref, text_ref, coll_ref, xform_ref; + JS_PushGCRef (ctx, &res_ref); + JS_PushGCRef (ctx, &text_ref); + JS_PushGCRef (ctx, &coll_ref); + JS_PushGCRef (ctx, &xform_ref); + res_ref.val = JS_NULL; + text_ref.val = text_val; + coll_ref.val = collection; + xform_ref.val = transformer; + +#define FMT_CLEANUP() do { \ + JS_PopGCRef (ctx, &xform_ref); \ + JS_PopGCRef (ctx, &coll_ref); \ + JS_PopGCRef (ctx, &text_ref); \ + JS_PopGCRef (ctx, &res_ref); \ +} while(0) + JSText *result = pretext_init (ctx, len); - if (!result) return JS_EXCEPTION; + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); int pos = 0; while (pos < len) { + text_val = text_ref.val; /* Find next '{' */ int brace_start = -1; for (int i = pos; i < len; i++) { @@ -22749,21 +22825,26 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, if (brace_start < 0) { /* No more braces, copy rest of string */ - JSValue tail = js_sub_string_val (ctx, text_val, pos, len); - if (JS_IsException (tail)) return JS_EXCEPTION; + JSValue tail = js_sub_string_val (ctx, text_ref.val, pos, len); + if (JS_IsException (tail)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, tail); + if (result) res_ref.val = JS_MKPTR (result); break; } /* Copy text before brace */ if (brace_start > pos) { - JSValue prefix = js_sub_string_val (ctx, text_val, pos, brace_start); - if (JS_IsException (prefix)) return JS_EXCEPTION; + JSValue prefix = js_sub_string_val (ctx, text_ref.val, pos, brace_start); + if (JS_IsException (prefix)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, prefix); - if (!result) return JS_EXCEPTION; + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); } /* Find closing '}' */ + text_val = text_ref.val; int brace_end = -1; for (int i = brace_start + 1; i < len; i++) { if (js_string_value_get (text_val, i) == '}') { @@ -22774,17 +22855,19 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, if (brace_end < 0) { /* No closing brace, copy '{' and continue */ - JSValue ch = js_sub_string_val (ctx, text_val, brace_start, brace_start + 1); - if (JS_IsException (ch)) return JS_EXCEPTION; + JSValue ch = js_sub_string_val (ctx, text_ref.val, brace_start, brace_start + 1); + if (JS_IsException (ch)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, ch); - if (!result) return JS_EXCEPTION; + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); pos = brace_start + 1; continue; } /* Extract content between braces */ - JSValue middle = js_sub_string_val (ctx, text_val, brace_start + 1, brace_end); - if (JS_IsException (middle)) return JS_EXCEPTION; + JSValue middle = js_sub_string_val (ctx, text_ref.val, brace_start + 1, brace_end); + if (JS_IsException (middle)) { FMT_CLEANUP(); return JS_EXCEPTION; } /* Split on ':' to get name and format_spec */ int middle_len = js_string_value_len (middle); @@ -22809,8 +22892,6 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, /* Get value from collection */ JSValue coll_value = JS_NULL; if (is_array) { - /* Parse name as integer index — parse digits from the text directly - since JS_ToInt32 doesn't handle text values */ int name_len = js_string_value_len (name_val); int32_t idx = 0; int valid = (name_len > 0); @@ -22822,30 +22903,26 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, valid = 0; } if (valid && idx >= 0) { - coll_value = JS_GetPropertyUint32 (ctx, collection, (uint32_t)idx); + coll_value = JS_GetPropertyUint32 (ctx, coll_ref.val, (uint32_t)idx); } } else { - /* Use name as key */ - coll_value = JS_GetProperty (ctx, collection, name_val); + coll_value = JS_GetProperty (ctx, coll_ref.val, name_val); } /* Try to get substitution */ JSValue substitution = JS_NULL; int made_substitution = 0; - /* Try transformer first */ - if (!JS_IsNull (transformer)) { - if (JS_IsFunction (transformer)) { - /* transformer(value, format_spec) */ + if (!JS_IsNull (xform_ref.val)) { + if (JS_IsFunction (xform_ref.val)) { JSValue args[2] = { coll_value, format_spec }; - JSValue result_val = JS_Call (ctx, transformer, JS_NULL, 2, args); + JSValue result_val = JS_Call (ctx, xform_ref.val, JS_NULL, 2, args); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } - } else if (JS_IsRecord (transformer)) { - /* transformer[format_spec](value) */ - JSValue func = JS_GetProperty (ctx, transformer, format_spec); + } else if (JS_IsRecord (xform_ref.val)) { + JSValue func = JS_GetProperty (ctx, xform_ref.val, format_spec); if (JS_IsFunction (func)) { JSValue result_val = JS_Call (ctx, func, JS_NULL, 1, &coll_value); if (JS_IsText (result_val)) { @@ -22856,7 +22933,6 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, } } - /* If no transformer match and value is number, try number.text(format) */ if (!made_substitution && JS_IsNumber (coll_value) && !JS_IsNull (format_spec)) { JSValue text_method = JS_GetPropertyStr (ctx, coll_value, "text"); if (JS_IsFunction (text_method)) { @@ -22868,7 +22944,6 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, } } - /* If still no substitution but we have a value, convert to text */ if (!made_substitution && !JS_IsNull (coll_value)) { JSValue conv_text_val = JS_ToString (ctx, coll_value); if (JS_IsText (conv_text_val)) { @@ -22877,20 +22952,26 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, } } + result = (JSText *)chase (res_ref.val); if (made_substitution) { result = pretext_concat_value (ctx, result, substitution); - if (!result) return JS_EXCEPTION; + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); } else { - /* No substitution, keep original {name} or {name:format} */ - JSValue orig = js_sub_string_val (ctx, text_val, brace_start, brace_end + 1); - if (JS_IsException (orig)) return JS_EXCEPTION; + JSValue orig = js_sub_string_val (ctx, text_ref.val, brace_start, brace_end + 1); + if (JS_IsException (orig)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, orig); - if (!result) return JS_EXCEPTION; + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); } pos = brace_end + 1; } + result = (JSText *)chase (res_ref.val); + FMT_CLEANUP(); +#undef FMT_CLEANUP return pretext_end (ctx, result); } @@ -23216,29 +23297,32 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu if (argc > 1 && JS_IsFunction (argv[1])) { /* Fill with function results - GC-safe */ - JSValue func = argv[1]; - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func))->length; + JSGCRef func_ref; + JS_PushGCRef (ctx, &func_ref); + func_ref.val = argv[1]; + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; if (arity >= 1) { for (int i = 0; i < len; i++) { JSValue idx_arg = JS_NewInt32 (ctx, i); JS_PUSH_VALUE (ctx, result); - JSValue val = JS_CallInternal (ctx, func, JS_NULL, 1, &idx_arg, 0); + JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &idx_arg, 0); JS_POP_VALUE (ctx, result); - if (JS_IsException (val)) return JS_EXCEPTION; + if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ out->values[i] = val; } } else { for (int i = 0; i < len; i++) { JS_PUSH_VALUE (ctx, result); - JSValue val = JS_CallInternal (ctx, func, JS_NULL, 0, NULL, 0); + JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); JS_POP_VALUE (ctx, result); - if (JS_IsException (val)) return JS_EXCEPTION; + if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ out->values[i] = val; } } + JS_PopGCRef (ctx, &func_ref); } else if (argc > 1) { /* Fill with value */ JSArray *out = JS_VALUE_GET_ARRAY (result); @@ -23642,13 +23726,14 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; - /* GC-safe: root argv[0] for the duration of this function */ - JSGCRef arr_ref; + /* GC-safe: root argv[0] and argv[1] for the duration of this function */ + JSGCRef arr_ref, func_ref; JS_PushGCRef (ctx, &arr_ref); + JS_PushGCRef (ctx, &func_ref); arr_ref.val = argv[0]; + func_ref.val = argv[1]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - JSValue func = argv[1]; word_t len = arr->len; int reverse = argc > 3 && JS_ToBool (ctx, argv[3]); @@ -23656,8 +23741,8 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue acc; if (argc < 3 || JS_IsNull (argv[2])) { - if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } - if (len == 1) { JS_PopGCRef (ctx, &arr_ref); return arr->values[0]; } + if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + if (len == 1) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return arr->values[0]; } if (reverse) { acc = arr->values[len - 1]; @@ -23666,9 +23751,9 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, if (i - 1 >= arr->len) continue; JSValue args[2] = { acc, arr->values[i - 1] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } else { @@ -23678,14 +23763,14 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, if (i >= arr->len) break; JSValue args[2] = { acc, arr->values[i] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } } else { - if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return argv[2]; } + if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return argv[2]; } acc = argv[2]; if (reverse) { @@ -23694,9 +23779,9 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, if (i - 1 >= arr->len) continue; JSValue args[2] = { acc, arr->values[i - 1] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } else { @@ -23705,14 +23790,15 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, if (i >= arr->len) break; JSValue args[2] = { acc, arr->values[i] }; JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } } + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return acc; } @@ -23723,22 +23809,23 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; - /* GC-safe: root argv[0] */ - JSGCRef arr_ref; + /* GC-safe: root argv[0] and argv[1] */ + JSGCRef arr_ref, func_ref; JS_PushGCRef (ctx, &arr_ref); + JS_PushGCRef (ctx, &func_ref); arr_ref.val = argv[0]; + func_ref.val = argv[1]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - JSValue func = argv[1]; word_t len = arr->len; - if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; /* Determine function arity */ - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (argv[1]))->length; + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; if (reverse) { for (word_t i = len; i > 0; i--) { @@ -23748,17 +23835,17 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS JSValue result; if (arity == 1) { JSValue item = arr->values[i - 1]; - result = JS_CallInternal (ctx, func, JS_NULL, 1, &item, 0); + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); } else { JSValue args[2]; args[0] = arr->values[i - 1]; args[1] = JS_NewInt32 (ctx, (int32_t)(i - 1)); - result = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); } - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { - JS_PopGCRef (ctx, &arr_ref); + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return result; } } @@ -23770,22 +23857,23 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS JSValue result; if (arity == 1) { JSValue item = arr->values[i]; - result = JS_CallInternal (ctx, func, JS_NULL, 1, &item, 0); + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); } else { JSValue args[2]; args[0] = arr->values[i]; args[1] = JS_NewInt32 (ctx, (int32_t)i); - result = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); } - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { - JS_PopGCRef (ctx, &arr_ref); + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return result; } } } + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } @@ -23839,8 +23927,10 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J } /* Use function predicate - must re-chase after each call */ - JSValue func = argv[1]; - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (argv[1]))->length; + JSGCRef func_ref; + JS_PushGCRef (ctx, &func_ref); + func_ref.val = argv[1]; + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; if (arity == 2) { if (reverse) { @@ -23848,10 +23938,10 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J arr = JS_VALUE_GET_ARRAY (arr_ref.val); if ((word_t)i >= arr->len) continue; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; - JSValue result = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &arr_ref); + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, i); } } @@ -23860,10 +23950,10 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, (int32_t)i) }; - JSValue result = JS_CallInternal (ctx, func, JS_NULL, 2, args, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &arr_ref); + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, (int32_t)i); } } @@ -23874,10 +23964,10 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J arr = JS_VALUE_GET_ARRAY (arr_ref.val); if ((word_t)i >= arr->len) continue; JSValue item = arr->values[i]; - JSValue result = JS_CallInternal (ctx, func, JS_NULL, 1, &item, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &arr_ref); + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, i); } } @@ -23886,16 +23976,17 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue item = arr->values[i]; - JSValue result = JS_CallInternal (ctx, func, JS_NULL, 1, &item, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &arr_ref); + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, (int32_t)i); } } } } + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } @@ -23998,6 +24089,11 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return result; } + /* Root result across allocating calls */ + JSGCRef result_ref; + JS_PushGCRef (ctx, &result_ref); + result_ref.val = result; + /* Re-chase arr after allocation */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); len = arr->len; @@ -24030,10 +24126,16 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J key = JS_NULL; } else { key = JS_GetPropertyInt64 (ctx, items[i], idx); + /* Re-read items[i] after potential GC */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + items[i] = arr->values[i]; } } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM) { JSValue prop_key = js_key_from_string (ctx, argv[1]); + /* Re-read items[i] after allocation (js_key_from_string can trigger GC) */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + items[i] = arr->values[i]; key = JS_GetProperty (ctx, items[i], prop_key); } else if (argc >= 2 && JS_IsArray (argv[1])) { /* Re-chase key array */ @@ -24052,6 +24154,7 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J JS_FreeCString (ctx, str_keys[j]); js_free (ctx, str_keys); } + JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } @@ -24071,11 +24174,17 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J for (word_t j = 0; j < i; j++) JS_FreeCString (ctx, str_keys[j]); } + JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } } + /* Re-read all items from GC-safe source after key extraction (GC may have moved them) */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + for (word_t i = 0; i < len && i < arr->len; i++) + items[i] = arr->values[i]; + /* Create index array using alloca */ int *indices = alloca (sizeof (int) * len); for (word_t i = 0; i < len; i++) @@ -24101,7 +24210,7 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J } /* Build sorted array directly into output - re-chase result */ - JSArray *out = JS_VALUE_GET_ARRAY (result); + JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val); for (word_t i = 0; i < len; i++) { out->values[i] = items[indices[i]]; } @@ -24113,6 +24222,8 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J JS_FreeCString (ctx, str_keys[i]); } + result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arr_ref); return result; } @@ -25523,34 +25634,42 @@ static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue if (argc < 2) return result; + /* Root result across allocating calls */ + JSGCRef result_ref; + JS_PushGCRef (ctx, &result_ref); + result_ref.val = result; + /* Helper function to apply a single mixin */ -#define APPLY_MIXIN(mix) \ +#define APPLY_MIXIN(mix_val) \ do { \ - if (!JS_IsObject (mix) || JS_IsNull (mix) || JS_IsArray (mix)) \ + if (!JS_IsObject (mix_val) || JS_IsNull (mix_val) || JS_IsArray (mix_val)) \ break; \ - JSValue _keys = JS_GetOwnPropertyNames (ctx, mix); \ + JSGCRef _mix_ref; \ + JS_PushGCRef (ctx, &_mix_ref); \ + _mix_ref.val = mix_val; \ + JSValue _keys = JS_GetOwnPropertyNames (ctx, _mix_ref.val); \ if (JS_IsException (_keys)) { \ - ; \ + JS_PopGCRef (ctx, &_mix_ref); \ + JS_PopGCRef (ctx, &result_ref); \ return JS_EXCEPTION; \ } \ uint32_t _len; \ if (js_get_length32 (ctx, &_len, _keys)) { \ - ; \ - ; \ + JS_PopGCRef (ctx, &_mix_ref); \ + JS_PopGCRef (ctx, &result_ref); \ return JS_EXCEPTION; \ } \ for (uint32_t j = 0; j < _len; j++) { \ JSValue _key = JS_GetPropertyUint32 (ctx, _keys, j); \ - JSValue val = JS_GetProperty (ctx, mix, _key); \ + JSValue val = JS_GetProperty (ctx, _mix_ref.val, _key); \ if (JS_IsException (val)) { \ - ; \ - ; \ - ; \ + JS_PopGCRef (ctx, &_mix_ref); \ + JS_PopGCRef (ctx, &result_ref); \ return JS_EXCEPTION; \ } \ - JS_SetProperty (ctx, result, _key, val); \ + JS_SetProperty (ctx, result_ref.val, _key, val); \ } \ - ; \ + JS_PopGCRef (ctx, &_mix_ref); \ } while (0) /* Process all arguments starting from argv[1] as mixins */ @@ -25558,19 +25677,27 @@ static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue JSValue mixins = argv[i]; if (JS_IsArray (mixins)) { - /* Array of mixins */ + /* Array of mixins - root the array across calls */ + JSGCRef mixins_ref; + JS_PushGCRef (ctx, &mixins_ref); + mixins_ref.val = mixins; int64_t len; - if (js_get_length64 (ctx, &len, mixins)) { + if (js_get_length64 (ctx, &len, mixins_ref.val)) { + JS_PopGCRef (ctx, &mixins_ref); + JS_PopGCRef (ctx, &result_ref); return JS_EXCEPTION; } for (int64_t j = 0; j < len; j++) { - JSValue mix = JS_GetPropertyInt64 (ctx, mixins, j); + JSValue mix = JS_GetPropertyInt64 (ctx, mixins_ref.val, j); if (JS_IsException (mix)) { + JS_PopGCRef (ctx, &mixins_ref); + JS_PopGCRef (ctx, &result_ref); return JS_EXCEPTION; } APPLY_MIXIN (mix); } + JS_PopGCRef (ctx, &mixins_ref); } else if (JS_IsObject (mixins) && !JS_IsNull (mixins)) { /* Single mixin object */ APPLY_MIXIN (mixins); @@ -25579,6 +25706,8 @@ static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue #undef APPLY_MIXIN + result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); return result; } @@ -33122,9 +33251,20 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JS_PushGCRef(ctx, &of_gc); of_gc.val = outer_frame; + /* Protect argv and this_obj from GC by pushing onto value_stack. + argv is a C-allocated array whose JSValues may point to GC heap objects; + alloc_frame_register and js_new_register_function can trigger GC. */ + int vs_save = ctx->value_stack_top; + int nargs_copy = (argc < code->arity) ? argc : code->arity; + ctx->value_stack[vs_save] = this_obj; + for (int i = 0; i < nargs_copy; i++) + ctx->value_stack[vs_save + 1 + i] = argv[i]; + ctx->value_stack_top = vs_save + 1 + nargs_copy; + /* Allocate initial frame */ JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); if (!frame) { + ctx->value_stack_top = vs_save; JS_PopGCRef(ctx, &of_gc); JS_PopGCRef(ctx, &env_gc); return JS_EXCEPTION; @@ -33145,12 +33285,13 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JS_PopGCRef(ctx, &env_gc); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->function = top_fn; - frame->slots[0] = this_obj; /* slot 0 = this */ + frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 = this (GC-safe from value_stack) */ - /* Copy arguments */ - for (int i = 0; i < argc && i < code->arity; i++) { - frame->slots[1 + i] = argv[i]; + /* Copy arguments from GC-safe value_stack */ + for (int i = 0; i < nargs_copy; i++) { + frame->slots[1 + i] = ctx->value_stack[vs_save + 1 + i]; } + ctx->value_stack_top = vs_save; uint32_t pc = code->entry_point; JSValue result = JS_NULL; @@ -33542,13 +33683,15 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, Else (record): get property, call property(obj_as_this, args...) */ int base = a; int nargs = b; - JSValue key = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c]; + JSGCRef key_ref; + JS_PushGCRef(ctx, &key_ref); + key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c]; - if (JS_IsFunction(frame->slots[base]) && JS_IsText(key)) { + if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val)) { /* Proxy call: obj(name, [args...]) */ JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(arr)) goto disrupt; + if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } frame->slots[base + 1] = arr; /* protect from GC in temp slot */ for (int i = 0; i < nargs; i++) { JS_SetPropertyUint32(ctx, frame->slots[base + 1], i, frame->slots[base + 2 + i]); @@ -33556,7 +33699,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, } /* Push proxy args onto value stack; re-read obj since GC may have moved it */ int vs_base = ctx->value_stack_top; - ctx->value_stack[vs_base] = key; + ctx->value_stack[vs_base] = key_ref.val; ctx->value_stack[vs_base + 1] = frame->slots[base + 1]; /* the array */ ctx->value_stack_top = vs_base + 2; ctx->reg_current_frame = frame_ref.val; @@ -33565,19 +33708,21 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, ctx->value_stack_top = vs_base; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); ctx->reg_current_frame = JS_NULL; - if (JS_IsException(ret)) goto disrupt; + if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } frame->slots[base] = ret; } else if (JS_IsFunction(frame->slots[base])) { /* Non-proxy function with non-text key: disrupt */ JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function"); + JS_PopGCRef(ctx, &key_ref); goto disrupt; } else { /* Record method call: get property, call with this=obj */ - JSValue method = JS_GetProperty(ctx, frame->slots[base], key); + JSValue method = JS_GetProperty(ctx, frame->slots[base], key_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(method)) goto disrupt; + if (JS_IsException(method)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } if (!JS_IsFunction(method)) { frame->slots[base] = JS_NULL; + JS_PopGCRef(ctx, &key_ref); break; } JSFunction *fn = JS_VALUE_GET_FUNCTION(method); @@ -33592,17 +33737,18 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, ctx->value_stack_top = vs_base; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); ctx->reg_current_frame = JS_NULL; - if (JS_IsException(ret)) goto disrupt; + if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } frame->slots[base] = ret; } else if (fn->kind == JS_FUNC_KIND_REGISTER) { JSCodeRegister *fn_code = fn->u.reg.code; JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); if (!new_frame) { frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + JS_PopGCRef(ctx, &key_ref); goto disrupt; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - method = JS_GetProperty(ctx, frame->slots[base], key); + method = JS_GetProperty(ctx, frame->slots[base], key_ref.val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); fn = JS_VALUE_GET_FUNCTION(method); new_frame->function = method; @@ -33626,10 +33772,11 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue ret = JS_CallInternal(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base], 0); ctx->value_stack_top = vs_base; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(ret)) goto disrupt; + if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } frame->slots[base] = ret; } } + JS_PopGCRef(ctx, &key_ref); break; } @@ -33785,6 +33932,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, Use frame_pc to track each frame's execution point: - For the faulting frame, it's the current pc. - For unwound caller frames, read from frame->address. */ + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* re-chase after GC */ { uint32_t frame_pc = pc; for (;;) { @@ -35467,8 +35615,18 @@ static JSValue js_new_mcode_function(JSContext *ctx, JSMCode *code) { /* Main MCODE interpreter — executes pre-parsed JSMCode */ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, int argc, JSValue *argv, JSValue outer_frame) { + /* Protect argv, this_obj, outer_frame from GC by pushing onto value_stack. + alloc_frame_register and js_new_mcode_function can trigger GC. */ + int vs_save = ctx->value_stack_top; + int nargs_copy = (argc < code->nr_args) ? argc : code->nr_args; + ctx->value_stack[vs_save] = this_obj; + ctx->value_stack[vs_save + 1] = outer_frame; + for (int i = 0; i < nargs_copy; i++) + ctx->value_stack[vs_save + 2 + i] = argv[i]; + ctx->value_stack_top = vs_save + 2 + nargs_copy; + JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); - if (!frame) return JS_EXCEPTION; + if (!frame) { ctx->value_stack_top = vs_save; return JS_EXCEPTION; } /* Protect frame from GC */ JSGCRef frame_ref; @@ -35478,20 +35636,22 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, /* Create a function object for the main frame so return can find the code */ JSValue main_func = js_new_mcode_function(ctx, code); if (JS_IsException(main_func)) { + ctx->value_stack_top = vs_save; JS_DeleteGCRef(ctx, &frame_ref); return JS_ThrowInternalError(ctx, "failed to allocate main function for MCODE"); } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - /* Set outer_frame AFTER allocation so it uses the post-GC frame pointer */ + /* Set outer_frame AFTER allocation so it uses the post-GC value */ JSFunction *main_fn = JS_VALUE_GET_FUNCTION(main_func); - main_fn->u.mcode.outer_frame = outer_frame; + main_fn->u.mcode.outer_frame = ctx->value_stack[vs_save + 1]; frame->function = main_func; - /* Setup initial frame */ - frame->slots[0] = this_obj; /* slot 0 is this */ - for (int i = 0; i < argc && i < code->nr_args; i++) { - frame->slots[1 + i] = argv[i]; + /* Setup initial frame from GC-safe value_stack */ + frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 is this */ + for (int i = 0; i < nargs_copy; i++) { + frame->slots[1 + i] = ctx->value_stack[vs_save + 2 + i]; } + ctx->value_stack_top = vs_save; uint32_t pc = 0; JSValue result = JS_NULL; @@ -36662,6 +36822,7 @@ static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, Use frame_pc to track each frame's execution point: - For the faulting frame, it's the current pc. - For unwound caller frames, read from frame->address. */ + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* re-chase after GC */ { uint32_t frame_pc = pc; for (;;) {