diff --git a/source/quickjs.c b/source/quickjs.c index d54a50eb..6b11fee8 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; } @@ -4148,8 +4159,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 */ } @@ -4870,12 +4889,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; } @@ -21897,35 +21921,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; } @@ -22220,10 +22258,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 *)JS_VALUE_GET_PTR (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]); @@ -22234,27 +22285,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); } @@ -22281,39 +22335,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; @@ -22322,17 +22376,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: @@ -22342,36 +22399,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; @@ -22381,39 +22435,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; @@ -22427,14 +22476,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: @@ -22445,6 +22497,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; @@ -22740,11 +22796,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++) { @@ -22756,21 +22832,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) == '}') { @@ -22781,17 +22862,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); @@ -22816,8 +22899,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); @@ -22829,30 +22910,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)) { @@ -22863,7 +22940,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)) { @@ -22875,7 +22951,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)) { @@ -22884,20 +22959,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); } @@ -23223,29 +23304,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); @@ -23649,13 +23733,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]); @@ -23663,8 +23748,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]; @@ -23673,9 +23758,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 { @@ -23685,14 +23770,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) { @@ -23701,9 +23786,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 { @@ -23712,14 +23797,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; } @@ -23730,22 +23816,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--) { @@ -23755,17 +23842,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; } } @@ -23777,22 +23864,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; } @@ -23846,8 +23934,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) { @@ -23855,10 +23945,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); } } @@ -23867,10 +23957,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); } } @@ -23881,10 +23971,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); } } @@ -23893,16 +23983,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; } @@ -24005,6 +24096,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; @@ -24037,10 +24133,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 */ @@ -24059,6 +24161,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; } @@ -24078,11 +24181,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++) @@ -24108,7 +24217,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]]; } @@ -24120,6 +24229,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; } @@ -25529,34 +25640,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 */ @@ -25564,19 +25683,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); @@ -25585,6 +25712,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; } @@ -33135,9 +33264,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; @@ -33158,12 +33298,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; @@ -33555,13 +33696,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]); @@ -33569,7 +33712,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; @@ -33578,19 +33721,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); @@ -33605,17 +33750,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; @@ -33639,10 +33785,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; } @@ -33798,6 +33945,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 (;;) { @@ -35558,8 +35706,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; @@ -35569,20 +35727,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; @@ -36993,6 +37153,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 (;;) {