no more global obj; eval w/ env
This commit is contained in:
@@ -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 */
|
||||
|
||||
|
||||
604
source/quickjs.c
604
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, "<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
|
||||
* ============================================================================
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user