From 19576533d9a40bb66154f4b5fe3be5dbf50ed87d Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 3 Feb 2026 22:55:22 -0600 Subject: [PATCH] no more global obj; eval w/ env --- source/quickjs-opcode.h | 1 - source/quickjs.c | 604 +++++++++++++++++----------------------- source/quickjs.h | 10 + 3 files changed, 264 insertions(+), 351 deletions(-) diff --git a/source/quickjs-opcode.h b/source/quickjs-opcode.h index d6953e70..829b4d4a 100644 --- a/source/quickjs-opcode.h +++ b/source/quickjs-opcode.h @@ -102,7 +102,6 @@ DEF( return, 1, 1, 0, none) DEF( return_undef, 1, 0, 0, none) DEF( throw, 1, 1, 0, none) DEF( throw_error, 6, 0, 0, key_u8) -DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a bytecode string */ diff --git a/source/quickjs.c b/source/quickjs.c index 9b40aac9..e2f3ffc1 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -825,8 +825,8 @@ struct JSContext { JSValue throw_type_error; JSValue eval_obj; - JSValue global_obj; /* global object */ - JSValue global_var_obj; /* contains the global let/const definitions */ + JSValue global_obj; /* global object (immutable intrinsics) */ + JSValue eval_env; /* environment record for eval (stone record) */ uint64_t random_state; @@ -1030,7 +1030,9 @@ static int st_text_resize (JSContext *ctx) { return 0; } -/* Realloc with slack reporting (for bump allocator) */ +/* Realloc with slack reporting (for bump allocator) + WARNING: This function is NOT GC-safe! The caller must protect the source + object with a GC ref before calling, and re-chase the pointer after. */ void *js_realloc (JSContext *ctx, void *ptr, size_t size) { void *new_ptr; @@ -1044,13 +1046,9 @@ void *js_realloc (JSContext *ctx, void *ptr, size_t size) { return new_ptr; } - /* Bump allocator: allocate new space and copy. - For simplicity, we allocate new space and copy. */ + /* Bump allocator: just allocate new space. + Caller is responsible for protecting ptr and copying data. */ new_ptr = js_malloc (ctx, size); - if (!new_ptr) return NULL; - - /* Copy old data (caller ensures safety) */ - memcpy (new_ptr, ptr, size); return new_ptr; } @@ -1831,6 +1829,7 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); @@ -2004,10 +2003,32 @@ static inline int pjs_resize_array (void **parray, int elem_size, int *psize, in static no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { int new_size; void *new_array; + void *old_array = *parray; + int old_size = *psize; + /* XXX: potential arithmetic overflow */ - new_size = max_int (req_size, *psize * 3 / 2); - new_array = js_realloc (ctx, *parray, new_size * elem_size); - if (!new_array) return -1; + new_size = max_int (req_size, old_size * 3 / 2); + + /* Protect source object with a GC ref before allocating (GC may move it) */ + JSGCRef src_ref; + JS_PushGCRef (ctx, &src_ref); + if (old_array) { + src_ref.val = JS_MKPTR (old_array); + } + + new_array = js_malloc (ctx, new_size * elem_size); + if (!new_array) { + JS_PopGCRef (ctx, &src_ref); + return -1; + } + + /* Get possibly-moved source pointer after GC */ + if (old_array) { + old_array = (void *)chase (src_ref.val); + memcpy (new_array, old_array, old_size * elem_size); + } + JS_PopGCRef (ctx, &src_ref); + *psize = new_size; *parray = new_array; return 0; @@ -2597,9 +2618,9 @@ static int ctx_gc (JSContext *ctx, int allow_grow) { printf(" after copy: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); #endif #ifdef DUMP_GC_DETAIL - printf(" roots: global_var_obj\n"); fflush(stdout); + printf(" roots: eval_env\n"); fflush(stdout); #endif - ctx->global_var_obj = gc_copy_value (ctx, ctx->global_var_obj, from_base, from_end, to_base, &to_free, to_end); + ctx->eval_env = gc_copy_value (ctx, ctx->eval_env, from_base, from_end, to_base, &to_free, to_end); #ifdef DUMP_GC_DETAIL printf(" roots: regexp_ctor\n"); fflush(stdout); #endif @@ -3015,7 +3036,7 @@ static void JS_MarkContext (JSRuntime *rt, JSContext *ctx, JS_MarkFunc *mark_fun int i; JS_MarkValue (rt, ctx->global_obj, mark_func); - JS_MarkValue (rt, ctx->global_var_obj, mark_func); + JS_MarkValue (rt, ctx->eval_env, mark_func); JS_MarkValue (rt, ctx->throw_type_error, mark_func); JS_MarkValue (rt, ctx->eval_obj, mark_func); @@ -3287,14 +3308,32 @@ static no_inline JSText *pretext_realloc (JSContext *ctx, JSText *s, int new_len return NULL; } int old_cap = (int)objhdr_cap56 (s->hdr); - int new_cap = min_int (max_int (new_len, old_cap * 3 / 2), JS_STRING_LEN_MAX); + int old_len = (int)s->length; + /* Grow by 50%, ensuring we have at least new_len capacity */ + int new_cap = max_int (new_len, old_cap * 3 / 2); + + /* Protect source object with a GC ref before allocating (GC may move it) */ + JSGCRef src_ref; + JS_PushGCRef (ctx, &src_ref); + src_ref.val = JS_MKPTR (s); + + /* Allocate new string - this may trigger GC */ + JSText *new_str = js_alloc_string (ctx, new_cap); + if (!new_str) { + JS_PopGCRef (ctx, &src_ref); + return NULL; + } + + /* Get possibly-moved source pointer after GC */ + s = (JSText *)chase (src_ref.val); + JS_PopGCRef (ctx, &src_ref); + + /* Copy data from old string to new */ + new_str->length = old_len; + for (int i = 0; i < old_len; i++) { + string_put (new_str, i, string_get (s, i)); + } - /* JSText stores UTF-32 packed into uint64_t (2 chars per word). */ - size_t new_size_bytes = sizeof (JSText) + ((new_cap + 1) / 2) * sizeof (uint64_t); - JSText *new_str = js_realloc (ctx, s, new_size_bytes); - if (!new_str) return NULL; - new_cap = min_int (new_cap, JS_STRING_LEN_MAX); - new_str->hdr = objhdr_set_cap56 (new_str->hdr, new_cap); return new_str; } @@ -3727,10 +3766,26 @@ static JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) { } if (JS_IsNull (ret_val)) { + /* Protect op1 and op2 from GC during allocation */ + JSGCRef op1_ref, op2_ref; + JS_PushGCRef (ctx, &op1_ref); + op1_ref.val = op1; + JS_PushGCRef (ctx, &op2_ref); + op2_ref.val = op2; + JSText *p = js_alloc_string (ctx, new_len); if (!p) { + JS_PopGCRef (ctx, &op2_ref); + JS_PopGCRef (ctx, &op1_ref); return JS_EXCEPTION; } + + /* Get possibly-moved values after GC */ + op1 = op1_ref.val; + op2 = op2_ref.val; + JS_PopGCRef (ctx, &op2_ref); + JS_PopGCRef (ctx, &op1_ref); + /* Copy characters using string_put/get */ for (int i = 0; i < len1; i++) { string_put (p, i, js_string_value_get (op1, i)); @@ -3738,6 +3793,7 @@ static JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) { for (int i = 0; i < len2; i++) { string_put (p, len1 + i, js_string_value_get (op2, i)); } + p->length = new_len; ret_val = JS_MKPTR (p); } @@ -7216,29 +7272,6 @@ restart: } goto exception; - CASE (OP_eval) : { - JSValue obj; - int scope_idx; - call_argc = get_u16 (pc); - scope_idx = get_u16 (pc + 2) + ARG_SCOPE_END; - pc += 4; - call_argv = sp - call_argc; - sf->cur_pc = pc; - if (js_strict_eq (ctx, call_argv[-1], ctx->eval_obj)) { - if (call_argc >= 1) - obj = call_argv[0]; - else - obj = JS_NULL; - ret_val = JS_EvalObject (ctx, JS_NULL, obj, JS_EVAL_TYPE_DIRECT, scope_idx); - } else { - ret_val = JS_CallInternal (ctx, call_argv[-1], JS_NULL, call_argc, call_argv, 0); - } - if (unlikely (JS_IsException (ret_val))) goto exception; - for (i = -1; i < call_argc; i++) - sp -= call_argc + 1; - *sp++ = ret_val; - } - BREAK; CASE (OP_regexp) : { sp[-2] = js_regexp_constructor_internal (ctx, sp[-2], sp[-1]); sp--; @@ -8468,9 +8501,8 @@ restart: CASE (OP_get_env_slot) : { int slot = get_u16 (pc); pc += 2; - (void)slot; - JS_ThrowInternalError (ctx, "OP_get_env_slot not yet implemented"); - goto exception; + JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (ctx->eval_env); + *sp++ = env->slots[slot].val; } BREAK; @@ -8764,7 +8796,6 @@ typedef struct JSFunctionDef { BOOL has_simple_parameter_list; BOOL has_parameter_expressions; /* if true, an argument scope is created */ BOOL has_use_strict; /* to reject directive in special cases */ - BOOL has_eval_call; /* true if the function contains a call to eval() */ BOOL has_this_binding; /* true if the 'this' binding is available in the function */ BOOL in_function_body; @@ -8951,7 +8982,8 @@ static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { Returns new bytecode on success, NULL on link error. The linked bytecode is a separate allocation that can be modified. */ static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, - JSFunctionBytecode *tpl) { + JSFunctionBytecode *tpl, + JSValue env) { /* Calculate total size of bytecode allocation */ int function_size; int cpool_offset, vardefs_offset, closure_var_offset, byte_code_offset; @@ -9014,30 +9046,38 @@ static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, /* Walk bytecode and patch global variable access opcodes */ uint8_t *bc = linked->byte_code_buf; int pos = 0; + + /* Get env record if provided */ + JSRecord *env_rec = NULL; + if (!JS_IsNull (env) && JS_IsRecord (env)) { + env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); + } + while (pos < linked->byte_code_len) { uint8_t op = bc[pos]; int len = short_opcode_info (op).size; - /* Patch OP_get_var -> OP_get_global_slot */ + /* Patch OP_get_var -> OP_get_global_slot or OP_get_env_slot */ if (op == OP_get_var || op == OP_get_var_undef) { uint32_t cpool_idx = get_u32 (bc + pos + 1); JSValue name = linked->cpool[cpool_idx]; - /* Try global_obj first (for built-ins like 'print') */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pos] = OP_get_global_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; + /* Try env first (if provided) */ + if (env_rec) { + int slot = rec_find_slot (env_rec, name); + if (slot > 0) { + bc[pos] = OP_get_env_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } } - /* Try global_var_obj (let/const declarations) */ - JSRecord *global_var = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); - slot = rec_find_slot (global_var, name); + /* Try global_obj (intrinsics like 'print') */ + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + int slot = rec_find_slot (global, name); if (slot > 0) { bc[pos] = OP_get_global_slot; put_u16 (bc + pos + 1, (uint16_t)slot); @@ -9058,44 +9098,17 @@ static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, /* OP_get_var_undef is ok - leaves as is for runtime check */ } - /* Patch OP_put_var family -> OP_set_global_slot */ + /* Patch OP_put_var family -> error (global is immutable) */ if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { uint32_t cpool_idx = get_u32 (bc + pos + 1); JSValue name = linked->cpool[cpool_idx]; - /* Try global_obj first */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pos] = OP_set_global_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - - /* Try global_var_obj */ - JSRecord *global_var = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); - slot = rec_find_slot (global_var, name); - if (slot > 0) { - bc[pos] = OP_set_global_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - - /* For put_var_strict, error if not found */ - if (op == OP_put_var_strict) { - char buf[64]; - JS_ThrowReferenceError (ctx, "'%s' is not defined", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - pjs_free (linked); - return NULL; - } - /* OP_put_var and OP_put_var_init - leave for runtime (implicit global) */ + /* Global object is immutable - can't write to intrinsics */ + char buf[64]; + JS_ThrowReferenceError (ctx, "cannot assign to '%s' - global object is immutable", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + pjs_free (linked); + return NULL; } /* Patch OP_check_var -> OP_nop (if variable exists) */ @@ -9103,10 +9116,18 @@ static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, uint32_t cpool_idx = get_u32 (bc + pos + 1); JSValue name = linked->cpool[cpool_idx]; - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - JSRecord *global_var = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); + BOOL found = FALSE; + if (env_rec && rec_find_slot (env_rec, name) > 0) { + found = TRUE; + } + if (!found) { + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + if (rec_find_slot (global, name) > 0) { + found = TRUE; + } + } - if (rec_find_slot (global, name) > 0 || rec_find_slot (global_var, name) > 0) { + if (found) { /* Variable exists, replace with NOPs */ bc[pos] = OP_nop; bc[pos + 1] = OP_nop; @@ -9114,7 +9135,7 @@ static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, bc[pos + 3] = OP_nop; bc[pos + 4] = OP_nop; } - /* else leave check_var for runtime - might be implicit global */ + /* else leave check_var for runtime */ } pos += len; @@ -12412,17 +12433,11 @@ static __exception int js_parse_postfix_expr (JSParseState *s, uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); name = fd->cpool[idx]; scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5); - if (js_key_equal_str (name, "eval") && call_type == FUNC_CALL_NORMAL - && !has_optional_chain) { - /* direct 'eval' */ - opcode = OP_eval; - } else { - /* verify if function name resolves to a simple - get_loc/get_arg: a function call inside a `with` - statement can resolve to a method call of the - `with` context object - */ - } + /* verify if function name resolves to a simple + get_loc/get_arg: a function call inside a `with` + statement can resolve to a method call of the + `with` context object + */ drop_count = 1; } break; default: @@ -12463,12 +12478,6 @@ static __exception int js_parse_postfix_expr (JSParseState *s, emit_op (s, OP_call_method); emit_u16 (s, arg_count); break; - case OP_eval: - emit_op (s, OP_eval); - emit_u16 (s, arg_count); - emit_u16 (s, fd->scope_level); - fd->has_eval_call = TRUE; - break; default: emit_op (s, OP_call); emit_u16 (s, arg_count); @@ -14801,187 +14810,6 @@ done: return pos_next; } -static void mark_eval_captured_variables (JSContext *ctx, JSFunctionDef *s, int scope_level) { - int idx; - JSVarDef *vd; - - for (idx = s->scopes[scope_level].first; idx >= 0;) { - vd = &s->vars[idx]; - vd->is_captured = 1; - idx = vd->scope_next; - } -} - -/* XXX: should handle the argument scope generically */ -static BOOL is_var_in_arg_scope (const JSVarDef *vd) { - return (js_key_equal_str (vd->var_name, "home_object") - || js_key_equal_str (vd->var_name, "this_active_func") - || js_key_equal_str (vd->var_name, "new_target") - || js_key_equal (vd->var_name, JS_KEY_this) - || js_key_equal_str (vd->var_name, "_arg_var_") - || vd->var_kind == JS_VAR_FUNCTION_NAME); -} - -static void add_eval_variables (JSContext *ctx, JSFunctionDef *s) { - JSFunctionDef *fd; - JSVarDef *vd; - int i, scope_level, scope_idx; - BOOL has_this_binding, is_arg_scope; - - has_this_binding = s->has_this_binding; - if (has_this_binding) { - if (s->this_var_idx < 0) s->this_var_idx = add_var_this (ctx, s); - } - if (s->is_func_expr && !JS_IsNull (s->func_name)) - add_func_var (ctx, s, s->func_name); - - /* eval can use all the variables of the enclosing functions, so - they must be all put in the closure. The closure variables are - ordered by scope. It works only because no closure are created - before. */ - assert (s->is_eval || s->closure_var_count == 0); - - /* XXX: inefficient, but eval performance is less critical */ - fd = s; - for (;;) { - scope_level = fd->parent_scope_level; - fd = fd->parent; - if (!fd) break; - /* add 'this' if it was not previously added */ - if (!has_this_binding && fd->has_this_binding) { - if (fd->this_var_idx < 0) fd->this_var_idx = add_var_this (ctx, fd); - has_this_binding = TRUE; - } - /* add function name */ - if (fd->is_func_expr && !JS_IsNull (fd->func_name)) - add_func_var (ctx, fd, fd->func_name); - - /* add lexical variables */ - scope_idx = fd->scopes[scope_level].first; - while (scope_idx >= 0) { - vd = &fd->vars[scope_idx]; - vd->is_captured = 1; - get_closure_var (ctx, s, fd, FALSE, scope_idx, vd->var_name, vd->is_const, vd->is_lexical, vd->var_kind); - scope_idx = vd->scope_next; - } - is_arg_scope = (scope_idx == ARG_SCOPE_END); - if (!is_arg_scope) { - /* add unscoped variables */ - /* XXX: propagate is_const and var_kind too ? */ - for (i = 0; i < fd->arg_count; i++) { - vd = &fd->args[i]; - if (!JS_IsNull (vd->var_name)) { - get_closure_var (ctx, s, fd, TRUE, i, vd->var_name, FALSE, vd->is_lexical, JS_VAR_NORMAL); - } - } - for (i = 0; i < fd->var_count; i++) { - vd = &fd->vars[i]; - /* do not close top level last result */ - if (vd->scope_level == 0 && !js_key_equal (vd->var_name, JS_KEY__ret_) - && !JS_IsNull (vd->var_name)) { - get_closure_var (ctx, s, fd, FALSE, i, vd->var_name, FALSE, vd->is_lexical, JS_VAR_NORMAL); - } - } - } else { - for (i = 0; i < fd->var_count; i++) { - vd = &fd->vars[i]; - /* do not close top level last result */ - if (vd->scope_level == 0 && is_var_in_arg_scope (vd)) { - get_closure_var (ctx, s, fd, FALSE, i, vd->var_name, FALSE, vd->is_lexical, JS_VAR_NORMAL); - } - } - } - if (fd->is_eval) { - int idx; - /* add direct eval variables (we are necessarily at the - top level) */ - for (idx = 0; idx < fd->closure_var_count; idx++) { - JSClosureVar *cv = &fd->closure_var[idx]; - get_closure_var2 (ctx, s, fd, FALSE, cv->is_arg, idx, cv->var_name, cv->is_const, cv->is_lexical, cv->var_kind); - } - } - } -} - -static void set_closure_from_var (JSContext *ctx, JSClosureVar *cv, JSVarDef *vd, int var_idx) { - cv->is_local = TRUE; - cv->is_arg = FALSE; - cv->is_const = vd->is_const; - cv->is_lexical = vd->is_lexical; - cv->var_kind = vd->var_kind; - cv->var_idx = var_idx; - cv->var_name = vd->var_name; -} - -/* for direct eval compilation: add references to the variables of the - calling function */ -static __exception int add_closure_variables (JSContext *ctx, JSFunctionDef *s, JSFunctionBytecode *b, int scope_idx) { - int i, count; - JSVarDef *vd; - BOOL is_arg_scope; - - count = b->arg_count + b->var_count + b->closure_var_count; - s->closure_var = NULL; - s->closure_var_count = 0; - s->closure_var_size = count; - if (count == 0) return 0; - s->closure_var = js_malloc (ctx, sizeof (s->closure_var[0]) * count); - if (!s->closure_var) return -1; - /* Add lexical variables in scope at the point of evaluation */ - for (i = scope_idx; i >= 0;) { - vd = &b->vardefs[b->arg_count + i]; - if (vd->scope_level > 0) { - JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; - set_closure_from_var (ctx, cv, vd, i); - } - i = vd->scope_next; - } - is_arg_scope = (i == ARG_SCOPE_END); - if (!is_arg_scope) { - /* Add argument variables */ - for (i = 0; i < b->arg_count; i++) { - JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; - vd = &b->vardefs[i]; - cv->is_local = TRUE; - cv->is_arg = TRUE; - cv->is_const = FALSE; - cv->is_lexical = FALSE; - cv->var_kind = JS_VAR_NORMAL; - cv->var_idx = i; - cv->var_name = vd->var_name; - } - /* Add local non lexical variables */ - for (i = 0; i < b->var_count; i++) { - vd = &b->vardefs[b->arg_count + i]; - if (vd->scope_level == 0 && !js_key_equal (vd->var_name, JS_KEY__ret_)) { - JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; - set_closure_from_var (ctx, cv, vd, i); - } - } - } else { - /* only add pseudo variables */ - for (i = 0; i < b->var_count; i++) { - vd = &b->vardefs[b->arg_count + i]; - if (vd->scope_level == 0 && is_var_in_arg_scope (vd)) { - JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; - set_closure_from_var (ctx, cv, vd, i); - } - } - } - for (i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv0 = &b->closure_var[i]; - JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; - cv->is_local = FALSE; - cv->is_arg = cv0->is_arg; - cv->is_const = cv0->is_const; - cv->is_lexical = cv0->is_lexical; - cv->var_kind = cv0->var_kind; - cv->var_idx = i; - cv->var_name = cv0->var_name; - } - return 0; -} - typedef struct CodeContext { const uint8_t *bc_buf; /* code buffer */ int bc_len; /* length of the code buffer */ @@ -15346,15 +15174,6 @@ static __exception int resolve_variables (JSContext *ctx, JSFunctionDef *s) { s->line_number_size++; goto no_change; - case OP_eval: /* convert scope index to adjusted variable index */ - { - int call_argc = get_u16 (bc_buf + pos + 1); - scope = get_u16 (bc_buf + pos + 1 + 2); - mark_eval_captured_variables (ctx, s, scope); - dbuf_putc (&bc_out, op); - dbuf_put_u16 (&bc_out, call_argc); - dbuf_put_u16 (&bc_out, s->scopes[scope].first - ARG_SCOPE_END); - } break; case OP_scope_get_var_checkthis: case OP_scope_get_var_undef: case OP_scope_get_var: @@ -16725,12 +16544,6 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { } } - /* if the function contains an eval call, the closure variables - are used to compile the eval and they must be ordered by scope, - so it is necessary to create the closure variables before any - other variable lookup is done. */ - if (fd->has_eval_call) add_eval_variables (ctx, fd); - /* first create all the child functions */ list_for_each_safe (el, el1, &fd->child_list) { JSFunctionDef *fd1; @@ -16775,7 +16588,7 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { cpool_offset = function_size; function_size += fd->cpool_count * sizeof (*fd->cpool); vardefs_offset = function_size; - if (!fd->strip_debug || fd->has_eval_call) { + if (!fd->strip_debug) { function_size += (fd->arg_count + fd->var_count) * sizeof (*b->vardefs); } closure_var_offset = function_size; @@ -16796,7 +16609,7 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { b->func_name = fd->func_name; if (fd->arg_count + fd->var_count > 0) { - if (fd->strip_debug && !fd->has_eval_call) { + if (fd->strip_debug) { /* Strip variable definitions not needed at runtime */ int i; for (i = 0; i < fd->var_count; i++) { @@ -17445,10 +17258,65 @@ JSValue JS_EvalFunction (JSContext *ctx, JSValue fun_obj) { return JS_EvalFunctionInternal (ctx, fun_obj, ctx->global_obj, NULL); } +/* Eval function with environment record for variable resolution. + The env must be a stoned record. Variables are resolved env first, + then global intrinsics. */ +JSValue JS_EvalFunctionEnv (JSContext *ctx, JSValue fun_obj, JSValue env) { + JSValue ret_val; + JSValue saved_env; + uint32_t tag; + JSGCRef env_ref, fun_ref; + + tag = JS_VALUE_GET_TAG (fun_obj); + /* JSFunctionBytecode uses OBJ_CODE type with JS_TAG_PTR */ + if (tag != JS_TAG_PTR || objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (fun_obj)) != OBJ_CODE) { + return JS_ThrowTypeError (ctx, "bytecode function expected"); + } + + /* Protect env and fun_obj from GC during linking */ + JS_AddGCRef (ctx, &env_ref); + env_ref.val = env; + JS_AddGCRef (ctx, &fun_ref); + fun_ref.val = fun_obj; + + /* Link with environment */ + JSValue linked = JS_LinkFunctionEnv (ctx, fun_ref.val, env_ref.val); + if (JS_IsException (linked)) { + JS_DeleteGCRef (ctx, &fun_ref); + JS_DeleteGCRef (ctx, &env_ref); + return JS_EXCEPTION; + } + + /* Update env from GC ref (may have moved) */ + env = env_ref.val; + JS_DeleteGCRef (ctx, &fun_ref); + JS_DeleteGCRef (ctx, &env_ref); + + /* Save and set eval environment for OP_get_env_slot */ + saved_env = ctx->eval_env; + ctx->eval_env = env; + + /* Create closure and execute */ + linked = js_closure (ctx, linked, NULL); + ret_val = JS_Call (ctx, linked, ctx->global_obj, 0, NULL); + + /* Restore env */ + ctx->eval_env = saved_env; + + return ret_val; +} + /* Link compiled bytecode to context - resolves global references. Returns linked bytecode on success, JS_EXCEPTION on link error. The linked bytecode is a separate copy that can be modified. */ JSValue JS_LinkFunction (JSContext *ctx, JSValue fun_obj) { + return JS_LinkFunctionEnv (ctx, fun_obj, JS_NULL); +} + +/* Link compiled bytecode with environment record for variable resolution. + Variables are resolved: env first, then global intrinsics. + Returns linked bytecode on success, JS_EXCEPTION on link error. */ +JSValue JS_LinkFunctionEnv (JSContext *ctx, JSValue fun_obj, JSValue env) { if (JS_VALUE_GET_TAG (fun_obj) != JS_TAG_PTR) return JS_ThrowTypeError (ctx, "bytecode function expected"); @@ -17457,7 +17325,7 @@ JSValue JS_LinkFunction (JSContext *ctx, JSValue fun_obj) { return JS_ThrowTypeError (ctx, "bytecode function expected"); JSFunctionBytecode *tpl = (JSFunctionBytecode *)hdr; - JSFunctionBytecode *linked = js_link_bytecode (ctx, tpl); + JSFunctionBytecode *linked = js_link_bytecode (ctx, tpl, env); if (!linked) return JS_EXCEPTION; @@ -17467,40 +17335,21 @@ JSValue JS_LinkFunction (JSContext *ctx, JSValue fun_obj) { /* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ static JSValue __JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx) { JSParseState s1, *s = &s1; - int err, js_mode, eval_type; + int err; JSValue fun_obj, ret_val; - JSStackFrame *sf; - JSFunctionBytecode *b; JSFunctionDef *fd; + (void)scope_idx; /* unused - direct eval no longer supported */ js_parse_init (ctx, s, input, input_len, filename); - eval_type = flags & JS_EVAL_TYPE_MASK; - if (eval_type == JS_EVAL_TYPE_DIRECT) { - JSFunction *fn; - sf = ctx->rt->current_stack_frame; - assert (sf != NULL); - assert (JS_VALUE_GET_TAG (sf->cur_func) == JS_TAG_FUNCTION); - fn = JS_VALUE_GET_FUNCTION (sf->cur_func); - assert (fn->kind == JS_FUNC_KIND_BYTECODE); - b = fn->u.func.function_bytecode; - js_mode = b->js_mode; - } else { - sf = NULL; - b = NULL; - js_mode = 0; - } fd = js_new_function_def (ctx, NULL, TRUE, FALSE, filename, s->buf_start, &s->get_line_col_cache); if (!fd) goto fail1; s->cur_func = fd; ctx->current_parse_fd = fd; /* Set GC root for parser's cpool */ - fd->eval_type = eval_type; - fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); - fd->js_mode = js_mode; + fd->eval_type = JS_EVAL_TYPE_GLOBAL; + fd->has_this_binding = TRUE; + fd->js_mode = 0; fd->func_name = JS_KEY__eval_; - if (b) { - if (add_closure_variables (ctx, fd, b, scope_idx)) goto fail; - } push_scope (s); /* body scope */ fd->body_scope = fd->scope_level; @@ -17522,7 +17371,7 @@ static JSValue __JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char * if (flags & JS_EVAL_FLAG_COMPILE_ONLY) { ret_val = fun_obj; } else { - ret_val = JS_EvalFunctionInternal (ctx, fun_obj, this_obj, sf); + ret_val = JS_EvalFunctionInternal (ctx, fun_obj, this_obj, NULL); } return ret_val; fail1: @@ -24318,6 +24167,66 @@ int js_is_blob (JSContext *js, JSValue v) { return js_get_blob (js, v) != NULL; } +/* ============================================================================ + * eval() function - compile and execute code with environment + * ============================================================================ + */ + +/* eval(text, env) - evaluate code with optional environment record + * text: string to compile and execute + * env: optional stone record for variable bindings (checked first before intrinsics) + */ +static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + const char *str; + size_t len; + JSValue env = JS_NULL; + JSValue result; + JSGCRef env_ref; + + if (argc < 1 || !JS_IsText (argv[0])) { + return JS_ThrowTypeError (ctx, "eval requires a text argument"); + } + + /* Get optional environment record (must be stone if provided) */ + if (argc > 1 && !JS_IsNull (argv[1])) { + if (!JS_IsRecord (argv[1])) { + return JS_ThrowTypeError (ctx, "eval environment must be an object"); + } + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (argv[1]); + if (!objhdr_s (rec->mist_hdr)) { + return JS_ThrowTypeError (ctx, "eval environment must be stoned"); + } + env = argv[1]; + } + + /* Protect env from GC during compilation */ + JS_AddGCRef (ctx, &env_ref); + env_ref.val = env; + + /* Get text string */ + str = JS_ToCStringLen (ctx, &len, argv[0]); + if (!str) { + JS_DeleteGCRef (ctx, &env_ref); + return JS_EXCEPTION; + } + + /* Compile the text */ + JSValue fun = JS_Eval (ctx, str, len, "", JS_EVAL_FLAG_COMPILE_ONLY); + JS_FreeCString (ctx, str); + if (JS_IsException (fun)) { + JS_DeleteGCRef (ctx, &env_ref); + return fun; + } + + /* Update env from GC ref (may have moved) */ + env = env_ref.val; + JS_DeleteGCRef (ctx, &env_ref); + + /* Eval with environment */ + result = JS_EvalFunctionEnv (ctx, fun, env); + return result; +} + /* ============================================================================ * stone() function - deep freeze with blob support * ============================================================================ @@ -25294,7 +25203,7 @@ void JS_AddIntrinsicBaseObjects (JSContext *ctx) { ctx->throw_type_error = JS_NewCFunction (ctx, js_throw_type_error, NULL, 0); ctx->global_obj = JS_NewObject (ctx); - ctx->global_var_obj = JS_NewObjectProto (ctx, JS_NULL); + ctx->eval_env = JS_NULL; /* no eval environment by default */ /* Error */ obj1 = JS_NewCFunctionMagic (ctx, js_error_constructor, "Error", 1, JS_CFUNC_generic_magic, -1); @@ -25314,12 +25223,6 @@ void JS_AddIntrinsicBaseObjects (JSContext *ctx) { REGISTER_ERROR(7, "AggregateError"); #undef REGISTER_ERROR - /* global properties */ - { - JSValue key = JS_KEY_STR (ctx, "globalThis"); - JS_SetPropertyInternal (ctx, ctx->global_obj, key, ctx->global_obj); - } - /* Cell Script global functions: text, number, array, object, fn */ { JSValue text_func = JS_NewCFunction (ctx, js_cell_text, "text", 3); @@ -25349,6 +25252,7 @@ void JS_AddIntrinsicBaseObjects (JSContext *ctx) { } /* Core functions - using GC-safe helper */ + js_set_global_cfunc(ctx, "eval", js_cell_eval, 2); js_set_global_cfunc(ctx, "stone", js_cell_stone, 1); js_set_global_cfunc(ctx, "length", js_cell_length, 1); js_set_global_cfunc(ctx, "call", js_cell_call, 3); diff --git a/source/quickjs.h b/source/quickjs.h index 1ff85c6d..825faee9 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -783,6 +783,11 @@ JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len, reading a script or module with JS_ReadObject() */ JSValue JS_EvalFunction (JSContext *ctx, JSValue fun_obj); +/* Eval function with environment record for variable resolution. + The env must be a stoned record. Variables are resolved env first, + then global intrinsics. */ +JSValue JS_EvalFunctionEnv (JSContext *ctx, JSValue fun_obj, JSValue env); + /* Dump bytecode of a compiled function (for debugging) */ void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val); @@ -790,6 +795,11 @@ void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val); Returns linked bytecode on success, JS_EXCEPTION on link error. */ JSValue JS_LinkFunction (JSContext *ctx, JSValue func_val); +/* Link compiled bytecode with environment record for variable resolution. + Variables are resolved: env first, then global intrinsics. + Returns linked bytecode on success, JS_EXCEPTION on link error. */ +JSValue JS_LinkFunctionEnv (JSContext *ctx, JSValue func_val, JSValue env); + /* C function definition */ typedef enum JSCFunctionEnum { JS_CFUNC_generic,