no more global obj; eval w/ env

This commit is contained in:
2026-02-03 22:55:22 -06:00
parent c08249b6f1
commit 19576533d9
3 changed files with 264 additions and 351 deletions

View File

@@ -102,7 +102,6 @@ DEF( return, 1, 1, 0, none)
DEF( return_undef, 1, 0, 0, none) DEF( return_undef, 1, 0, 0, none)
DEF( throw, 1, 1, 0, none) DEF( throw, 1, 1, 0, none)
DEF( throw_error, 6, 0, 0, key_u8) 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 DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
bytecode string */ bytecode string */

View File

@@ -825,8 +825,8 @@ struct JSContext {
JSValue throw_type_error; JSValue throw_type_error;
JSValue eval_obj; JSValue eval_obj;
JSValue global_obj; /* global object */ JSValue global_obj; /* global object (immutable intrinsics) */
JSValue global_var_obj; /* contains the global let/const definitions */ JSValue eval_env; /* environment record for eval (stone record) */
uint64_t random_state; uint64_t random_state;
@@ -1030,7 +1030,9 @@ static int st_text_resize (JSContext *ctx) {
return 0; 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 *js_realloc (JSContext *ctx, void *ptr, size_t size) {
void *new_ptr; void *new_ptr;
@@ -1044,13 +1046,9 @@ void *js_realloc (JSContext *ctx, void *ptr, size_t size) {
return new_ptr; return new_ptr;
} }
/* Bump allocator: allocate new space and copy. /* Bump allocator: just allocate new space.
For simplicity, we allocate new space and copy. */ Caller is responsible for protecting ptr and copying data. */
new_ptr = js_malloc (ctx, size); 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; 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_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_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_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_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_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_reverse (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) { static no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) {
int new_size; int new_size;
void *new_array; void *new_array;
void *old_array = *parray;
int old_size = *psize;
/* XXX: potential arithmetic overflow */ /* XXX: potential arithmetic overflow */
new_size = max_int (req_size, *psize * 3 / 2); new_size = max_int (req_size, old_size * 3 / 2);
new_array = js_realloc (ctx, *parray, new_size * elem_size);
if (!new_array) return -1; /* 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; *psize = new_size;
*parray = new_array; *parray = new_array;
return 0; 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); printf(" after copy: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout);
#endif #endif
#ifdef DUMP_GC_DETAIL #ifdef DUMP_GC_DETAIL
printf(" roots: global_var_obj\n"); fflush(stdout); printf(" roots: eval_env\n"); fflush(stdout);
#endif #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 #ifdef DUMP_GC_DETAIL
printf(" roots: regexp_ctor\n"); fflush(stdout); printf(" roots: regexp_ctor\n"); fflush(stdout);
#endif #endif
@@ -3015,7 +3036,7 @@ static void JS_MarkContext (JSRuntime *rt, JSContext *ctx, JS_MarkFunc *mark_fun
int i; int i;
JS_MarkValue (rt, ctx->global_obj, mark_func); 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->throw_type_error, mark_func);
JS_MarkValue (rt, ctx->eval_obj, 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; return NULL;
} }
int old_cap = (int)objhdr_cap56 (s->hdr); 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; return new_str;
} }
@@ -3727,10 +3766,26 @@ static JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) {
} }
if (JS_IsNull (ret_val)) { 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); JSText *p = js_alloc_string (ctx, new_len);
if (!p) { if (!p) {
JS_PopGCRef (ctx, &op2_ref);
JS_PopGCRef (ctx, &op1_ref);
return JS_EXCEPTION; 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 */ /* Copy characters using string_put/get */
for (int i = 0; i < len1; i++) { for (int i = 0; i < len1; i++) {
string_put (p, i, js_string_value_get (op1, 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++) { for (int i = 0; i < len2; i++) {
string_put (p, len1 + i, js_string_value_get (op2, i)); string_put (p, len1 + i, js_string_value_get (op2, i));
} }
p->length = new_len;
ret_val = JS_MKPTR (p); ret_val = JS_MKPTR (p);
} }
@@ -7216,29 +7272,6 @@ restart:
} }
goto exception; 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) : { CASE (OP_regexp) : {
sp[-2] = js_regexp_constructor_internal (ctx, sp[-2], sp[-1]); sp[-2] = js_regexp_constructor_internal (ctx, sp[-2], sp[-1]);
sp--; sp--;
@@ -8468,9 +8501,8 @@ restart:
CASE (OP_get_env_slot) : { CASE (OP_get_env_slot) : {
int slot = get_u16 (pc); pc += 2; int slot = get_u16 (pc); pc += 2;
(void)slot; JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (ctx->eval_env);
JS_ThrowInternalError (ctx, "OP_get_env_slot not yet implemented"); *sp++ = env->slots[slot].val;
goto exception;
} }
BREAK; BREAK;
@@ -8764,7 +8796,6 @@ typedef struct JSFunctionDef {
BOOL has_simple_parameter_list; BOOL has_simple_parameter_list;
BOOL has_parameter_expressions; /* if true, an argument scope is created */ BOOL has_parameter_expressions; /* if true, an argument scope is created */
BOOL has_use_strict; /* to reject directive in special cases */ 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 BOOL has_this_binding; /* true if the 'this' binding is available in the
function */ function */
BOOL in_function_body; 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. Returns new bytecode on success, NULL on link error.
The linked bytecode is a separate allocation that can be modified. */ The linked bytecode is a separate allocation that can be modified. */
static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, static JSFunctionBytecode *js_link_bytecode (JSContext *ctx,
JSFunctionBytecode *tpl) { JSFunctionBytecode *tpl,
JSValue env) {
/* Calculate total size of bytecode allocation */ /* Calculate total size of bytecode allocation */
int function_size; int function_size;
int cpool_offset, vardefs_offset, closure_var_offset, byte_code_offset; 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 */ /* Walk bytecode and patch global variable access opcodes */
uint8_t *bc = linked->byte_code_buf; uint8_t *bc = linked->byte_code_buf;
int pos = 0; 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) { while (pos < linked->byte_code_len) {
uint8_t op = bc[pos]; uint8_t op = bc[pos];
int len = short_opcode_info (op).size; 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) { if (op == OP_get_var || op == OP_get_var_undef) {
uint32_t cpool_idx = get_u32 (bc + pos + 1); uint32_t cpool_idx = get_u32 (bc + pos + 1);
JSValue name = linked->cpool[cpool_idx]; JSValue name = linked->cpool[cpool_idx];
/* Try global_obj first (for built-ins like 'print') */ /* Try env first (if provided) */
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); if (env_rec) {
int slot = rec_find_slot (global, name); int slot = rec_find_slot (env_rec, name);
if (slot > 0) { if (slot > 0) {
bc[pos] = OP_get_global_slot; bc[pos] = OP_get_env_slot;
put_u16 (bc + pos + 1, (uint16_t)slot); put_u16 (bc + pos + 1, (uint16_t)slot);
bc[pos + 3] = OP_nop; bc[pos + 3] = OP_nop;
bc[pos + 4] = OP_nop; bc[pos + 4] = OP_nop;
pos += len; pos += len;
continue; continue;
}
} }
/* Try global_var_obj (let/const declarations) */ /* Try global_obj (intrinsics like 'print') */
JSRecord *global_var = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj);
slot = rec_find_slot (global_var, name); int slot = rec_find_slot (global, name);
if (slot > 0) { if (slot > 0) {
bc[pos] = OP_get_global_slot; bc[pos] = OP_get_global_slot;
put_u16 (bc + pos + 1, (uint16_t)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 */ /* 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) { if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) {
uint32_t cpool_idx = get_u32 (bc + pos + 1); uint32_t cpool_idx = get_u32 (bc + pos + 1);
JSValue name = linked->cpool[cpool_idx]; JSValue name = linked->cpool[cpool_idx];
/* Try global_obj first */ /* Global object is immutable - can't write to intrinsics */
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); char buf[64];
int slot = rec_find_slot (global, name); JS_ThrowReferenceError (ctx, "cannot assign to '%s' - global object is immutable",
if (slot > 0) { JS_KeyGetStr (ctx, buf, sizeof (buf), name));
bc[pos] = OP_set_global_slot; pjs_free (linked);
put_u16 (bc + pos + 1, (uint16_t)slot); return NULL;
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) */
} }
/* Patch OP_check_var -> OP_nop (if variable exists) */ /* 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); uint32_t cpool_idx = get_u32 (bc + pos + 1);
JSValue name = linked->cpool[cpool_idx]; JSValue name = linked->cpool[cpool_idx];
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); BOOL found = FALSE;
JSRecord *global_var = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); 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 */ /* Variable exists, replace with NOPs */
bc[pos] = OP_nop; bc[pos] = OP_nop;
bc[pos + 1] = 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 + 3] = OP_nop;
bc[pos + 4] = 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; 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); uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
name = fd->cpool[idx]; name = fd->cpool[idx];
scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5); scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5);
if (js_key_equal_str (name, "eval") && call_type == FUNC_CALL_NORMAL /* verify if function name resolves to a simple
&& !has_optional_chain) { get_loc/get_arg: a function call inside a `with`
/* direct 'eval' */ statement can resolve to a method call of the
opcode = OP_eval; `with` context object
} 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
*/
}
drop_count = 1; drop_count = 1;
} break; } break;
default: default:
@@ -12463,12 +12478,6 @@ static __exception int js_parse_postfix_expr (JSParseState *s,
emit_op (s, OP_call_method); emit_op (s, OP_call_method);
emit_u16 (s, arg_count); emit_u16 (s, arg_count);
break; 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: default:
emit_op (s, OP_call); emit_op (s, OP_call);
emit_u16 (s, arg_count); emit_u16 (s, arg_count);
@@ -14801,187 +14810,6 @@ done:
return pos_next; 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 { typedef struct CodeContext {
const uint8_t *bc_buf; /* code buffer */ const uint8_t *bc_buf; /* code buffer */
int bc_len; /* length of the 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++; s->line_number_size++;
goto no_change; 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_checkthis:
case OP_scope_get_var_undef: case OP_scope_get_var_undef:
case OP_scope_get_var: 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 */ /* first create all the child functions */
list_for_each_safe (el, el1, &fd->child_list) { list_for_each_safe (el, el1, &fd->child_list) {
JSFunctionDef *fd1; JSFunctionDef *fd1;
@@ -16775,7 +16588,7 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) {
cpool_offset = function_size; cpool_offset = function_size;
function_size += fd->cpool_count * sizeof (*fd->cpool); function_size += fd->cpool_count * sizeof (*fd->cpool);
vardefs_offset = function_size; 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); function_size += (fd->arg_count + fd->var_count) * sizeof (*b->vardefs);
} }
closure_var_offset = function_size; closure_var_offset = function_size;
@@ -16796,7 +16609,7 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) {
b->func_name = fd->func_name; b->func_name = fd->func_name;
if (fd->arg_count + fd->var_count > 0) { 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 */ /* Strip variable definitions not needed at runtime */
int i; int i;
for (i = 0; i < fd->var_count; 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); 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. /* Link compiled bytecode to context - resolves global references.
Returns linked bytecode on success, JS_EXCEPTION on link error. Returns linked bytecode on success, JS_EXCEPTION on link error.
The linked bytecode is a separate copy that can be modified. */ The linked bytecode is a separate copy that can be modified. */
JSValue JS_LinkFunction (JSContext *ctx, JSValue fun_obj) { 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) if (JS_VALUE_GET_TAG (fun_obj) != JS_TAG_PTR)
return JS_ThrowTypeError (ctx, "bytecode function expected"); 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"); return JS_ThrowTypeError (ctx, "bytecode function expected");
JSFunctionBytecode *tpl = (JSFunctionBytecode *)hdr; JSFunctionBytecode *tpl = (JSFunctionBytecode *)hdr;
JSFunctionBytecode *linked = js_link_bytecode (ctx, tpl); JSFunctionBytecode *linked = js_link_bytecode (ctx, tpl, env);
if (!linked) if (!linked)
return JS_EXCEPTION; 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'. */ /* '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) { 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; JSParseState s1, *s = &s1;
int err, js_mode, eval_type; int err;
JSValue fun_obj, ret_val; JSValue fun_obj, ret_val;
JSStackFrame *sf;
JSFunctionBytecode *b;
JSFunctionDef *fd; JSFunctionDef *fd;
(void)scope_idx; /* unused - direct eval no longer supported */
js_parse_init (ctx, s, input, input_len, filename); 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); fd = js_new_function_def (ctx, NULL, TRUE, FALSE, filename, s->buf_start, &s->get_line_col_cache);
if (!fd) goto fail1; if (!fd) goto fail1;
s->cur_func = fd; s->cur_func = fd;
ctx->current_parse_fd = fd; /* Set GC root for parser's cpool */ ctx->current_parse_fd = fd; /* Set GC root for parser's cpool */
fd->eval_type = eval_type; fd->eval_type = JS_EVAL_TYPE_GLOBAL;
fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); fd->has_this_binding = TRUE;
fd->js_mode = js_mode; fd->js_mode = 0;
fd->func_name = JS_KEY__eval_; fd->func_name = JS_KEY__eval_;
if (b) {
if (add_closure_variables (ctx, fd, b, scope_idx)) goto fail;
}
push_scope (s); /* body scope */ push_scope (s); /* body scope */
fd->body_scope = fd->scope_level; 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) { if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
ret_val = fun_obj; ret_val = fun_obj;
} else { } else {
ret_val = JS_EvalFunctionInternal (ctx, fun_obj, this_obj, sf); ret_val = JS_EvalFunctionInternal (ctx, fun_obj, this_obj, NULL);
} }
return ret_val; return ret_val;
fail1: fail1:
@@ -24318,6 +24167,66 @@ int js_is_blob (JSContext *js, JSValue v) {
return js_get_blob (js, v) != NULL; 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, "<eval>", 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 * 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->throw_type_error = JS_NewCFunction (ctx, js_throw_type_error, NULL, 0);
ctx->global_obj = JS_NewObject (ctx); 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 */ /* Error */
obj1 = JS_NewCFunctionMagic (ctx, js_error_constructor, "Error", 1, JS_CFUNC_generic_magic, -1); 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"); REGISTER_ERROR(7, "AggregateError");
#undef REGISTER_ERROR #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 */ /* Cell Script global functions: text, number, array, object, fn */
{ {
JSValue text_func = JS_NewCFunction (ctx, js_cell_text, "text", 3); 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 */ /* 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, "stone", js_cell_stone, 1);
js_set_global_cfunc(ctx, "length", js_cell_length, 1); js_set_global_cfunc(ctx, "length", js_cell_length, 1);
js_set_global_cfunc(ctx, "call", js_cell_call, 3); js_set_global_cfunc(ctx, "call", js_cell_call, 3);

View File

@@ -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() */ reading a script or module with JS_ReadObject() */
JSValue JS_EvalFunction (JSContext *ctx, JSValue fun_obj); 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) */ /* Dump bytecode of a compiled function (for debugging) */
void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val); 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. */ Returns linked bytecode on success, JS_EXCEPTION on link error. */
JSValue JS_LinkFunction (JSContext *ctx, JSValue func_val); 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 */ /* C function definition */
typedef enum JSCFunctionEnum { typedef enum JSCFunctionEnum {
JS_CFUNC_generic, JS_CFUNC_generic,