more test fixing

This commit is contained in:
2026-02-07 07:59:52 -06:00
parent 9ffe60ebef
commit 73cd6a255d

View File

@@ -563,6 +563,11 @@ typedef enum MachOpcode {
MACH_PUSH, /* push R(B) onto array R(A) */
MACH_POP, /* R(A) = pop last element from array R(B) */
MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */
MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */
MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */
MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */
MACH_NOP,
MACH_OP_COUNT
@@ -620,6 +625,10 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_THROW] = "throw",
[MACH_PUSH] = "push",
[MACH_POP] = "pop",
[MACH_DELETE] = "delete",
[MACH_DELETEINDEX] = "deleteindex",
[MACH_HASPROP] = "hasprop",
[MACH_REGEXP] = "regexp",
[MACH_NOP] = "nop",
};
@@ -2646,6 +2655,22 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8
rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, from_base, from_end, to_base, to_free, to_end);
}
}
/* Forward internal GC pointers for regex objects */
if (REC_GET_CLASS_ID(rec) == JS_CLASS_REGEXP) {
JSRegExp *re = (JSRegExp *)REC_GET_OPAQUE(rec);
if (re) {
if (re->pattern) {
JSValue pv = JS_MKPTR(re->pattern);
pv = gc_copy_value(ctx, pv, from_base, from_end, to_base, to_free, to_end);
re->pattern = (JSText *)JS_VALUE_GET_PTR(pv);
}
if (re->bytecode) {
JSValue bv = JS_MKPTR(re->bytecode);
bv = gc_copy_value(ctx, bv, from_base, from_end, to_base, to_free, to_end);
re->bytecode = (JSText *)JS_VALUE_GET_PTR(bv);
}
}
}
break;
}
case OBJ_FUNCTION: {
@@ -6549,6 +6574,7 @@ static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue thi
JSValue ret_val;
JSValue *arg_buf;
int arg_count, i;
int saved_vs_top = -1; /* for value stack padding cleanup */
JSCFunctionEnum cproto;
f = JS_VALUE_GET_FUNCTION (func_obj);
@@ -6571,12 +6597,14 @@ static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue thi
arg_buf = argv;
if (unlikely (argc < arg_count)) {
/* ensure that at least argc_count arguments are readable */
arg_buf = alloca (sizeof (arg_buf[0]) * arg_count);
/* Pad args on the value stack (GC-scanned) instead of alloca */
saved_vs_top = ctx->value_stack_top;
for (i = 0; i < argc; i++)
arg_buf[i] = argv[i];
ctx->value_stack[saved_vs_top + i] = argv[i];
for (i = argc; i < arg_count; i++)
arg_buf[i] = JS_NULL;
ctx->value_stack[saved_vs_top + i] = JS_NULL;
ctx->value_stack_top = saved_vs_top + arg_count;
arg_buf = &ctx->value_stack[saved_vs_top];
sf->arg_count = arg_count;
}
sf->arg_buf = (JSValue *)arg_buf;
@@ -6660,6 +6688,10 @@ static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue thi
rt->current_stack_frame = sf->prev_frame;
/* Restore value stack if we used it for arg padding */
if (saved_vs_top >= 0)
ctx->value_stack_top = saved_vs_top;
if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
ctx->trace_hook (ctx, JS_HOOK_RET, NULL, ctx->trace_data);
@@ -6731,6 +6763,9 @@ static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue
return js_call_c_function (caller_ctx, func_obj, this_obj, argc, (JSValue *)argv);
case JS_FUNC_KIND_BYTECODE:
break; /* continue to bytecode execution below */
case JS_FUNC_KIND_REGISTER:
return JS_CallRegisterVM(caller_ctx, f->u.reg.code, this_obj, argc, (JSValue *)argv,
f->u.reg.env_record, f->u.reg.outer_frame);
default:
goto not_a_function;
}
@@ -19757,9 +19792,16 @@ static int js_is_regexp (JSContext *ctx, JSValue obj);
/* RegExp */
static void js_regexp_finalizer (JSRuntime *rt, JSValue val) {
/* With copying GC, memory is reclaimed automatically */
/* JSRegExp is allocated with js_mallocz_rt, not on GC heap */
if (JS_IsRecord(val)) {
JSRecord *p = (JSRecord *)JS_VALUE_GET_OBJ(val);
JSRegExp *re = (JSRegExp *)REC_GET_OPAQUE(p);
if (re) {
js_free_rt(re);
REC_SET_OPAQUE(p, NULL);
}
}
(void)rt;
(void)val;
}
/* create a string containing the RegExp bytecode */
@@ -19852,13 +19894,35 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern,
return JS_EXCEPTION;
}
/* Root pattern and bc so GC can update them during allocations */
JSGCRef pat_ref, bc_ref;
JS_AddGCRef(ctx, &pat_ref);
JS_AddGCRef(ctx, &bc_ref);
pat_ref.val = pattern;
bc_ref.val = bc;
obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP);
if (JS_IsException (obj)) goto fail;
if (JS_IsException (obj)) {
JS_DeleteGCRef(ctx, &bc_ref);
JS_DeleteGCRef(ctx, &pat_ref);
goto fail;
}
p = JS_VALUE_GET_OBJ (obj);
/* Allocate JSRegExp and store in opaque slot */
re = js_malloc (ctx, sizeof(JSRegExp));
if (!re) goto fail;
/* Allocate JSRegExp with runtime allocator (not GC heap) so it survives GC.
The pattern/bytecode JSText pointers inside are forwarded by the GC's
OBJ_RECORD handler for JS_CLASS_REGEXP. */
re = js_mallocz_rt (sizeof(JSRegExp));
if (!re) {
JS_DeleteGCRef(ctx, &bc_ref);
JS_DeleteGCRef(ctx, &pat_ref);
goto fail;
}
REC_SET_OPAQUE(p, re);
/* Re-read pattern and bc from GC roots — they may have been moved */
pattern = pat_ref.val;
bc = bc_ref.val;
JS_DeleteGCRef(ctx, &bc_ref);
JS_DeleteGCRef(ctx, &pat_ref);
/* Store pattern and bytecode - need to handle both immediate and heap strings */
re->pattern = MIST_IsImmediateASCII (pattern) ? NULL : (JSText *)JS_VALUE_GET_PTR (pattern);
re->bytecode = MIST_IsImmediateASCII (bc) ? NULL : (JSText *)JS_VALUE_GET_PTR (bc);
@@ -20025,11 +20089,14 @@ void *lre_realloc (void *opaque, void *ptr, size_t size) {
/* Convert UTF-32 JSText to UTF-16 buffer for regex engine.
Returns allocated uint16_t buffer that must be freed by caller.
Sets *out_len to number of uint16 code units. */
static uint16_t *js_string_to_utf16 (JSContext *ctx, JSText *str, int *out_len) {
static uint16_t *js_string_to_utf16 (JSContext *ctx, JSValue str_val, int *out_len) {
JSText *str = JS_VALUE_GET_STRING (str_val);
int len = (int)JSText_len (str);
/* Worst case: each UTF-32 char becomes 2 UTF-16 surrogates */
uint16_t *buf = js_malloc (ctx, len * 2 * sizeof (uint16_t));
if (!buf) return NULL;
/* Re-read str after js_malloc (GC may have moved it) */
str = JS_VALUE_GET_STRING (str_val);
int j = 0;
for (int i = 0; i < len; i++) {
@@ -20061,8 +20128,16 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
if (!re) return JS_EXCEPTION;
/* GC-safety: root this_val and str_val since GC may move them */
JSGCRef this_ref, str_ref;
JS_PushGCRef(ctx, &this_ref);
this_ref.val = this_val;
str_val = JS_ToString (ctx, argv[0]);
if (JS_IsException (str_val)) return JS_EXCEPTION;
if (JS_IsException (str_val)) { JS_PopGCRef(ctx, &this_ref); return JS_EXCEPTION; }
JS_PushGCRef(ctx, &str_ref);
str_ref.val = str_val;
ret = JS_EXCEPTION;
res = JS_NULL;
@@ -20071,7 +20146,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
match0 = JS_NULL;
capture = NULL;
val = JS_GetPropertyStr (ctx, this_val, "lastIndex");
val = JS_GetPropertyStr (ctx, this_ref.val, "lastIndex");
if (JS_IsException (val) || JS_ToLength (ctx, &last_index, val))
goto fail;
@@ -20079,17 +20154,23 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
re_flags = lre_get_flags (re_bytecode);
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) last_index = 0;
str = JS_VALUE_GET_STRING (str_val);
str = JS_VALUE_GET_STRING (str_ref.val);
capture_count = lre_get_capture_count (re_bytecode);
if (capture_count > 0) {
capture = js_malloc (ctx, sizeof (capture[0]) * capture_count * 2);
if (!capture) goto fail;
/* GC may have run — re-read bytecode and str */
re_bytecode = (uint8_t *)re->bytecode->packed;
str = JS_VALUE_GET_STRING (str_ref.val);
}
/* Convert UTF-32 string to UTF-16 for regex engine */
utf16_buf = js_string_to_utf16 (ctx, str, &utf16_len);
utf16_buf = js_string_to_utf16 (ctx, str_ref.val, &utf16_len);
if (!utf16_buf) goto fail;
/* GC may have run inside js_string_to_utf16 — re-read */
re_bytecode = (uint8_t *)re->bytecode->packed;
str = JS_VALUE_GET_STRING (str_ref.val);
shift = 1; /* UTF-16 mode */
str_buf = (uint8_t *)utf16_buf;
@@ -20102,7 +20183,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
if (rc != 1) {
if (rc >= 0) {
if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) {
if (JS_SetPropertyStr (ctx, this_val, "lastIndex", JS_NewInt32 (ctx, 0))
if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, 0))
< 0)
goto fail;
}
@@ -20117,7 +20198,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
}
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
if (JS_SetPropertyStr (ctx, this_val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift))
if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift))
< 0)
goto fail;
}
@@ -20160,6 +20241,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
s = JS_NULL;
if (start != -1) {
str = JS_VALUE_GET_STRING(str_ref.val); /* re-read after GC points */
s = js_sub_string (ctx, str, start, end);
if (JS_IsException (s)) goto fail;
}
@@ -20210,11 +20292,15 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal
done:
js_free (ctx, capture);
js_free (ctx, utf16_buf);
JS_PopGCRef(ctx, &str_ref);
JS_PopGCRef(ctx, &this_ref);
return ret;
fail:
js_free (ctx, capture);
js_free (ctx, utf16_buf);
JS_PopGCRef(ctx, &str_ref);
JS_PopGCRef(ctx, &this_ref);
return JS_EXCEPTION;
}
@@ -21744,7 +21830,13 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
/* Handle array */
if (JS_IsArray (arg)) {
int64_t len;
if (js_get_length64 (ctx, &len, arg)) return JS_EXCEPTION;
JSGCRef arg_ref;
JS_AddGCRef(ctx, &arg_ref);
arg_ref.val = arg;
if (js_get_length64 (ctx, &len, arg_ref.val)) {
JS_DeleteGCRef(ctx, &arg_ref);
return JS_EXCEPTION;
}
const char *separator = "";
BOOL sep_alloc = FALSE;
@@ -21767,11 +21859,12 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
if (!b) goto array_fail;
}
JSValue item = JS_GetPropertyInt64 (ctx, arg, i);
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);
return JS_ThrowTypeError (ctx, "text: array element is not a string");
}
@@ -21783,10 +21876,12 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
}
if (sep_alloc) JS_FreeCString (ctx, separator);
JS_DeleteGCRef(ctx, &arg_ref);
return pretext_end (ctx, b);
array_fail:
if (sep_alloc) JS_FreeCString (ctx, separator);
JS_DeleteGCRef(ctx, &arg_ref);
return JS_EXCEPTION;
}
@@ -29411,10 +29506,12 @@ static cJSON *ast_parse_arrow_function (ASTParseState *s) {
cJSON_AddItemToObject (node, "statements", stmts);
if (s->token_val == '}') ast_next_token (s);
} else {
/* Expression body - wrap in implicit return */
/* Expression body - wrap in implicit return.
Use assign_expr (not full expr) so commas after the body
are NOT consumed matches JS spec (AssignmentExpression). */
cJSON *stmts = cJSON_CreateArray ();
cJSON *ret = ast_node (s, "return", s->token_ptr);
cJSON *expr = ast_parse_expr (s);
cJSON *expr = ast_parse_assign_expr (s);
cJSON_AddItemToObject (ret, "expression", expr);
ast_node_end (s, ret, s->buf_ptr);
cJSON_AddItemToArray (stmts, ret);
@@ -30568,6 +30665,7 @@ typedef struct MachVarInfo {
int slot;
int is_const; /* 1 for def, function args; 0 for var */
int is_closure; /* 1 if captured by a nested function */
int scope_depth; /* block scope nesting level */
} MachVarInfo;
/* ---- Compile-time constant pool entry ---- */
@@ -30784,6 +30882,7 @@ static void mach_add_var(MachCompState *cs, const char *name, int slot, int is_c
v->slot = slot;
v->is_const = is_const;
v->is_closure = 0;
v->scope_depth = cs->scope_depth;
}
/* Find a variable in the current scope */
@@ -31206,28 +31305,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) {
int save = cs->freereg;
int lr = mach_compile_expr(cs, left, -1);
int rr = mach_compile_expr(cs, right, -1);
/* Use MACH_ABC with a new opcode or use GETINDEX + check null.
For now, emit a HAS instruction (MACH_GETINDEX) and check if result is non-null.
Actually, we need a proper HAS check. Let's use a call to a runtime helper. */
/* We'll emit GETFIELD/GETINDEX then JMPNULL to check existence.
But "in" checks presence, not value. For string keys, use GETFIELD and check non-null.
This is an approximation that works for most cases. */
mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, rr, lr));
/* Check if result is null — if null, property doesn't exist → false
But this is wrong because a property CAN be set to null.
We need a proper HASPROP opcode. For now let's use JMPNULL. */
int jmpnull_pc = mach_current_pc(cs);
mach_emit(cs, MACH_AsBx(MACH_JMPNULL, dest, 0));
mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0));
int jmpend_pc = mach_current_pc(cs);
mach_emit(cs, MACH_sJ(MACH_JMP, 0));
/* Patch jmpnull to false */
int offset = mach_current_pc(cs) - (jmpnull_pc + 1);
cs->code[jmpnull_pc] = MACH_AsBx(MACH_JMPNULL, dest, (int16_t)offset);
mach_emit(cs, MACH_ABx(MACH_LOADFALSE, dest, 0));
/* Patch jmpend */
offset = mach_current_pc(cs) - (jmpend_pc + 1);
cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset);
mach_emit(cs, MACH_ABC(MACH_HASPROP, dest, rr, lr));
mach_free_reg_to(cs, save);
return dest;
}
@@ -31555,6 +31633,63 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) {
return dest;
}
/* Delete operator */
if (strcmp(kind, "delete") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
cJSON *operand = cJSON_GetObjectItem(node, "expression");
if (!operand) operand = cJSON_GetObjectItem(node, "right");
if (operand) {
const char *okind = cJSON_GetStringValue(cJSON_GetObjectItem(operand, "kind"));
if (okind && strcmp(okind, ".") == 0) {
/* delete obj.prop */
cJSON *obj_node = cJSON_GetObjectItem(operand, "left");
cJSON *prop_node = cJSON_GetObjectItem(operand, "right");
const char *pname = cJSON_GetStringValue(cJSON_GetObjectItem(prop_node, "name"));
if (!pname) pname = cJSON_GetStringValue(prop_node);
int save = cs->freereg;
int objr = mach_compile_expr(cs, obj_node, -1);
int ki = mach_cpool_add_str(cs, pname);
mach_emit(cs, MACH_ABC(MACH_DELETE, dest, objr, ki));
mach_free_reg_to(cs, save);
return dest;
} else if (okind && strcmp(okind, "[") == 0) {
/* delete obj[expr] */
cJSON *obj_node = cJSON_GetObjectItem(operand, "left");
cJSON *idx_node = cJSON_GetObjectItem(operand, "right");
int save = cs->freereg;
int objr = mach_compile_expr(cs, obj_node, -1);
int ir = mach_compile_expr(cs, idx_node, -1);
mach_emit(cs, MACH_ABC(MACH_DELETEINDEX, dest, objr, ir));
mach_free_reg_to(cs, save);
return dest;
}
}
mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0));
return dest;
}
/* This reference — slot 0 is always 'this' */
if (strcmp(kind, "this") == 0) {
if (dest >= 0 && dest != 0) {
mach_emit(cs, MACH_ABC(MACH_MOVE, dest, 0, 0));
return dest;
}
return 0;
}
/* Regex literal */
if (strcmp(kind, "regexp") == 0) {
if (dest < 0) dest = mach_reserve_reg(cs);
const char *pattern = cJSON_GetStringValue(cJSON_GetObjectItem(node, "pattern"));
const char *flags = cJSON_GetStringValue(cJSON_GetObjectItem(node, "flags"));
if (!pattern) pattern = "";
if (!flags) flags = "";
int pi = mach_cpool_add_str(cs, pattern);
int fi = mach_cpool_add_str(cs, flags);
mach_emit(cs, MACH_ABC(MACH_REGEXP, dest, pi, fi));
return dest;
}
/* Fallback: unsupported expression kind — load null */
if (dest < 0) dest = mach_reserve_reg(cs);
mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0));
@@ -31575,7 +31710,17 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
cJSON *right = cJSON_GetObjectItem(stmt, "right");
const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "name"));
if (!name) return;
int slot = mach_find_var(cs, name);
/* Check if var exists at current scope depth — if so, reuse it.
If it exists at a shallower depth, shadow it with a new slot. */
int slot = -1;
for (int i = cs->var_count - 1; i >= 0; i--) {
if (strcmp(cs->vars[i].name, name) == 0) {
if (cs->vars[i].scope_depth == cs->scope_depth) {
slot = cs->vars[i].slot; /* same scope — reuse */
}
break;
}
}
if (slot < 0) {
slot = mach_reserve_reg(cs);
mach_add_var(cs, name, slot, strcmp(kind, "def") == 0);
@@ -31650,6 +31795,8 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* Block */
if (strcmp(kind, "block") == 0) {
int saved_var_count = cs->var_count;
cs->scope_depth++;
cJSON *stmts = cJSON_GetObjectItem(stmt, "statements");
if (stmts && cJSON_IsArray(stmts)) {
int count = cJSON_GetArraySize(stmts);
@@ -31657,6 +31804,10 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i));
}
}
cs->scope_depth--;
for (int i = saved_var_count; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_var_count;
return;
}
@@ -31676,6 +31827,8 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* Compile then branch — "then" is a direct array of statements */
if (then_body) {
int saved_vc = cs->var_count;
cs->scope_depth++;
if (cJSON_IsArray(then_body)) {
int count = cJSON_GetArraySize(then_body);
for (int i = 0; i < count; i++)
@@ -31690,6 +31843,10 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
mach_compile_stmt(cs, then_body);
}
}
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
}
/* Check for else-if chain ("list") or plain else */
@@ -31708,6 +31865,8 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset);
/* Compile else — could be a direct array, object, or else-if stmt */
int saved_vc = cs->var_count;
cs->scope_depth++;
if (cJSON_IsArray(else_body)) {
int count = cJSON_GetArraySize(else_body);
for (int i = 0; i < count; i++)
@@ -31722,6 +31881,10 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
mach_compile_stmt(cs, else_body);
}
}
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
/* Patch jmpend */
offset = mach_current_pc(cs) - (jmpend_pc + 1);
@@ -31754,6 +31917,8 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* Compile body — "statements" on a child "block"/"body", or directly on the node */
{
int saved_vc = cs->var_count;
cs->scope_depth++;
cJSON *body = cJSON_GetObjectItem(stmt, "block");
if (!body) body = cJSON_GetObjectItem(stmt, "body");
cJSON *stmts = body ? cJSON_GetObjectItem(body, "statements") : NULL;
@@ -31765,6 +31930,10 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
} else if (body) {
mach_compile_stmt(cs, body);
}
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
}
/* Patch continue chain to loop_top */
@@ -31801,6 +31970,8 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* For loop */
if (strcmp(kind, "for") == 0) {
int saved_vc = cs->var_count;
cs->scope_depth++;
cJSON *init = cJSON_GetObjectItem(stmt, "init");
cJSON *cond = cJSON_GetObjectItem(stmt, "test");
cJSON *update = cJSON_GetObjectItem(stmt, "update");
@@ -31829,6 +32000,8 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
/* Body — "statements" on a child "block"/"body", or directly on the for node */
{
int body_vc = cs->var_count;
cs->scope_depth++;
cJSON *stmts = body ? cJSON_GetObjectItem(body, "statements") : NULL;
if (!stmts) stmts = cJSON_GetObjectItem(stmt, "statements");
if (stmts && cJSON_IsArray(stmts)) {
@@ -31838,6 +32011,10 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
} else if (body) {
mach_compile_stmt(cs, body);
}
cs->scope_depth--;
for (int i = body_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = body_vc;
}
/* Patch continue chain to update (or loop_top if no update) */
@@ -31879,6 +32056,10 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) {
}
cs->loop_break = old_break;
cs->loop_continue = old_continue;
cs->scope_depth--;
for (int i = saved_vc; i < cs->var_count; i++)
sys_free(cs->vars[i].name);
cs->var_count = saved_vc;
return;
}
@@ -32228,6 +32409,11 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
/* Comparison ops allow mixed types — return false for mismatches */
if (op >= MACH_EQ && op <= MACH_GE) {
/* Fast path: bitwise-identical values (same object/pointer) */
if (a == b) {
if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) return JS_TRUE;
if (op == MACH_NEQ) return JS_FALSE;
}
if (JS_IsNumber(a) && JS_IsNumber(b)) {
double da, db;
JS_ToFloat64(ctx, &da, a);
@@ -32575,8 +32761,18 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
case MACH_GETFIELD: {
JSValue obj = frame->slots[b];
JSValue key = code->cpool[c];
/* Non-proxy functions (arity != 2) can't have properties read */
if (JS_IsFunction(obj)) {
JSFunction *fn_chk = JS_VALUE_GET_FUNCTION(obj);
if (fn_chk->length != 2) {
JS_ThrowTypeError(ctx, "cannot read property of non-proxy function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
}
JSValue val = JS_GetProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
break;
}
@@ -32586,8 +32782,9 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue obj = frame->slots[a];
JSValue key = code->cpool[b];
JSValue val = frame->slots[c];
JS_SetProperty(ctx, obj, key, val);
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
break;
}
@@ -32600,6 +32797,7 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
else
val = JS_GetProperty(ctx, obj, idx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[a] = val;
break;
}
@@ -32609,11 +32807,22 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue obj = frame->slots[a];
JSValue idx = frame->slots[b];
JSValue val = frame->slots[c];
if (JS_IsInt(idx))
JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
else
JS_SetProperty(ctx, obj, idx, val);
int ret;
if (JS_IsInt(idx)) {
ret = JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
} else if (JS_IsText(idx) && JS_IsRecord(obj)) {
ret = JS_SetProperty(ctx, obj, idx, val);
} else if (JS_IsArray(obj)) {
JS_ThrowTypeError(ctx, "array index must be a number");
ret = -1;
} else if (JS_IsRecord(obj)) {
JS_ThrowTypeError(ctx, "object key must be a string");
ret = -1;
} else {
ret = JS_SetProperty(ctx, obj, idx, val);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
break;
}
@@ -32873,6 +33082,46 @@ static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
break;
}
case MACH_DELETE: {
JSValue obj = frame->slots[b];
JSValue key = code->cpool[c];
int ret = JS_DeleteProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
frame->slots[a] = JS_NewBool(ctx, ret >= 0);
break;
}
case MACH_DELETEINDEX: {
JSValue obj = frame->slots[b];
JSValue key = frame->slots[c];
int ret = JS_DeleteProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
frame->slots[a] = JS_NewBool(ctx, ret >= 0);
break;
}
case MACH_HASPROP: {
JSValue obj = frame->slots[b];
JSValue key = frame->slots[c];
int has = JS_HasProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = JS_NewBool(ctx, has > 0);
break;
}
case MACH_REGEXP: {
JSValue argv[2];
argv[0] = code->cpool[b]; /* pattern */
argv[1] = code->cpool[c]; /* flags */
JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(re)) goto disrupt;
frame->slots[a] = re;
break;
}
case MACH_THROW:
goto disrupt;