12741 lines
372 KiB
C
12741 lines
372 KiB
C
/*
|
||
* QuickJS Javascript Engine
|
||
*
|
||
* Copyright (c) 2017-2025 Fabrice Bellard
|
||
* Copyright (c) 2017-2025 Charlie Gordon
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
* of this software and associated documentation files (the "Software"), to
|
||
* deal in the Software without restriction, including without limitation the
|
||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||
* sell copies of the Software, and to permit persons to whom the Software is
|
||
* furnished to do so, subject to the following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be included in
|
||
* all copies or substantial portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||
* IN THE SOFTWARE.
|
||
*/
|
||
|
||
#include "quickjs-internal.h"
|
||
|
||
/* argument of OP_special_object */
|
||
typedef enum {
|
||
OP_SPECIAL_OBJECT_THIS_FUNC,
|
||
OP_SPECIAL_OBJECT_VAR_OBJECT,
|
||
} OPSpecialObjectEnum;
|
||
|
||
JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags) {
|
||
JSContext *ctx = caller_ctx;
|
||
JSFunction *f;
|
||
JSFunctionBytecode *b;
|
||
JSStackFrame sf_s, *sf = &sf_s;
|
||
const uint8_t *pc;
|
||
int opcode, arg_allocated_size, i;
|
||
JSValue *stack_buf, *var_buf, *arg_buf, *sp, ret_val;
|
||
size_t alloca_size;
|
||
|
||
#if !DIRECT_DISPATCH
|
||
#define SWITCH(pc) switch (opcode = *pc++)
|
||
#define CASE(op) case op
|
||
#define DEFAULT default
|
||
#define BREAK break
|
||
#define SWITCH_OPCODE(new_op, target_label) \
|
||
do { \
|
||
const uint8_t *instr_ptr = pc - 1; \
|
||
uint8_t *bc = (uint8_t *)b->byte_code_buf; \
|
||
size_t instr_idx = instr_ptr - b->byte_code_buf; \
|
||
bc[instr_idx] = new_op; \
|
||
goto target_label; \
|
||
} while (0)
|
||
#else
|
||
static const void *const dispatch_table[256] = {
|
||
#define DEF(id, size, n_pop, n_push, f) &&case_OP_##id,
|
||
#if SHORT_OPCODES
|
||
#define def(id, size, n_pop, n_push, f)
|
||
#else
|
||
#define def(id, size, n_pop, n_push, f) &&case_default,
|
||
#endif
|
||
#include "quickjs-opcode.h"
|
||
[OP_COUNT... 255] = &&case_default
|
||
};
|
||
#define SWITCH(pc) goto *dispatch_table[opcode = *pc++];
|
||
#define CASE(op) case_##op
|
||
#define DEFAULT case_default
|
||
#define BREAK SWITCH (pc)
|
||
#endif
|
||
|
||
if (js_poll_interrupts (caller_ctx)) return JS_EXCEPTION;
|
||
if (unlikely (!JS_IsFunction (func_obj))) {
|
||
not_a_function:
|
||
return JS_ThrowTypeError (caller_ctx, "not a function");
|
||
}
|
||
f = JS_VALUE_GET_FUNCTION (func_obj);
|
||
/* Strict arity enforcement: too many arguments throws (length < 0 means variadic) */
|
||
if (unlikely (f->length >= 0 && argc > f->length)) {
|
||
char buf[KEY_GET_STR_BUF_SIZE];
|
||
return JS_ThrowTypeError (
|
||
caller_ctx, "too many arguments for %s: expected %d, got %d", JS_KeyGetStr (caller_ctx, buf, KEY_GET_STR_BUF_SIZE, f->name), f->length, argc);
|
||
}
|
||
switch (f->kind) {
|
||
case JS_FUNC_KIND_C:
|
||
return js_call_c_function (caller_ctx, func_obj, this_obj, argc, (JSValue *)argv);
|
||
case JS_FUNC_KIND_BYTECODE:
|
||
break; /* continue to bytecode execution below */
|
||
case JS_FUNC_KIND_REGISTER:
|
||
return JS_CallRegisterVM(caller_ctx, f->u.reg.code, this_obj, argc, (JSValue *)argv,
|
||
f->u.reg.env_record, f->u.reg.outer_frame);
|
||
case JS_FUNC_KIND_MCODE:
|
||
return mcode_exec(caller_ctx, f->u.mcode.code, this_obj, argc, (JSValue *)argv, f->u.mcode.outer_frame);
|
||
default:
|
||
goto not_a_function;
|
||
}
|
||
b = f->u.func.function_bytecode;
|
||
|
||
if (unlikely (caller_ctx->trace_hook)
|
||
&& (caller_ctx->trace_type & JS_HOOK_CALL)) {
|
||
js_debug dbg;
|
||
js_debug_info (caller_ctx, func_obj, &dbg);
|
||
caller_ctx->trace_hook (caller_ctx, JS_HOOK_CALL, &dbg, caller_ctx->trace_data);
|
||
}
|
||
|
||
if (unlikely (argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) {
|
||
arg_allocated_size = b->arg_count;
|
||
} else {
|
||
arg_allocated_size = 0;
|
||
}
|
||
|
||
alloca_size
|
||
= sizeof (JSValue) * (arg_allocated_size + b->var_count + b->stack_size);
|
||
if (js_check_stack_overflow (ctx, alloca_size))
|
||
return JS_ThrowStackOverflow (caller_ctx);
|
||
|
||
sf->js_mode = b->js_mode;
|
||
sf->arg_count = argc;
|
||
sf->cur_func = (JSValue)func_obj;
|
||
|
||
/* Allocate JSFrame for args + vars on regular heap (stable pointers).
|
||
This enables the outer_frame closure model. */
|
||
{
|
||
int frame_slot_count = b->arg_count + b->var_count;
|
||
size_t frame_size = sizeof(JSFrame) + frame_slot_count * sizeof(JSValue);
|
||
JSFrame *jf = pjs_mallocz(frame_size);
|
||
if (!jf)
|
||
return JS_ThrowOutOfMemory(caller_ctx);
|
||
jf->header = objhdr_make(frame_slot_count, OBJ_FRAME, false, false, false, false);
|
||
jf->function = JS_MKPTR(f);
|
||
jf->caller = JS_NULL;
|
||
jf->return_pc = 0;
|
||
sf->js_frame = JS_MKPTR(jf);
|
||
|
||
/* Point arg_buf and var_buf into JSFrame slots */
|
||
arg_buf = jf->slots;
|
||
var_buf = jf->slots + b->arg_count;
|
||
sf->arg_buf = arg_buf;
|
||
sf->var_buf = var_buf;
|
||
|
||
/* Copy arguments into frame */
|
||
int n = min_int(argc, b->arg_count);
|
||
for (i = 0; i < n; i++)
|
||
arg_buf[i] = argv[i];
|
||
for (; i < b->arg_count; i++)
|
||
arg_buf[i] = JS_NULL;
|
||
if (argc < b->arg_count)
|
||
sf->arg_count = b->arg_count;
|
||
|
||
/* Initialize local variables */
|
||
for (i = 0; i < b->var_count; i++)
|
||
var_buf[i] = JS_NULL;
|
||
}
|
||
|
||
/* Stack is still on C stack (transient) */
|
||
stack_buf = alloca(sizeof(JSValue) * b->stack_size);
|
||
sp = stack_buf;
|
||
pc = b->byte_code_buf;
|
||
sf->stack_buf = stack_buf;
|
||
sf->p_sp = &sp; /* GC uses this to find current stack top */
|
||
sf->prev_frame = ctx->current_stack_frame;
|
||
ctx->current_stack_frame = sf;
|
||
|
||
restart:
|
||
for (;;) {
|
||
int call_argc;
|
||
JSValue *call_argv;
|
||
|
||
SWITCH (pc) {
|
||
CASE (OP_push_i32) : *sp++ = JS_NewInt32 (ctx, get_u32 (pc));
|
||
pc += 4;
|
||
BREAK;
|
||
CASE (OP_push_const) : *sp++ = b->cpool[get_u32 (pc)];
|
||
pc += 4;
|
||
BREAK;
|
||
#if SHORT_OPCODES
|
||
CASE (OP_push_minus1)
|
||
: CASE (OP_push_0)
|
||
: CASE (OP_push_1)
|
||
: CASE (OP_push_2)
|
||
: CASE (OP_push_3)
|
||
: CASE (OP_push_4)
|
||
: CASE (OP_push_5)
|
||
: CASE (OP_push_6)
|
||
: CASE (OP_push_7) : *sp++ = JS_NewInt32 (ctx, opcode - OP_push_0);
|
||
BREAK;
|
||
CASE (OP_push_i8) : *sp++ = JS_NewInt32 (ctx, get_i8 (pc));
|
||
pc += 1;
|
||
BREAK;
|
||
CASE (OP_push_i16) : *sp++ = JS_NewInt32 (ctx, get_i16 (pc));
|
||
pc += 2;
|
||
BREAK;
|
||
CASE (OP_push_const8) : *sp++ = b->cpool[*pc++];
|
||
BREAK;
|
||
CASE (OP_fclosure8)
|
||
: *sp++ = js_closure (ctx, b->cpool[*pc++], sf);
|
||
if (unlikely (JS_IsException (sp[-1]))) goto exception;
|
||
BREAK;
|
||
CASE (OP_push_empty_string) : *sp++ = JS_KEY_empty;
|
||
BREAK;
|
||
#endif
|
||
CASE (OP_null) : *sp++ = JS_NULL;
|
||
BREAK;
|
||
CASE (OP_push_this)
|
||
: /* OP_push_this is only called at the start of a function */
|
||
{
|
||
JSValue val = this_obj;
|
||
*sp++ = val;
|
||
}
|
||
BREAK;
|
||
CASE (OP_push_false) : *sp++ = JS_FALSE;
|
||
BREAK;
|
||
CASE (OP_push_true) : *sp++ = JS_TRUE;
|
||
BREAK;
|
||
CASE (OP_object) : *sp++ = JS_NewObject (ctx);
|
||
if (unlikely (JS_IsException (sp[-1]))) goto exception;
|
||
BREAK;
|
||
CASE (OP_special_object) : {
|
||
int arg = *pc++;
|
||
switch (arg) {
|
||
case OP_SPECIAL_OBJECT_THIS_FUNC:
|
||
*sp++ = sf->cur_func;
|
||
break;
|
||
case OP_SPECIAL_OBJECT_VAR_OBJECT:
|
||
*sp++ = JS_NewObjectProto (ctx, JS_NULL);
|
||
if (unlikely (JS_IsException (sp[-1]))) goto exception;
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_drop) : ;
|
||
sp--;
|
||
BREAK;
|
||
CASE (OP_nip) : ;
|
||
sp[-2] = sp[-1];
|
||
sp--;
|
||
BREAK;
|
||
CASE (OP_nip1)
|
||
: /* a b c -> b c */
|
||
sp[-3] = sp[-2];
|
||
sp[-2] = sp[-1];
|
||
sp--;
|
||
BREAK;
|
||
CASE (OP_dup) : sp[0] = sp[-1];
|
||
sp++;
|
||
BREAK;
|
||
CASE (OP_dup2)
|
||
: /* a b -> a b a b */
|
||
sp[0] = sp[-2];
|
||
sp[1] = sp[-1];
|
||
sp += 2;
|
||
BREAK;
|
||
CASE (OP_dup3)
|
||
: /* a b c -> a b c a b c */
|
||
sp[0] = sp[-3];
|
||
sp[1] = sp[-2];
|
||
sp[2] = sp[-1];
|
||
sp += 3;
|
||
BREAK;
|
||
CASE (OP_dup1)
|
||
: /* a b -> a a b */
|
||
sp[0] = sp[-1];
|
||
sp[-1] = sp[-2];
|
||
sp++;
|
||
BREAK;
|
||
CASE (OP_insert2)
|
||
: /* obj a -> a obj a (dup_x1) */
|
||
sp[0] = sp[-1];
|
||
sp[-1] = sp[-2];
|
||
sp[-2] = sp[0];
|
||
sp++;
|
||
BREAK;
|
||
CASE (OP_insert3)
|
||
: /* obj prop a -> a obj prop a (dup_x2) */
|
||
sp[0] = sp[-1];
|
||
sp[-1] = sp[-2];
|
||
sp[-2] = sp[-3];
|
||
sp[-3] = sp[0];
|
||
sp++;
|
||
BREAK;
|
||
CASE (OP_insert4)
|
||
: /* this obj prop a -> a this obj prop a */
|
||
sp[0] = sp[-1];
|
||
sp[-1] = sp[-2];
|
||
sp[-2] = sp[-3];
|
||
sp[-3] = sp[-4];
|
||
sp[-4] = sp[0];
|
||
sp++;
|
||
BREAK;
|
||
CASE (OP_perm3)
|
||
: /* obj a b -> a obj b (213) */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-2];
|
||
sp[-2] = sp[-3];
|
||
sp[-3] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_rot3l)
|
||
: /* x a b -> a b x (231) */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-3];
|
||
sp[-3] = sp[-2];
|
||
sp[-2] = sp[-1];
|
||
sp[-1] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_rot4l)
|
||
: /* x a b c -> a b c x */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-4];
|
||
sp[-4] = sp[-3];
|
||
sp[-3] = sp[-2];
|
||
sp[-2] = sp[-1];
|
||
sp[-1] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_rot5l)
|
||
: /* x a b c d -> a b c d x */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-5];
|
||
sp[-5] = sp[-4];
|
||
sp[-4] = sp[-3];
|
||
sp[-3] = sp[-2];
|
||
sp[-2] = sp[-1];
|
||
sp[-1] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_rot3r)
|
||
: /* a b x -> x a b (312) */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-1];
|
||
sp[-1] = sp[-2];
|
||
sp[-2] = sp[-3];
|
||
sp[-3] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_perm4)
|
||
: /* obj prop a b -> a obj prop b */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-2];
|
||
sp[-2] = sp[-3];
|
||
sp[-3] = sp[-4];
|
||
sp[-4] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_perm5)
|
||
: /* this obj prop a b -> a this obj prop b */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-2];
|
||
sp[-2] = sp[-3];
|
||
sp[-3] = sp[-4];
|
||
sp[-4] = sp[-5];
|
||
sp[-5] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_swap)
|
||
: /* a b -> b a */
|
||
{
|
||
JSValue tmp;
|
||
tmp = sp[-2];
|
||
sp[-2] = sp[-1];
|
||
sp[-1] = tmp;
|
||
}
|
||
BREAK;
|
||
CASE (OP_swap2)
|
||
: /* a b c d -> c d a b */
|
||
{
|
||
JSValue tmp1, tmp2;
|
||
tmp1 = sp[-4];
|
||
tmp2 = sp[-3];
|
||
sp[-4] = sp[-2];
|
||
sp[-3] = sp[-1];
|
||
sp[-2] = tmp1;
|
||
sp[-1] = tmp2;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_fclosure) : {
|
||
JSValue bfunc = b->cpool[get_u32 (pc)];
|
||
pc += 4;
|
||
*sp++ = js_closure (ctx, bfunc, sf);
|
||
if (unlikely (JS_IsException (sp[-1]))) goto exception;
|
||
}
|
||
BREAK;
|
||
#if SHORT_OPCODES
|
||
CASE (OP_call0)
|
||
: CASE (OP_call1)
|
||
: CASE (OP_call2) : CASE (OP_call3) : call_argc = opcode - OP_call0;
|
||
goto has_call_argc;
|
||
#endif
|
||
CASE (OP_call) : CASE (OP_tail_call) : {
|
||
call_argc = get_u16 (pc);
|
||
pc += 2;
|
||
goto has_call_argc;
|
||
has_call_argc:
|
||
call_argv = sp - call_argc;
|
||
sf->cur_pc = pc;
|
||
/* TODO: Use trampoline - for now keep recursive */
|
||
ret_val = JS_CallInternal (ctx, call_argv[-1], JS_NULL, call_argc, call_argv, 0);
|
||
if (unlikely (JS_IsException (ret_val))) goto exception;
|
||
if (opcode == OP_tail_call) goto done;
|
||
sp -= call_argc + 1;
|
||
*sp++ = ret_val;
|
||
}
|
||
BREAK;
|
||
CASE (OP_call_method) : CASE (OP_tail_call_method) : {
|
||
call_argc = get_u16 (pc);
|
||
pc += 2;
|
||
call_argv = sp - call_argc;
|
||
sf->cur_pc = pc;
|
||
/* Proxy method-call: detect [func, "name", ...args]
|
||
and rewrite as func("name", [args]) */
|
||
|
||
if (JS_IsFunction (call_argv[-2])) {
|
||
JSValue name = call_argv[-1];
|
||
if (!JS_IsText (name)) {
|
||
ret_val
|
||
= JS_ThrowTypeError (ctx, "second argument must be a string");
|
||
goto exception;
|
||
}
|
||
|
||
JSValue args = JS_NewArrayLen (ctx, call_argc);
|
||
if (unlikely (JS_IsException (args))) goto exception;
|
||
|
||
/* Move args into the array, then null out stack slots. */
|
||
JSArray *ar = JS_VALUE_GET_ARRAY (args);
|
||
for (i = 0; i < call_argc; i++) {
|
||
ar->values[i] = call_argv[i];
|
||
call_argv[i] = JS_NULL;
|
||
}
|
||
|
||
JSValue proxy_argv[2];
|
||
proxy_argv[0]
|
||
= name; /* still owned by stack; freed by normal cleanup */
|
||
proxy_argv[1] = args;
|
||
|
||
ret_val
|
||
= JS_CallInternal (ctx, call_argv[-2], JS_NULL, 2, proxy_argv, 0);
|
||
} else {
|
||
ret_val = JS_CallInternal (ctx, call_argv[-1], call_argv[-2], call_argc, call_argv, 0);
|
||
}
|
||
if (unlikely (JS_IsException (ret_val))) goto exception;
|
||
if (opcode == OP_tail_call_method) goto done;
|
||
for (i = -2; i < call_argc; i++)
|
||
sp -= call_argc + 2;
|
||
*sp++ = ret_val;
|
||
}
|
||
BREAK;
|
||
CASE (OP_array_from) : {
|
||
int i;
|
||
|
||
call_argc = get_u16 (pc);
|
||
pc += 2;
|
||
ret_val = JS_NewArrayLen (ctx, call_argc);
|
||
if (unlikely (JS_IsException (ret_val))) goto exception;
|
||
call_argv = sp - call_argc;
|
||
JSArray *ar = JS_VALUE_GET_ARRAY (ret_val);
|
||
for (i = 0; i < call_argc; i++) {
|
||
ar->values[i] = call_argv[i];
|
||
call_argv[i] = JS_NULL;
|
||
}
|
||
sp -= call_argc;
|
||
*sp++ = ret_val;
|
||
}
|
||
BREAK;
|
||
CASE (OP_return) : ret_val = *--sp;
|
||
goto done;
|
||
CASE (OP_return_undef) : ret_val = JS_NULL;
|
||
goto done;
|
||
|
||
CASE (OP_throw) : JS_Throw (ctx, *--sp);
|
||
goto exception;
|
||
|
||
CASE (OP_throw_error)
|
||
:
|
||
#define JS_THROW_VAR_RO 0
|
||
#define JS_THROW_VAR_REDECL 1
|
||
#define JS_THROW_VAR_UNINITIALIZED 2
|
||
#define JS_THROW_ERROR_ITERATOR_THROW 4
|
||
{
|
||
JSValue key;
|
||
int type;
|
||
key = b->cpool[get_u32 (pc)];
|
||
type = pc[4];
|
||
pc += 5;
|
||
if (type == JS_THROW_VAR_REDECL)
|
||
JS_ThrowSyntaxErrorVarRedeclaration (ctx, key);
|
||
else if (type == JS_THROW_VAR_UNINITIALIZED)
|
||
JS_ThrowReferenceErrorUninitialized (ctx, key);
|
||
else if (type == JS_THROW_ERROR_ITERATOR_THROW)
|
||
JS_ThrowTypeError (ctx, "iterator does not have a throw method");
|
||
else
|
||
JS_ThrowInternalError (ctx, "invalid throw var type %d", type);
|
||
}
|
||
goto exception;
|
||
|
||
CASE (OP_regexp) : {
|
||
sp[-2] = js_regexp_constructor_internal (ctx, sp[-2], sp[-1]);
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
|
||
/* Global variable opcodes - resolved by linker to get/set_global_slot */
|
||
CASE (OP_check_var) :
|
||
pc += 4;
|
||
JS_ThrowInternalError (ctx, "OP_check_var: linker should have resolved this");
|
||
goto exception;
|
||
BREAK;
|
||
CASE (OP_get_var_undef) :
|
||
CASE (OP_get_var) :
|
||
pc += 4;
|
||
JS_ThrowInternalError (ctx, "OP_get_var: linker should have resolved to get_global_slot");
|
||
goto exception;
|
||
BREAK;
|
||
CASE (OP_put_var) :
|
||
CASE (OP_put_var_init) :
|
||
pc += 4;
|
||
sp--;
|
||
JS_ThrowInternalError (ctx, "OP_put_var: global object is immutable, linker should resolve");
|
||
goto exception;
|
||
BREAK;
|
||
CASE (OP_put_var_strict) :
|
||
pc += 4;
|
||
sp -= 2;
|
||
JS_ThrowInternalError (ctx, "OP_put_var_strict: global object is immutable, linker should resolve");
|
||
goto exception;
|
||
BREAK;
|
||
|
||
CASE (OP_define_var) :
|
||
CASE (OP_check_define_var) :
|
||
pc += 5;
|
||
JS_ThrowInternalError (ctx, "global object is immutable - cannot define variables at runtime");
|
||
goto exception;
|
||
BREAK;
|
||
CASE (OP_define_func) :
|
||
pc += 5;
|
||
sp--;
|
||
JS_ThrowInternalError (ctx, "global object is immutable - cannot define functions at runtime");
|
||
goto exception;
|
||
BREAK;
|
||
|
||
CASE (OP_get_loc) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
sp[0] = var_buf[idx];
|
||
sp++;
|
||
}
|
||
BREAK;
|
||
CASE (OP_put_loc) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
set_value (ctx, &var_buf[idx], sp[-1]);
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
CASE (OP_set_loc) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
set_value (ctx, &var_buf[idx], sp[-1]);
|
||
}
|
||
BREAK;
|
||
CASE (OP_get_arg) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
sp[0] = arg_buf[idx];
|
||
sp++;
|
||
}
|
||
BREAK;
|
||
CASE (OP_put_arg) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
set_value (ctx, &arg_buf[idx], sp[-1]);
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
CASE (OP_set_arg) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
set_value (ctx, &arg_buf[idx], sp[-1]);
|
||
}
|
||
BREAK;
|
||
|
||
#if SHORT_OPCODES
|
||
CASE (OP_get_loc8) : *sp++ = var_buf[*pc++];
|
||
BREAK;
|
||
CASE (OP_put_loc8) : set_value (ctx, &var_buf[*pc++], *--sp);
|
||
BREAK;
|
||
CASE (OP_set_loc8)
|
||
: set_value (ctx, &var_buf[*pc++], sp[-1]);
|
||
BREAK;
|
||
|
||
CASE (OP_get_loc0) : *sp++ = var_buf[0];
|
||
BREAK;
|
||
CASE (OP_get_loc1) : *sp++ = var_buf[1];
|
||
BREAK;
|
||
CASE (OP_get_loc2) : *sp++ = var_buf[2];
|
||
BREAK;
|
||
CASE (OP_get_loc3) : *sp++ = var_buf[3];
|
||
BREAK;
|
||
CASE (OP_put_loc0) : set_value (ctx, &var_buf[0], *--sp);
|
||
BREAK;
|
||
CASE (OP_put_loc1) : set_value (ctx, &var_buf[1], *--sp);
|
||
BREAK;
|
||
CASE (OP_put_loc2) : set_value (ctx, &var_buf[2], *--sp);
|
||
BREAK;
|
||
CASE (OP_put_loc3) : set_value (ctx, &var_buf[3], *--sp);
|
||
BREAK;
|
||
CASE (OP_set_loc0)
|
||
: set_value (ctx, &var_buf[0], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_set_loc1)
|
||
: set_value (ctx, &var_buf[1], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_set_loc2)
|
||
: set_value (ctx, &var_buf[2], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_set_loc3)
|
||
: set_value (ctx, &var_buf[3], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_get_arg0) : *sp++ = arg_buf[0];
|
||
BREAK;
|
||
CASE (OP_get_arg1) : *sp++ = arg_buf[1];
|
||
BREAK;
|
||
CASE (OP_get_arg2) : *sp++ = arg_buf[2];
|
||
BREAK;
|
||
CASE (OP_get_arg3) : *sp++ = arg_buf[3];
|
||
BREAK;
|
||
CASE (OP_put_arg0) : set_value (ctx, &arg_buf[0], *--sp);
|
||
BREAK;
|
||
CASE (OP_put_arg1) : set_value (ctx, &arg_buf[1], *--sp);
|
||
BREAK;
|
||
CASE (OP_put_arg2) : set_value (ctx, &arg_buf[2], *--sp);
|
||
BREAK;
|
||
CASE (OP_put_arg3) : set_value (ctx, &arg_buf[3], *--sp);
|
||
BREAK;
|
||
CASE (OP_set_arg0)
|
||
: set_value (ctx, &arg_buf[0], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_set_arg1)
|
||
: set_value (ctx, &arg_buf[1], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_set_arg2)
|
||
: set_value (ctx, &arg_buf[2], sp[-1]);
|
||
BREAK;
|
||
CASE (OP_set_arg3)
|
||
: set_value (ctx, &arg_buf[3], sp[-1]);
|
||
BREAK;
|
||
#endif
|
||
CASE (OP_set_loc_uninitialized) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
set_value (ctx, &var_buf[idx], JS_UNINITIALIZED);
|
||
}
|
||
BREAK;
|
||
CASE (OP_get_loc_check) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
if (unlikely (JS_IsUninitialized (var_buf[idx]))) {
|
||
JS_ThrowReferenceErrorUninitialized2 (ctx, b, idx, FALSE);
|
||
goto exception;
|
||
}
|
||
sp[0] = var_buf[idx];
|
||
sp++;
|
||
}
|
||
BREAK;
|
||
CASE (OP_get_loc_checkthis) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
if (unlikely (JS_IsUninitialized (var_buf[idx]))) {
|
||
JS_ThrowReferenceErrorUninitialized2 (caller_ctx, b, idx, FALSE);
|
||
goto exception;
|
||
}
|
||
sp[0] = var_buf[idx];
|
||
sp++;
|
||
}
|
||
BREAK;
|
||
CASE (OP_put_loc_check) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
if (unlikely (JS_IsUninitialized (var_buf[idx]))) {
|
||
JS_ThrowReferenceErrorUninitialized2 (ctx, b, idx, FALSE);
|
||
goto exception;
|
||
}
|
||
set_value (ctx, &var_buf[idx], sp[-1]);
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
CASE (OP_put_loc_check_init) : {
|
||
int idx;
|
||
idx = get_u16 (pc);
|
||
pc += 2;
|
||
if (unlikely (!JS_IsUninitialized (var_buf[idx]))) {
|
||
JS_ThrowReferenceError (ctx, "'this' can be initialized only once");
|
||
goto exception;
|
||
}
|
||
set_value (ctx, &var_buf[idx], sp[-1]);
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_goto) : pc += (int32_t)get_u32 (pc);
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
BREAK;
|
||
#if SHORT_OPCODES
|
||
CASE (OP_goto16) : pc += (int16_t)get_u16 (pc);
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
BREAK;
|
||
CASE (OP_goto8) : pc += (int8_t)pc[0];
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
BREAK;
|
||
#endif
|
||
CASE (OP_if_true) : {
|
||
int res;
|
||
JSValue op1;
|
||
|
||
op1 = sp[-1];
|
||
pc += 4;
|
||
if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) {
|
||
res = JS_VALUE_GET_INT (op1);
|
||
} else {
|
||
res = JS_ToBool (ctx, op1);
|
||
}
|
||
sp--;
|
||
if (res) { pc += (int32_t)get_u32 (pc - 4) - 4; }
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
}
|
||
BREAK;
|
||
CASE (OP_if_false) : {
|
||
int res;
|
||
JSValue op1;
|
||
|
||
op1 = sp[-1];
|
||
pc += 4;
|
||
/* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and
|
||
* JS_TAG_UNDEFINED */
|
||
if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) {
|
||
res = JS_VALUE_GET_INT (op1);
|
||
} else {
|
||
res = JS_ToBool (ctx, op1);
|
||
}
|
||
sp--;
|
||
if (!res) { pc += (int32_t)get_u32 (pc - 4) - 4; }
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
}
|
||
BREAK;
|
||
#if SHORT_OPCODES
|
||
CASE (OP_if_true8) : {
|
||
int res;
|
||
JSValue op1;
|
||
|
||
op1 = sp[-1];
|
||
pc += 1;
|
||
if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) {
|
||
res = JS_VALUE_GET_INT (op1);
|
||
} else {
|
||
res = JS_ToBool (ctx, op1);
|
||
}
|
||
sp--;
|
||
if (res) { pc += (int8_t)pc[-1] - 1; }
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
}
|
||
BREAK;
|
||
CASE (OP_if_false8) : {
|
||
int res;
|
||
JSValue op1;
|
||
|
||
op1 = sp[-1];
|
||
pc += 1;
|
||
if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) {
|
||
res = JS_VALUE_GET_INT (op1);
|
||
} else {
|
||
res = JS_ToBool (ctx, op1);
|
||
}
|
||
sp--;
|
||
if (!res) { pc += (int8_t)pc[-1] - 1; }
|
||
if (unlikely (js_poll_interrupts (ctx))) goto exception;
|
||
}
|
||
BREAK;
|
||
#endif
|
||
CASE (OP_catch) : {
|
||
int32_t diff;
|
||
diff = get_u32 (pc);
|
||
sp[0] = JS_NewCatchOffset (ctx, pc + diff - b->byte_code_buf);
|
||
sp++;
|
||
pc += 4;
|
||
}
|
||
BREAK;
|
||
CASE (OP_gosub) : {
|
||
int32_t diff;
|
||
diff = get_u32 (pc);
|
||
/* XXX: should have a different tag to avoid security flaw */
|
||
sp[0] = JS_NewInt32 (ctx, pc + 4 - b->byte_code_buf);
|
||
sp++;
|
||
pc += diff;
|
||
}
|
||
BREAK;
|
||
CASE (OP_ret) : {
|
||
JSValue op1;
|
||
uint32_t pos;
|
||
op1 = sp[-1];
|
||
if (unlikely (JS_VALUE_GET_TAG (op1) != JS_TAG_INT)) goto ret_fail;
|
||
pos = JS_VALUE_GET_INT (op1);
|
||
if (unlikely (pos >= b->byte_code_len)) {
|
||
ret_fail:
|
||
JS_ThrowInternalError (ctx, "invalid ret value");
|
||
goto exception;
|
||
}
|
||
sp--;
|
||
pc = b->byte_code_buf + pos;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_nip_catch) : {
|
||
JSValue ret_val;
|
||
/* catch_offset ... ret_val -> ret_eval */
|
||
ret_val = *--sp;
|
||
while (sp > stack_buf
|
||
&& JS_VALUE_GET_TAG (sp[-1]) != JS_TAG_CATCH_OFFSET) {
|
||
}
|
||
if (unlikely (sp == stack_buf)) {
|
||
JS_ThrowInternalError (ctx, "nip_catch");
|
||
goto exception;
|
||
}
|
||
sp[-1] = ret_val;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_lnot) : {
|
||
int res;
|
||
JSValue op1;
|
||
|
||
op1 = sp[-1];
|
||
if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) {
|
||
res = JS_VALUE_GET_INT (op1) != 0;
|
||
} else {
|
||
res = JS_ToBool (ctx, op1);
|
||
}
|
||
sp[-1] = JS_NewBool (ctx, !res);
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_get_field) : {
|
||
JSValue val;
|
||
uint32_t idx;
|
||
JSValue key;
|
||
JSValue obj;
|
||
|
||
idx = get_u32 (pc);
|
||
pc += 4;
|
||
|
||
sf->cur_pc = pc;
|
||
|
||
/* Get JSValue key from cpool */
|
||
key = b->cpool[idx];
|
||
|
||
obj = sp[-1];
|
||
|
||
/* Record property access - use JSValue key directly */
|
||
if (JS_IsRecord (obj)) {
|
||
JSRecord *rec = JS_VALUE_GET_RECORD (obj);
|
||
val = rec_get (ctx, rec, key);
|
||
sp[-1] = val;
|
||
} else {
|
||
/* Non-record: return null */
|
||
sp[-1] = JS_NULL;
|
||
}
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_get_field2) : {
|
||
JSValue val;
|
||
uint32_t idx;
|
||
JSValue key;
|
||
JSValue obj;
|
||
|
||
idx = get_u32 (pc);
|
||
pc += 4;
|
||
|
||
sf->cur_pc = pc;
|
||
|
||
/* Get JSValue key from cpool */
|
||
key = b->cpool[idx];
|
||
|
||
obj = sp[-1];
|
||
|
||
/* Proxy method-call sugar: func.name(...) -> func("name", [args...])
|
||
OP_get_field2 is only emitted when a call immediately follows. */
|
||
if (JS_IsFunction (obj)) {
|
||
val = key; /* "name" as JSValue string */
|
||
*sp++ = val; /* stack becomes [func, "name"] */
|
||
} else if (JS_IsRecord (obj)) {
|
||
/* Record property access - use JSValue key directly */
|
||
JSRecord *rec = JS_VALUE_GET_RECORD (obj);
|
||
val = rec_get (ctx, rec, key);
|
||
*sp++ = val;
|
||
} else {
|
||
/* Non-record: push null */
|
||
*sp++ = JS_NULL;
|
||
}
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_put_field) : {
|
||
int ret;
|
||
uint32_t idx;
|
||
JSValue key;
|
||
|
||
idx = get_u32 (pc);
|
||
pc += 4;
|
||
sf->cur_pc = pc;
|
||
|
||
/* Get JSValue key from cpool */
|
||
key = b->cpool[idx];
|
||
|
||
/* Must be a record to set property */
|
||
if (!JS_IsRecord (sp[-2])) {
|
||
sp -= 2;
|
||
JS_ThrowTypeError (ctx, "cannot set property of non-record");
|
||
goto exception;
|
||
}
|
||
|
||
/* Record property set - use JSValue key directly */
|
||
ret = rec_set_own (ctx, &sp[-2], key, sp[-1]);
|
||
sp -= 2;
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_define_field) : {
|
||
int ret;
|
||
uint32_t idx;
|
||
JSValue key;
|
||
|
||
idx = get_u32 (pc);
|
||
pc += 4;
|
||
|
||
/* Get JSValue key from cpool */
|
||
key = b->cpool[idx];
|
||
|
||
/* Must be a record */
|
||
if (!JS_IsRecord (sp[-2])) {
|
||
sp--;
|
||
JS_ThrowTypeError (ctx, "cannot define field on non-record");
|
||
goto exception;
|
||
}
|
||
|
||
/* Record property set - use JSValue key directly */
|
||
ret = rec_set_own (ctx, &sp[-2], key, sp[-1]);
|
||
sp--;
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_set_name) : {
|
||
int ret;
|
||
JSValue key;
|
||
key = b->cpool[get_u32 (pc)];
|
||
pc += 4;
|
||
|
||
ret = JS_DefineObjectName (ctx, sp[-1], key);
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
CASE (OP_set_name_computed) : {
|
||
int ret;
|
||
ret = JS_DefineObjectNameComputed (ctx, sp[-1], sp[-2]);
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
CASE (OP_define_method) : CASE (OP_define_method_computed) : {
|
||
JSValue value;
|
||
JSValue obj;
|
||
JSValue key;
|
||
int ret, op_flags;
|
||
BOOL is_computed;
|
||
#define OP_DEFINE_METHOD_METHOD 0
|
||
#define OP_DEFINE_METHOD_ENUMERABLE 4
|
||
|
||
is_computed = (opcode == OP_define_method_computed);
|
||
if (is_computed) {
|
||
key = sp[-2];
|
||
opcode += OP_define_method - OP_define_method_computed;
|
||
} else {
|
||
key = b->cpool[get_u32 (pc)];
|
||
pc += 4;
|
||
}
|
||
op_flags = *pc++;
|
||
|
||
obj = sp[-2 - is_computed];
|
||
op_flags &= 3;
|
||
value = JS_NULL;
|
||
|
||
if (op_flags == OP_DEFINE_METHOD_METHOD) { value = sp[-1]; }
|
||
ret = js_method_set_properties (ctx, sp[-1], key, 0, obj);
|
||
if (ret >= 0) {
|
||
/* JS_SetProperty consumes value, so don't free sp[-1] on success */
|
||
ret = JS_SetProperty (ctx, obj, key, value);
|
||
} else {
|
||
}
|
||
if (is_computed) { ; }
|
||
sp -= 1 + is_computed;
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_define_class)
|
||
: CASE (OP_define_class_computed)
|
||
: JS_ThrowTypeError (ctx, "classes are not supported");
|
||
goto exception;
|
||
|
||
CASE (OP_get_array_el) : {
|
||
JSValue val;
|
||
sf->cur_pc = pc;
|
||
val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]);
|
||
sp[-2] = val;
|
||
sp--;
|
||
if (unlikely (JS_IsException (val))) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_get_array_el2) : {
|
||
JSValue val;
|
||
sf->cur_pc = pc;
|
||
val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]);
|
||
sp[-1] = val;
|
||
if (unlikely (JS_IsException (val))) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_get_array_el3) : {
|
||
JSValue val;
|
||
switch (JS_VALUE_GET_TAG (sp[-2])) {
|
||
case JS_TAG_INT:
|
||
case JS_TAG_STRING:
|
||
break;
|
||
default:
|
||
/* must be tested nefore JS_ToPropertyKey */
|
||
if (unlikely (JS_IsNull (sp[-2]))) {
|
||
JS_ThrowTypeError (ctx, "value has no property");
|
||
goto exception;
|
||
}
|
||
sf->cur_pc = pc;
|
||
ret_val = sp[-1]; // JS_ToPropertyKey (ctx, sp[-1]); // is this correct? No conversion can happen.
|
||
if (JS_IsException (ret_val)) goto exception;
|
||
sp[-1] = ret_val;
|
||
break;
|
||
}
|
||
sf->cur_pc = pc;
|
||
val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]);
|
||
*sp++ = val;
|
||
if (unlikely (JS_IsException (val))) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_put_array_el) : {
|
||
int ret;
|
||
|
||
/* Functions don't support property assignment in cell script */
|
||
if (JS_IsFunction (sp[-3])) {
|
||
JS_ThrowTypeError (ctx, "cannot set property of function");
|
||
goto exception;
|
||
}
|
||
|
||
sf->cur_pc = pc;
|
||
ret = JS_SetPropertyValue (ctx, sp[-3], sp[-2], sp[-1]);
|
||
sp -= 3;
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_define_array_el) : {
|
||
int ret;
|
||
ret = JS_SetPropertyValue (ctx, sp[-3], sp[-2], sp[-1]);
|
||
sp -= 1;
|
||
if (unlikely (ret < 0)) goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_copy_data_properties)
|
||
: /* target source excludeList */
|
||
{
|
||
/* stack offsets (-1 based):
|
||
2 bits for target,
|
||
3 bits for source,
|
||
2 bits for exclusionList */
|
||
int mask;
|
||
|
||
mask = *pc++;
|
||
sf->cur_pc = pc;
|
||
if (JS_CopyDataProperties (ctx, sp[-1 - (mask & 3)], sp[-1 - ((mask >> 2) & 7)], sp[-1 - ((mask >> 5) & 7)], 0))
|
||
goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_add) : CASE (OP_add_float) : {
|
||
JSValue op1 = sp[-2], op2 = sp[-1], res;
|
||
int tag1 = JS_VALUE_GET_NORM_TAG (op1);
|
||
int tag2 = JS_VALUE_GET_NORM_TAG (op2);
|
||
|
||
/* 1) both ints? keep fast int path with overflow check */
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
int64_t tmp
|
||
= (int64_t)JS_VALUE_GET_INT (op1) + JS_VALUE_GET_INT (op2);
|
||
if (likely ((int)tmp == tmp)) {
|
||
res = JS_NewInt32 (ctx, (int)tmp);
|
||
} else {
|
||
res = __JS_NewFloat64 (ctx, (double)JS_VALUE_GET_INT (op1) + (double)JS_VALUE_GET_INT (op2));
|
||
}
|
||
}
|
||
/* 2) both floats? */
|
||
else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) {
|
||
res = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (op1) + JS_VALUE_GET_FLOAT64 (op2));
|
||
}
|
||
/* 3) both strings? */
|
||
else if (JS_IsText (op1) && JS_IsText (op2)) {
|
||
res = JS_ConcatString (ctx, op1, op2);
|
||
if (JS_IsException (res)) goto exception;
|
||
}
|
||
/* 4) mixed int/float? promote to float */
|
||
// TODO: Seems slow
|
||
else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64)
|
||
|| (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) {
|
||
double a, b;
|
||
if (tag1 == JS_TAG_INT)
|
||
a = (double)JS_VALUE_GET_INT (op1);
|
||
else
|
||
a = JS_VALUE_GET_FLOAT64 (op1);
|
||
if (tag2 == JS_TAG_INT)
|
||
b = (double)JS_VALUE_GET_INT (op2);
|
||
else
|
||
b = JS_VALUE_GET_FLOAT64 (op2);
|
||
res = __JS_NewFloat64 (ctx, a + b);
|
||
} else if (tag1 == JS_TAG_NULL || tag2 == JS_TAG_NULL) {
|
||
/* null + string or string + null should throw */
|
||
if (JS_IsText (op1) || JS_IsText (op2)) {
|
||
JS_ThrowTypeError (ctx, "cannot concatenate null with string");
|
||
goto exception;
|
||
}
|
||
res = JS_NULL;
|
||
}
|
||
/* 5) anything else → throw */
|
||
else {
|
||
JS_ThrowTypeError (ctx, "cannot concatenate with string");
|
||
goto exception;
|
||
}
|
||
|
||
sp[-2] = res;
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
CASE (OP_add_loc) : {
|
||
int idx = *pc++;
|
||
JSValue rhs = sp[-1];
|
||
JSValue lhs = var_buf[idx];
|
||
JSValue res;
|
||
int tag1 = JS_VALUE_GET_NORM_TAG (lhs);
|
||
int tag2 = JS_VALUE_GET_NORM_TAG (rhs);
|
||
|
||
/* 1) both ints? fast path, overflow → float64 */
|
||
if (likely (JS_VALUE_IS_BOTH_INT (lhs, rhs))) {
|
||
int a_i = JS_VALUE_GET_INT (lhs);
|
||
int b_i = JS_VALUE_GET_INT (rhs);
|
||
int64_t tmp = (int64_t)a_i + b_i;
|
||
if ((int)tmp == tmp)
|
||
res = JS_NewInt32 (ctx, (int)tmp);
|
||
else
|
||
res = __JS_NewFloat64 (ctx, (double)a_i + (double)b_i);
|
||
}
|
||
/* 2) both floats? */
|
||
else if (JS_VALUE_IS_BOTH_FLOAT (lhs, rhs)) {
|
||
res = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (lhs) + JS_VALUE_GET_FLOAT64 (rhs));
|
||
}
|
||
/* 3) both strings? */
|
||
else if (JS_IsText (lhs) && JS_IsText (rhs)) {
|
||
res = JS_ConcatString (ctx, lhs, rhs);
|
||
if (JS_IsException (res)) goto exception;
|
||
}
|
||
/* 4) mixed int/float? promote to float64 */
|
||
else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64)
|
||
|| (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) {
|
||
double a = tag1 == JS_TAG_INT ? (double)JS_VALUE_GET_INT (lhs)
|
||
: JS_VALUE_GET_FLOAT64 (lhs);
|
||
double b = tag2 == JS_TAG_INT ? (double)JS_VALUE_GET_INT (rhs)
|
||
: JS_VALUE_GET_FLOAT64 (rhs);
|
||
res = __JS_NewFloat64 (ctx, a + b);
|
||
}
|
||
/* 5) anything else → throw */
|
||
else {
|
||
JS_ThrowTypeError (ctx, "cannot concatenate with string");
|
||
goto exception;
|
||
}
|
||
|
||
var_buf[idx] = res;
|
||
sp--;
|
||
}
|
||
BREAK;
|
||
CASE (OP_sub) : CASE (OP_sub_float) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
int64_t r;
|
||
r = (int64_t)JS_VALUE_GET_INT (op1) - JS_VALUE_GET_INT (op2);
|
||
if (unlikely ((int)r != r)) goto binary_arith_slow;
|
||
sp[-2] = JS_NewInt32 (ctx, r);
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) {
|
||
sp[-2] = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (op1) - JS_VALUE_GET_FLOAT64 (op2));
|
||
sp--;
|
||
} else {
|
||
goto binary_arith_slow;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_mul) : CASE (OP_mul_float) : {
|
||
JSValue op1, op2;
|
||
double d;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
int32_t v1, v2;
|
||
int64_t r;
|
||
v1 = JS_VALUE_GET_INT (op1);
|
||
v2 = JS_VALUE_GET_INT (op2);
|
||
r = (int64_t)v1 * v2;
|
||
if (unlikely ((int)r != r)) {
|
||
d = (double)r;
|
||
goto mul_fp_res;
|
||
}
|
||
/* -0 normalized to 0, no special case needed */
|
||
sp[-2] = JS_NewInt32 (ctx, r);
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) {
|
||
d = JS_VALUE_GET_FLOAT64 (op1) * JS_VALUE_GET_FLOAT64 (op2);
|
||
mul_fp_res:
|
||
sp[-2] = __JS_NewFloat64 (ctx, d);
|
||
sp--;
|
||
} else {
|
||
goto binary_arith_slow;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_div) : CASE (OP_div_float) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
int v1, v2;
|
||
v1 = JS_VALUE_GET_INT (op1);
|
||
v2 = JS_VALUE_GET_INT (op2);
|
||
sp[-2] = JS_NewFloat64 (ctx, (double)v1 / (double)v2);
|
||
sp--;
|
||
} else {
|
||
goto binary_arith_slow;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_mod) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
int v1, v2, r;
|
||
v1 = JS_VALUE_GET_INT (op1);
|
||
v2 = JS_VALUE_GET_INT (op2);
|
||
/* We must avoid v2 = 0, v1 = INT32_MIN and v2 =
|
||
-1 and the cases where the result is -0. */
|
||
if (unlikely (v1 < 0 || v2 <= 0)) goto binary_arith_slow;
|
||
r = v1 % v2;
|
||
sp[-2] = JS_NewInt32 (ctx, r);
|
||
sp--;
|
||
} else {
|
||
goto binary_arith_slow;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_pow) : binary_arith_slow : sf->cur_pc = pc;
|
||
if (js_binary_arith_slow (ctx, sp, opcode)) goto exception;
|
||
sp--;
|
||
BREAK;
|
||
|
||
CASE (OP_plus) : {
|
||
JSValue op1;
|
||
uint32_t tag;
|
||
op1 = sp[-1];
|
||
tag = JS_VALUE_GET_TAG (op1);
|
||
if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64 (tag)) {
|
||
} else {
|
||
sf->cur_pc = pc;
|
||
if (js_unary_arith_slow (ctx, sp, opcode)) goto exception;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_neg) : {
|
||
JSValue op1;
|
||
uint32_t tag;
|
||
int val;
|
||
double d;
|
||
op1 = sp[-1];
|
||
tag = JS_VALUE_GET_TAG (op1);
|
||
if (tag == JS_TAG_INT) {
|
||
val = JS_VALUE_GET_INT (op1);
|
||
/* -0 normalized to 0, val==0 just stays 0 */
|
||
if (unlikely (val == INT32_MIN)) {
|
||
d = -(double)val;
|
||
goto neg_fp_res;
|
||
}
|
||
sp[-1] = JS_NewInt32 (ctx, -val);
|
||
} else if (JS_TAG_IS_FLOAT64 (tag)) {
|
||
d = -JS_VALUE_GET_FLOAT64 (op1);
|
||
neg_fp_res:
|
||
sp[-1] = __JS_NewFloat64 (ctx, d);
|
||
} else {
|
||
sf->cur_pc = pc;
|
||
if (js_unary_arith_slow (ctx, sp, opcode)) goto exception;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_inc) : {
|
||
JSValue op1;
|
||
int val;
|
||
op1 = sp[-1];
|
||
if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) {
|
||
val = JS_VALUE_GET_INT (op1);
|
||
if (unlikely (val == INT32_MAX)) goto inc_slow;
|
||
sp[-1] = JS_NewInt32 (ctx, val + 1);
|
||
} else {
|
||
inc_slow:
|
||
sf->cur_pc = pc;
|
||
if (js_unary_arith_slow (ctx, sp, opcode)) goto exception;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_dec) : {
|
||
JSValue op1;
|
||
int val;
|
||
op1 = sp[-1];
|
||
if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) {
|
||
val = JS_VALUE_GET_INT (op1);
|
||
if (unlikely (val == INT32_MIN)) goto dec_slow;
|
||
sp[-1] = JS_NewInt32 (ctx, val - 1);
|
||
} else {
|
||
dec_slow:
|
||
sf->cur_pc = pc;
|
||
if (js_unary_arith_slow (ctx, sp, opcode)) goto exception;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_post_inc) : CASE (OP_post_dec) : sf->cur_pc = pc;
|
||
if (js_post_inc_slow (ctx, sp, opcode)) goto exception;
|
||
sp++;
|
||
BREAK;
|
||
CASE (OP_inc_loc) : {
|
||
JSValue op1;
|
||
int val;
|
||
int idx;
|
||
idx = *pc;
|
||
pc += 1;
|
||
|
||
op1 = var_buf[idx];
|
||
if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) {
|
||
val = JS_VALUE_GET_INT (op1);
|
||
if (unlikely (val == INT32_MAX)) goto inc_loc_slow;
|
||
var_buf[idx] = JS_NewInt32 (ctx, val + 1);
|
||
} else {
|
||
inc_loc_slow:
|
||
sf->cur_pc = pc;
|
||
/* op1 is read from var_buf, passed by reference to js_unary_arith_slow */
|
||
if (js_unary_arith_slow (ctx, &op1 + 1, OP_inc)) goto exception;
|
||
set_value (ctx, &var_buf[idx], op1);
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_dec_loc) : {
|
||
JSValue op1;
|
||
int val;
|
||
int idx;
|
||
idx = *pc;
|
||
pc += 1;
|
||
|
||
op1 = var_buf[idx];
|
||
if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) {
|
||
val = JS_VALUE_GET_INT (op1);
|
||
if (unlikely (val == INT32_MIN)) goto dec_loc_slow;
|
||
var_buf[idx] = JS_NewInt32 (ctx, val - 1);
|
||
} else {
|
||
dec_loc_slow:
|
||
sf->cur_pc = pc;
|
||
/* op1 is read from var_buf, passed by reference to js_unary_arith_slow */
|
||
if (js_unary_arith_slow (ctx, &op1 + 1, OP_dec)) goto exception;
|
||
set_value (ctx, &var_buf[idx], op1);
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_not) : {
|
||
JSValue op1;
|
||
op1 = sp[-1];
|
||
if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) {
|
||
sp[-1] = JS_NewInt32 (ctx, ~JS_VALUE_GET_INT (op1));
|
||
} else {
|
||
sf->cur_pc = pc;
|
||
if (js_not_slow (ctx, sp)) goto exception;
|
||
}
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_shl) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
uint32_t v1, v2;
|
||
v1 = JS_VALUE_GET_INT (op1);
|
||
v2 = JS_VALUE_GET_INT (op2);
|
||
v2 &= 0x1f;
|
||
sp[-2] = JS_NewInt32 (ctx, v1 << v2);
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)
|
||
|| (JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
&& JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2)))
|
||
|| (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1))
|
||
&& JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) {
|
||
uint32_t v1, v2;
|
||
v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op1)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op1);
|
||
v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op2)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op2);
|
||
v2 &= 0x1f;
|
||
sp[-2] = JS_NewInt32 (ctx, v1 << v2);
|
||
sp--;
|
||
} else {
|
||
sp[-2] = JS_NULL;
|
||
sp--;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_shr) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
uint32_t v2;
|
||
v2 = JS_VALUE_GET_INT (op2);
|
||
v2 &= 0x1f;
|
||
sp[-2] = JS_NewUint32 (ctx, (uint32_t)JS_VALUE_GET_INT (op1) >> v2);
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)
|
||
|| (JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
&& JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2)))
|
||
|| (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1))
|
||
&& JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) {
|
||
uint32_t v1, v2;
|
||
v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op1)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op1);
|
||
v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op2)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op2);
|
||
v2 &= 0x1f;
|
||
sp[-2] = JS_NewUint32 (ctx, v1 >> v2);
|
||
sp--;
|
||
} else {
|
||
sp[-2] = JS_NULL;
|
||
sp--;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_sar) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
uint32_t v2;
|
||
v2 = JS_VALUE_GET_INT (op2);
|
||
v2 &= 0x1f;
|
||
sp[-2] = JS_NewInt32 (ctx, (int)JS_VALUE_GET_INT (op1) >> v2);
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)
|
||
|| (JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
&& JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2)))
|
||
|| (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1))
|
||
&& JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) {
|
||
int32_t v1;
|
||
uint32_t v2;
|
||
v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op1)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op1);
|
||
v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op2)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op2);
|
||
v2 &= 0x1f;
|
||
sp[-2] = JS_NewInt32 (ctx, v1 >> v2);
|
||
sp--;
|
||
} else {
|
||
sp[-2] = JS_NULL;
|
||
sp--;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_and) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) & JS_VALUE_GET_INT (op2));
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)
|
||
|| (JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
&& JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2)))
|
||
|| (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1))
|
||
&& JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) {
|
||
int32_t v1, v2;
|
||
v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op1)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op1);
|
||
v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op2)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op2);
|
||
sp[-2] = JS_NewInt32 (ctx, v1 & v2);
|
||
sp--;
|
||
} else {
|
||
sp[-2] = JS_NULL;
|
||
sp--;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_or) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) | JS_VALUE_GET_INT (op2));
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)
|
||
|| (JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
&& JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2)))
|
||
|| (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1))
|
||
&& JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) {
|
||
int32_t v1, v2;
|
||
v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op1)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op1);
|
||
v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op2)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op2);
|
||
sp[-2] = JS_NewInt32 (ctx, v1 | v2);
|
||
sp--;
|
||
} else {
|
||
sp[-2] = JS_NULL;
|
||
sp--;
|
||
}
|
||
}
|
||
BREAK;
|
||
CASE (OP_xor) : {
|
||
JSValue op1, op2;
|
||
op1 = sp[-2];
|
||
op2 = sp[-1];
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) {
|
||
sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) ^ JS_VALUE_GET_INT (op2));
|
||
sp--;
|
||
} else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)
|
||
|| (JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
&& JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2)))
|
||
|| (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1))
|
||
&& JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) {
|
||
int32_t v1, v2;
|
||
v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op1)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op1);
|
||
v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT
|
||
? JS_VALUE_GET_INT (op2)
|
||
: (int32_t)JS_VALUE_GET_FLOAT64 (op2);
|
||
sp[-2] = JS_NewInt32 (ctx, v1 ^ v2);
|
||
sp--;
|
||
} else {
|
||
sp[-2] = JS_NULL;
|
||
sp--;
|
||
}
|
||
}
|
||
BREAK;
|
||
|
||
#define OP_CMP(opcode, binary_op, slow_call) \
|
||
CASE (opcode) : { \
|
||
JSValue op1, op2; \
|
||
op1 = sp[-2]; \
|
||
op2 = sp[-1]; \
|
||
if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { \
|
||
sp[-2] = JS_NewBool (ctx, JS_VALUE_GET_INT (op1) binary_op JS_VALUE_GET_INT (op2)); \
|
||
sp--; \
|
||
} else { \
|
||
sf->cur_pc = pc; \
|
||
if (slow_call) goto exception; \
|
||
sp--; \
|
||
} \
|
||
} \
|
||
BREAK
|
||
|
||
OP_CMP (OP_lt, <, js_relational_slow (ctx, sp, opcode));
|
||
OP_CMP (OP_lte, <=, js_relational_slow (ctx, sp, opcode));
|
||
OP_CMP (OP_gt, >, js_relational_slow (ctx, sp, opcode));
|
||
OP_CMP (OP_gte, >=, js_relational_slow (ctx, sp, opcode));
|
||
OP_CMP (OP_strict_eq, ==, js_strict_eq_slow (ctx, sp, 0));
|
||
OP_CMP (OP_strict_neq, !=, js_strict_eq_slow (ctx, sp, 1));
|
||
|
||
CASE (OP_in) : sf->cur_pc = pc;
|
||
if (js_operator_in (ctx, sp)) goto exception;
|
||
sp--;
|
||
BREAK;
|
||
CASE (OP_delete) : sf->cur_pc = pc;
|
||
if (js_operator_delete (ctx, sp)) goto exception;
|
||
sp--;
|
||
BREAK;
|
||
|
||
CASE (OP_delete_var) :
|
||
pc += 4;
|
||
JS_ThrowInternalError (ctx, "OP_delete_var: global object is immutable");
|
||
goto exception;
|
||
BREAK;
|
||
|
||
CASE (OP_to_propkey) : switch (JS_VALUE_GET_TAG (sp[-1])) {
|
||
case JS_TAG_INT:
|
||
case JS_TAG_STRING:
|
||
break;
|
||
default:
|
||
sf->cur_pc = pc;
|
||
ret_val = sp[-1]; // JS_ToPropertyKey (ctx, sp[-1]); // is this correct? No conversion can happen.
|
||
if (JS_IsException (ret_val)) goto exception;
|
||
sp[-1] = ret_val;
|
||
break;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_format_template) : {
|
||
int expr_count = get_u16 (pc); pc += 2;
|
||
uint32_t cpool_idx = get_u32 (pc); pc += 4;
|
||
|
||
/* Expression values are on the stack. We'll process them in place,
|
||
building the result string using pretext. */
|
||
JSText *result = pretext_init (ctx, 64);
|
||
if (!result) goto exception;
|
||
|
||
/* Re-read format_str after pretext_init (may have triggered GC) */
|
||
JSValue format_str = b->cpool[cpool_idx];
|
||
|
||
/* Parse format string and substitute {N} with stringified stack values.
|
||
Format string is like "hello {0} world {1}" for `hello ${a} world ${b}`.
|
||
Each {N} refers to stack value at position N (0-indexed from base). */
|
||
JSValue *expr_base = sp - expr_count;
|
||
int fmt_len = js_string_value_len (format_str);
|
||
int pos = 0;
|
||
|
||
while (pos < fmt_len) {
|
||
/* Re-read format_str in case GC moved the cpool entry */
|
||
format_str = b->cpool[cpool_idx];
|
||
|
||
/* Find next '{' */
|
||
int brace_start = -1;
|
||
for (int i = pos; i < fmt_len; i++) {
|
||
if (js_string_value_get (format_str, i) == '{') {
|
||
brace_start = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (brace_start < 0) {
|
||
/* No more braces, copy rest of string */
|
||
format_str = b->cpool[cpool_idx]; /* Re-read */
|
||
for (int i = pos; i < fmt_len; i++) {
|
||
result = pretext_putc (ctx, result, js_string_value_get (format_str, i));
|
||
if (!result) goto exception;
|
||
}
|
||
break;
|
||
}
|
||
|
||
/* Copy text before brace */
|
||
format_str = b->cpool[cpool_idx]; /* Re-read */
|
||
for (int i = pos; i < brace_start; i++) {
|
||
result = pretext_putc (ctx, result, js_string_value_get (format_str, i));
|
||
if (!result) goto exception;
|
||
}
|
||
|
||
/* Find closing '}' */
|
||
format_str = b->cpool[cpool_idx]; /* Re-read */
|
||
int brace_end = -1;
|
||
for (int i = brace_start + 1; i < fmt_len; i++) {
|
||
if (js_string_value_get (format_str, i) == '}') {
|
||
brace_end = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (brace_end < 0) {
|
||
/* No closing brace, copy '{' and continue */
|
||
result = pretext_putc (ctx, result, '{');
|
||
if (!result) goto exception;
|
||
pos = brace_start + 1;
|
||
continue;
|
||
}
|
||
|
||
/* Parse index from {N} */
|
||
int idx = 0;
|
||
format_str = b->cpool[cpool_idx]; /* Re-read */
|
||
for (int i = brace_start + 1; i < brace_end; i++) {
|
||
uint32_t c = js_string_value_get (format_str, i);
|
||
if (c >= '0' && c <= '9') {
|
||
idx = idx * 10 + (c - '0');
|
||
} else {
|
||
idx = -1; /* Invalid */
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (idx >= 0 && idx < expr_count) {
|
||
/* Valid index, stringify the stack value */
|
||
JSValue val = expr_base[idx];
|
||
JSValue str_val = JS_ToString (ctx, val);
|
||
if (JS_IsException (str_val)) {
|
||
goto exception;
|
||
}
|
||
/* Append stringified value to result */
|
||
int str_len = js_string_value_len (str_val);
|
||
for (int i = 0; i < str_len; i++) {
|
||
result = pretext_putc (ctx, result, js_string_value_get (str_val, i));
|
||
if (!result) goto exception;
|
||
}
|
||
} else {
|
||
/* Invalid index, keep original {N} */
|
||
format_str = b->cpool[cpool_idx]; /* Re-read */
|
||
for (int i = brace_start; i <= brace_end; i++) {
|
||
result = pretext_putc (ctx, result, js_string_value_get (format_str, i));
|
||
if (!result) goto exception;
|
||
}
|
||
}
|
||
|
||
pos = brace_end + 1;
|
||
}
|
||
|
||
/* Finalize result string */
|
||
JSValue result_str = pretext_end (ctx, result);
|
||
if (JS_IsException (result_str)) goto exception;
|
||
|
||
/* Pop expr values, push result */
|
||
sp -= expr_count;
|
||
*sp++ = result_str;
|
||
}
|
||
BREAK;
|
||
|
||
/* Upvalue access via outer_frame chain (Phase 5) */
|
||
CASE (OP_get_up) : {
|
||
int depth = *pc++;
|
||
int slot = get_u16 (pc); pc += 2;
|
||
JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot);
|
||
if (!p) {
|
||
JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot);
|
||
goto exception;
|
||
}
|
||
*sp++ = *p;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_set_up) : {
|
||
int depth = *pc++;
|
||
int slot = get_u16 (pc); pc += 2;
|
||
JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot);
|
||
if (!p) {
|
||
JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot);
|
||
goto exception;
|
||
}
|
||
*p = *--sp;
|
||
}
|
||
BREAK;
|
||
|
||
/* Name resolution with bytecode patching (Phase 6) */
|
||
CASE (OP_get_name) : {
|
||
uint32_t idx = get_u32 (pc); pc += 4;
|
||
(void)idx;
|
||
JS_ThrowInternalError (ctx, "OP_get_name not yet implemented");
|
||
goto exception;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_get_env_slot) : {
|
||
int slot = get_u16 (pc); pc += 2;
|
||
/* Get env_record from current function, not global ctx->eval_env */
|
||
JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func);
|
||
JSValue env_val = fn->u.func.env_record;
|
||
if (JS_IsNull (env_val)) {
|
||
JS_ThrowReferenceError (ctx, "no environment record");
|
||
goto exception;
|
||
}
|
||
JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (env_val);
|
||
*sp++ = env->slots[slot].val;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_set_env_slot) : {
|
||
int slot = get_u16 (pc); pc += 2;
|
||
/* Get env_record from current function */
|
||
JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func);
|
||
JSValue env_val = fn->u.func.env_record;
|
||
if (JS_IsNull (env_val)) {
|
||
JS_ThrowReferenceError (ctx, "no environment record");
|
||
goto exception;
|
||
}
|
||
JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (env_val);
|
||
env->slots[slot].val = *--sp;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_get_global_slot) : {
|
||
int slot = get_u16 (pc); pc += 2;
|
||
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj);
|
||
*sp++ = global->slots[slot].val;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_set_global_slot) : {
|
||
int slot = get_u16 (pc); pc += 2;
|
||
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj);
|
||
global->slots[slot].val = *--sp;
|
||
}
|
||
BREAK;
|
||
|
||
CASE (OP_nop) : BREAK;
|
||
CASE (OP_is_null) : if (JS_VALUE_GET_TAG (sp[-1]) == JS_TAG_NULL) {
|
||
goto set_true;
|
||
}
|
||
else { goto free_and_set_false; }
|
||
set_true:
|
||
sp[-1] = JS_TRUE;
|
||
BREAK;
|
||
free_and_set_false:
|
||
sp[-1] = JS_FALSE;
|
||
BREAK;
|
||
CASE (OP_invalid)
|
||
: DEFAULT
|
||
: JS_ThrowInternalError (ctx, "invalid opcode: pc=%u opcode=0x%02x", (int)(pc - b->byte_code_buf - 1), opcode);
|
||
goto exception;
|
||
}
|
||
}
|
||
exception:
|
||
if (is_backtrace_needed (ctx, ctx->current_exception)) {
|
||
/* add the backtrace information now (it is not done
|
||
before if the exception happens in a bytecode
|
||
operation */
|
||
sf->cur_pc = pc;
|
||
build_backtrace (ctx, ctx->current_exception, NULL, 0, 0, 0);
|
||
}
|
||
/* All exceptions are catchable in the simplified runtime */
|
||
while (sp > stack_buf) {
|
||
JSValue val = *--sp;
|
||
if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) {
|
||
int pos = JS_VALUE_GET_INT (val);
|
||
if (pos != 0) {
|
||
*sp++ = ctx->current_exception;
|
||
ctx->current_exception = JS_UNINITIALIZED;
|
||
pc = b->byte_code_buf + pos;
|
||
goto restart;
|
||
}
|
||
}
|
||
}
|
||
ret_val = JS_EXCEPTION;
|
||
done:
|
||
ctx->current_stack_frame = sf->prev_frame;
|
||
|
||
if (unlikely (caller_ctx->trace_hook)
|
||
&& (caller_ctx->trace_type & JS_HOOK_RET))
|
||
caller_ctx->trace_hook (caller_ctx, JS_HOOK_RET, NULL, caller_ctx->trace_data);
|
||
|
||
return ret_val;
|
||
}
|
||
|
||
/* JS parser */
|
||
|
||
|
||
/* Map token values to kind strings for tokenizer output */
|
||
|
||
typedef struct BlockEnv {
|
||
struct BlockEnv *prev;
|
||
JSValue label_name; /* JS_NULL if none */
|
||
int label_break; /* -1 if none */
|
||
int label_cont; /* -1 if none */
|
||
int drop_count; /* number of stack elements to drop */
|
||
int label_finally; /* -1 if none */
|
||
int scope_level;
|
||
uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */
|
||
} BlockEnv;
|
||
|
||
typedef struct JSGlobalVar {
|
||
int cpool_idx; /* if >= 0, index in the constant pool for hoisted
|
||
function defintion*/
|
||
uint8_t force_init : 1; /* force initialization to undefined */
|
||
uint8_t is_lexical : 1; /* global let/const definition */
|
||
uint8_t is_const : 1; /* const definition */
|
||
int scope_level; /* scope of definition */
|
||
JSValue var_name; /* variable name as JSValue text */
|
||
} JSGlobalVar;
|
||
|
||
typedef struct RelocEntry {
|
||
struct RelocEntry *next;
|
||
uint32_t addr; /* address to patch */
|
||
int size; /* address size: 1, 2 or 4 bytes */
|
||
} RelocEntry;
|
||
|
||
typedef struct JumpSlot {
|
||
int op;
|
||
int size;
|
||
int pos;
|
||
int label;
|
||
} JumpSlot;
|
||
|
||
typedef struct LabelSlot {
|
||
int ref_count;
|
||
int pos; /* phase 1 address, -1 means not resolved yet */
|
||
int pos2; /* phase 2 address, -1 means not resolved yet */
|
||
int addr; /* phase 3 address, -1 means not resolved yet */
|
||
RelocEntry *first_reloc;
|
||
} LabelSlot;
|
||
|
||
typedef struct LineNumberSlot {
|
||
uint32_t pc;
|
||
uint32_t source_pos;
|
||
} LineNumberSlot;
|
||
|
||
|
||
typedef enum JSParseFunctionEnum {
|
||
JS_PARSE_FUNC_STATEMENT,
|
||
JS_PARSE_FUNC_VAR,
|
||
JS_PARSE_FUNC_EXPR,
|
||
JS_PARSE_FUNC_ARROW,
|
||
JS_PARSE_FUNC_GETTER,
|
||
JS_PARSE_FUNC_SETTER,
|
||
JS_PARSE_FUNC_METHOD,
|
||
JS_PARSE_FUNC_CLASS_STATIC_INIT,
|
||
} JSParseFunctionEnum;
|
||
|
||
typedef struct JSFunctionDef {
|
||
JSContext *ctx;
|
||
struct JSFunctionDef *parent;
|
||
int parent_cpool_idx; /* index in the constant pool of the parent
|
||
or -1 if none */
|
||
int parent_scope_level; /* scope level in parent at point of definition */
|
||
struct list_head child_list; /* list of JSFunctionDef.link */
|
||
struct list_head link;
|
||
|
||
BOOL is_global_var; /* TRUE if variables are not defined locally */
|
||
BOOL is_func_expr; /* TRUE if function expression */
|
||
BOOL has_prototype; /* true if a prototype field is necessary */
|
||
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_this_binding; /* true if the 'this' binding is available in the
|
||
function */
|
||
BOOL in_function_body;
|
||
JSParseFunctionEnum func_type : 8;
|
||
uint8_t js_mode; /* bitmap of JS_MODE_x */
|
||
JSValue func_name; /* JS_NULL if no name */
|
||
|
||
JSVarDef *vars;
|
||
int var_size; /* allocated size for vars[] */
|
||
int var_count;
|
||
JSVarDef *args;
|
||
int arg_size; /* allocated size for args[] */
|
||
int arg_count; /* number of arguments */
|
||
int defined_arg_count;
|
||
int var_object_idx; /* -1 if none */
|
||
int arg_var_object_idx; /* -1 if none (var object for the argument scope) */
|
||
int func_var_idx; /* variable containing the current function (-1
|
||
if none, only used if is_func_expr is true) */
|
||
int this_var_idx; /* variable containg the 'this' value, -1 if none */
|
||
int this_active_func_var_idx; /* variable containg the 'this.active_func'
|
||
value, -1 if none */
|
||
|
||
int scope_level; /* index into fd->scopes if the current lexical scope */
|
||
int scope_first; /* index into vd->vars of first lexically scoped variable */
|
||
int scope_size; /* allocated size of fd->scopes array */
|
||
int scope_count; /* number of entries used in the fd->scopes array */
|
||
JSVarScope *scopes;
|
||
JSVarScope def_scope_array[4];
|
||
int body_scope; /* scope of the body of the function or eval */
|
||
|
||
int global_var_count;
|
||
int global_var_size;
|
||
JSGlobalVar *global_vars;
|
||
|
||
DynBuf byte_code;
|
||
int last_opcode_pos; /* -1 if no last opcode */
|
||
const uint8_t *last_opcode_source_ptr;
|
||
BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */
|
||
|
||
LabelSlot *label_slots;
|
||
int label_size; /* allocated size for label_slots[] */
|
||
int label_count;
|
||
BlockEnv *top_break; /* break/continue label stack */
|
||
|
||
/* constant pool (strings, functions, numbers) */
|
||
JSValue *cpool;
|
||
int cpool_count;
|
||
int cpool_size;
|
||
|
||
/* list of variables in the closure */
|
||
int closure_var_count;
|
||
int closure_var_size;
|
||
JSClosureVar *closure_var;
|
||
|
||
JumpSlot *jump_slots;
|
||
int jump_size;
|
||
int jump_count;
|
||
|
||
LineNumberSlot *line_number_slots;
|
||
int line_number_size;
|
||
int line_number_count;
|
||
int line_number_last;
|
||
int line_number_last_pc;
|
||
|
||
/* pc2line table */
|
||
BOOL strip_debug
|
||
: 1; /* strip all debug info (implies strip_source = TRUE) */
|
||
BOOL strip_source : 1; /* strip only source code */
|
||
JSValue filename;
|
||
uint32_t source_pos; /* pointer in the eval() source */
|
||
GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */
|
||
DynBuf pc2line;
|
||
|
||
char *source; /* raw source, utf-8 encoded */
|
||
int source_len;
|
||
} JSFunctionDef;
|
||
|
||
/* Scan a single JSFunctionDef's cpool and recursively scan its children */
|
||
void gc_scan_parser_fd (JSContext *ctx, JSFunctionDef *fd,
|
||
uint8_t *from_base, uint8_t *from_end,
|
||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) {
|
||
if (!fd) return;
|
||
/* Scan constant pool */
|
||
for (int i = 0; i < fd->cpool_count; i++) {
|
||
fd->cpool[i] = gc_copy_value (ctx, fd->cpool[i], from_base, from_end, to_base, to_free, to_end);
|
||
}
|
||
/* Scan func_name */
|
||
fd->func_name = gc_copy_value (ctx, fd->func_name, from_base, from_end, to_base, to_free, to_end);
|
||
/* Scan filename */
|
||
fd->filename = gc_copy_value (ctx, fd->filename, from_base, from_end, to_base, to_free, to_end);
|
||
/* Recursively scan child functions */
|
||
struct list_head *el;
|
||
list_for_each (el, &fd->child_list) {
|
||
JSFunctionDef *child = list_entry (el, JSFunctionDef, link);
|
||
gc_scan_parser_fd (ctx, child, from_base, from_end, to_base, to_free, to_end);
|
||
}
|
||
}
|
||
|
||
/* Scan parser's cpool values during GC - called when parsing is in progress */
|
||
void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end,
|
||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) {
|
||
gc_scan_parser_fd (ctx, ctx->current_parse_fd, from_base, from_end, to_base, to_free, to_end);
|
||
}
|
||
|
||
typedef struct JSToken {
|
||
int val;
|
||
const uint8_t *ptr; /* position in the source */
|
||
union {
|
||
struct {
|
||
JSValue str;
|
||
int sep;
|
||
} str;
|
||
struct {
|
||
JSValue val;
|
||
} num;
|
||
struct {
|
||
JSValue str; /* identifier as JSValue text */
|
||
BOOL has_escape;
|
||
BOOL is_reserved;
|
||
} ident;
|
||
struct {
|
||
JSValue body;
|
||
JSValue flags;
|
||
} regexp;
|
||
} u;
|
||
} JSToken;
|
||
|
||
typedef struct JSParseState {
|
||
JSContext *ctx;
|
||
const char *filename;
|
||
JSToken token;
|
||
BOOL got_lf; /* true if got line feed before the current token */
|
||
const uint8_t *last_ptr;
|
||
const uint8_t *buf_start;
|
||
const uint8_t *buf_ptr;
|
||
const uint8_t *buf_end;
|
||
|
||
/* current function code */
|
||
JSFunctionDef *cur_func;
|
||
BOOL ext_json; /* true if accepting JSON superset */
|
||
GetLineColCache get_line_col_cache;
|
||
} JSParseState;
|
||
|
||
|
||
/* Clone bytecode and resolve OP_get_var to OP_get_global_slot.
|
||
Returns new bytecode on success, NULL on link error.
|
||
The linked bytecode is a separate allocation that can be modified.
|
||
Note: closure_var is not copied - closures use outer_frame chain at runtime. */
|
||
JSFunctionBytecode *js_link_bytecode (JSContext *ctx,
|
||
JSFunctionBytecode *tpl,
|
||
JSValue env) {
|
||
/* Calculate total size of bytecode allocation */
|
||
int function_size;
|
||
int cpool_offset, vardefs_offset, byte_code_offset;
|
||
|
||
if (tpl->has_debug) {
|
||
function_size = sizeof (JSFunctionBytecode);
|
||
} else {
|
||
function_size = offsetof (JSFunctionBytecode, debug);
|
||
}
|
||
|
||
cpool_offset = function_size;
|
||
function_size += tpl->cpool_count * sizeof (JSValue);
|
||
|
||
vardefs_offset = function_size;
|
||
if (tpl->vardefs) {
|
||
function_size += (tpl->arg_count + tpl->var_count) * sizeof (JSVarDef);
|
||
}
|
||
|
||
/* closure_var not needed at runtime - closures use outer_frame chain */
|
||
|
||
byte_code_offset = function_size;
|
||
function_size += tpl->byte_code_len;
|
||
|
||
/* Allocate and copy */
|
||
JSFunctionBytecode *linked = pjs_malloc (function_size);
|
||
if (!linked) return NULL;
|
||
|
||
/* Copy base structure */
|
||
if (tpl->has_debug) {
|
||
memcpy (linked, tpl, sizeof (JSFunctionBytecode));
|
||
} else {
|
||
memcpy (linked, tpl, offsetof (JSFunctionBytecode, debug));
|
||
}
|
||
|
||
/* Clear closure_var - not needed at runtime */
|
||
linked->closure_var = NULL;
|
||
linked->closure_var_count = 0;
|
||
|
||
/* Fix up self pointers */
|
||
if (tpl->cpool_count > 0) {
|
||
linked->cpool = (JSValue *)((uint8_t *)linked + cpool_offset);
|
||
memcpy (linked->cpool, tpl->cpool, tpl->cpool_count * sizeof (JSValue));
|
||
}
|
||
|
||
if (tpl->vardefs) {
|
||
linked->vardefs = (JSVarDef *)((uint8_t *)linked + vardefs_offset);
|
||
memcpy (linked->vardefs, tpl->vardefs,
|
||
(tpl->arg_count + tpl->var_count) * sizeof (JSVarDef));
|
||
}
|
||
|
||
linked->byte_code_buf = (uint8_t *)linked + byte_code_offset;
|
||
memcpy (linked->byte_code_buf, tpl->byte_code_buf, tpl->byte_code_len);
|
||
|
||
/* Mark as writable (for patching) */
|
||
linked->read_only_bytecode = 0;
|
||
|
||
/* 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 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 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_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);
|
||
bc[pos + 3] = OP_nop;
|
||
bc[pos + 4] = OP_nop;
|
||
pos += len;
|
||
continue;
|
||
}
|
||
|
||
/* Link error: variable not found */
|
||
if (op == OP_get_var) {
|
||
char buf[64];
|
||
JS_ThrowReferenceError (ctx, "'%s' is not defined",
|
||
JS_KeyGetStr (ctx, buf, sizeof (buf), name));
|
||
pjs_free (linked);
|
||
return NULL;
|
||
}
|
||
/* OP_get_var_undef is ok - leaves as is for runtime check */
|
||
}
|
||
|
||
/* Patch OP_put_var family -> OP_set_env_slot or error */
|
||
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 env first (if provided) - env is writable */
|
||
if (env_rec) {
|
||
int slot = rec_find_slot (env_rec, name);
|
||
if (slot > 0) {
|
||
bc[pos] = OP_set_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 for set_global_slot */
|
||
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;
|
||
}
|
||
|
||
/* Global object is immutable - can't write to intrinsics */
|
||
char buf[64];
|
||
JS_ThrowReferenceError (ctx, "cannot assign to '%s' - not found in environment",
|
||
JS_KeyGetStr (ctx, buf, sizeof (buf), name));
|
||
pjs_free (linked);
|
||
return NULL;
|
||
}
|
||
|
||
/* Patch OP_check_var -> OP_nop (if variable exists) */
|
||
if (op == OP_check_var) {
|
||
uint32_t cpool_idx = get_u32 (bc + pos + 1);
|
||
JSValue name = linked->cpool[cpool_idx];
|
||
|
||
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 (found) {
|
||
/* Variable exists, replace with NOPs */
|
||
bc[pos] = OP_nop;
|
||
bc[pos + 1] = OP_nop;
|
||
bc[pos + 2] = OP_nop;
|
||
bc[pos + 3] = OP_nop;
|
||
bc[pos + 4] = OP_nop;
|
||
}
|
||
/* else leave check_var for runtime */
|
||
}
|
||
|
||
pos += len;
|
||
}
|
||
|
||
return linked;
|
||
}
|
||
|
||
/* New simplified linker producing JSCompiledUnit.
|
||
Converts JSFunctionBytecode template to JSCompiledUnit:
|
||
- Copies bytecode, cpool (no vardefs, no closure_var)
|
||
- Patches OP_get_var -> OP_get_env_slot or OP_get_global_slot
|
||
- Returns standalone unit ready for execution */
|
||
static JSCompiledUnit *js_link_unit (JSContext *ctx,
|
||
JSFunctionBytecode *tpl,
|
||
JSValue env) {
|
||
int function_size;
|
||
int cpool_offset, byte_code_offset;
|
||
|
||
/* Calculate size: base struct + cpool + bytecode */
|
||
if (tpl->has_debug) {
|
||
function_size = sizeof (JSCompiledUnit);
|
||
} else {
|
||
function_size = offsetof (JSCompiledUnit, debug);
|
||
}
|
||
|
||
cpool_offset = function_size;
|
||
function_size += tpl->cpool_count * sizeof (JSValue);
|
||
|
||
byte_code_offset = function_size;
|
||
function_size += tpl->byte_code_len;
|
||
|
||
/* Allocate */
|
||
JSCompiledUnit *unit = pjs_malloc (function_size);
|
||
if (!unit) return NULL;
|
||
|
||
/* Initialize header */
|
||
unit->header = objhdr_make (0, OBJ_CODE, false, false, false, false);
|
||
unit->has_debug = tpl->has_debug;
|
||
unit->read_only_bytecode = 0;
|
||
|
||
/* Copy stack requirements */
|
||
unit->local_count = tpl->arg_count + tpl->var_count;
|
||
unit->stack_size = tpl->stack_size;
|
||
|
||
/* Setup cpool */
|
||
unit->cpool_count = tpl->cpool_count;
|
||
if (tpl->cpool_count > 0) {
|
||
unit->cpool = (JSValue *)((uint8_t *)unit + cpool_offset);
|
||
memcpy (unit->cpool, tpl->cpool, tpl->cpool_count * sizeof (JSValue));
|
||
} else {
|
||
unit->cpool = NULL;
|
||
}
|
||
|
||
/* Copy bytecode */
|
||
unit->byte_code_buf = (uint8_t *)unit + byte_code_offset;
|
||
unit->byte_code_len = tpl->byte_code_len;
|
||
memcpy (unit->byte_code_buf, tpl->byte_code_buf, tpl->byte_code_len);
|
||
|
||
/* Copy debug info if present */
|
||
if (tpl->has_debug) {
|
||
unit->debug.filename = tpl->debug.filename;
|
||
unit->debug.source_len = tpl->debug.source_len;
|
||
unit->debug.pc2line_len = tpl->debug.pc2line_len;
|
||
unit->debug.pc2line_buf = tpl->debug.pc2line_buf;
|
||
unit->debug.source = tpl->debug.source;
|
||
}
|
||
|
||
/* Walk bytecode and patch global variable access opcodes */
|
||
uint8_t *bc = unit->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 < unit->byte_code_len) {
|
||
uint8_t op = bc[pos];
|
||
int len = short_opcode_info (op).size;
|
||
|
||
/* 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 = unit->cpool[cpool_idx];
|
||
|
||
/* 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_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);
|
||
bc[pos + 3] = OP_nop;
|
||
bc[pos + 4] = OP_nop;
|
||
pos += len;
|
||
continue;
|
||
}
|
||
|
||
/* Link error: variable not found */
|
||
if (op == OP_get_var) {
|
||
char buf[64];
|
||
JS_ThrowReferenceError (ctx, "'%s' is not defined",
|
||
JS_KeyGetStr (ctx, buf, sizeof (buf), name));
|
||
pjs_free (unit);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/* 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 = unit->cpool[cpool_idx];
|
||
char buf[64];
|
||
JS_ThrowReferenceError (ctx, "cannot assign to '%s' - global object is immutable",
|
||
JS_KeyGetStr (ctx, buf, sizeof (buf), name));
|
||
pjs_free (unit);
|
||
return NULL;
|
||
}
|
||
|
||
/* Patch OP_check_var -> OP_nop (if variable exists) */
|
||
if (op == OP_check_var) {
|
||
uint32_t cpool_idx = get_u32 (bc + pos + 1);
|
||
JSValue name = unit->cpool[cpool_idx];
|
||
|
||
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 (found) {
|
||
bc[pos] = OP_nop;
|
||
bc[pos + 1] = OP_nop;
|
||
bc[pos + 2] = OP_nop;
|
||
bc[pos + 3] = OP_nop;
|
||
bc[pos + 4] = OP_nop;
|
||
}
|
||
}
|
||
|
||
pos += len;
|
||
}
|
||
|
||
return unit;
|
||
}
|
||
|
||
static __exception int next_token (JSParseState *s);
|
||
|
||
static void free_token (JSParseState *s, JSToken *token) {
|
||
switch (token->val) {
|
||
case TOK_NUMBER:
|
||
break;
|
||
case TOK_STRING:
|
||
case TOK_TEMPLATE:
|
||
break;
|
||
case TOK_REGEXP:
|
||
break;
|
||
case TOK_IDENT:
|
||
break;
|
||
default:
|
||
if (token->val >= TOK_FIRST_KEYWORD && token->val <= TOK_LAST_KEYWORD) {
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void __attribute ((unused)) dump_token (JSParseState *s,
|
||
const JSToken *token) {
|
||
switch (token->val) {
|
||
case TOK_NUMBER: {
|
||
double d;
|
||
JS_ToFloat64 (s->ctx, &d, token->u.num.val); /* no exception possible */
|
||
printf ("number: %.14g\n", d);
|
||
} break;
|
||
case TOK_IDENT:
|
||
dump_ident: {
|
||
const char *str = JS_ToCString (s->ctx, token->u.ident.str);
|
||
printf ("ident: '%s'\n", str ? str : "<null>");
|
||
JS_FreeCString (s->ctx, str);
|
||
} break;
|
||
case TOK_STRING: {
|
||
const char *str;
|
||
/* XXX: quote the string */
|
||
str = JS_ToCString (s->ctx, token->u.str.str);
|
||
printf ("string: '%s'\n", str);
|
||
JS_FreeCString (s->ctx, str);
|
||
} break;
|
||
case TOK_TEMPLATE: {
|
||
const char *str;
|
||
str = JS_ToCString (s->ctx, token->u.str.str);
|
||
printf ("template: `%s`\n", str);
|
||
JS_FreeCString (s->ctx, str);
|
||
} break;
|
||
case TOK_REGEXP: {
|
||
const char *str, *str2;
|
||
str = JS_ToCString (s->ctx, token->u.regexp.body);
|
||
str2 = JS_ToCString (s->ctx, token->u.regexp.flags);
|
||
printf ("regexp: '%s' '%s'\n", str, str2);
|
||
JS_FreeCString (s->ctx, str);
|
||
JS_FreeCString (s->ctx, str2);
|
||
} break;
|
||
case TOK_EOF:
|
||
printf ("eof\n");
|
||
break;
|
||
default:
|
||
if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) {
|
||
goto dump_ident;
|
||
} else if (s->token.val >= 256) {
|
||
printf ("token: %d\n", token->val);
|
||
} else {
|
||
printf ("token: '%c'\n", token->val);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* return the zero based line and column number in the source. */
|
||
/* Note: we no longer support '\r' as line terminator */
|
||
int get_line_col (int *pcol_num, const uint8_t *buf, size_t len) {
|
||
int line_num, col_num, c;
|
||
size_t i;
|
||
|
||
line_num = 0;
|
||
col_num = 0;
|
||
for (i = 0; i < len; i++) {
|
||
c = buf[i];
|
||
if (c == '\n') {
|
||
line_num++;
|
||
col_num = 0;
|
||
} else if (c < 0x80 || c >= 0xc0) {
|
||
col_num++;
|
||
}
|
||
}
|
||
*pcol_num = col_num;
|
||
return line_num;
|
||
}
|
||
|
||
int get_line_col_cached (GetLineColCache *s, int *pcol_num, const uint8_t *ptr) {
|
||
int line_num, col_num;
|
||
if (ptr >= s->ptr) {
|
||
line_num = get_line_col (&col_num, s->ptr, ptr - s->ptr);
|
||
if (line_num == 0) {
|
||
s->col_num += col_num;
|
||
} else {
|
||
s->line_num += line_num;
|
||
s->col_num = col_num;
|
||
}
|
||
} else {
|
||
line_num = get_line_col (&col_num, ptr, s->ptr - ptr);
|
||
if (line_num == 0) {
|
||
s->col_num -= col_num;
|
||
} else {
|
||
const uint8_t *p;
|
||
s->line_num -= line_num;
|
||
/* find the absolute column position */
|
||
col_num = 0;
|
||
for (p = ptr - 1; p >= s->buf_start; p--) {
|
||
if (*p == '\n') {
|
||
break;
|
||
} else if (*p < 0x80 || *p >= 0xc0) {
|
||
col_num++;
|
||
}
|
||
}
|
||
s->col_num = col_num;
|
||
}
|
||
}
|
||
s->ptr = ptr;
|
||
*pcol_num = s->col_num;
|
||
return s->line_num;
|
||
}
|
||
|
||
/* 'ptr' is the position of the error in the source */
|
||
static int js_parse_error_v (JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) {
|
||
JSContext *ctx = s->ctx;
|
||
int line_num, col_num;
|
||
line_num = get_line_col (&col_num, s->buf_start, ptr - s->buf_start);
|
||
JS_ThrowError2 (ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE);
|
||
build_backtrace (ctx, ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0);
|
||
return -1;
|
||
}
|
||
|
||
static __attribute__ ((format (printf, 3, 4))) int
|
||
js_parse_error_pos (JSParseState *s, const uint8_t *ptr, const char *fmt, ...) {
|
||
va_list ap;
|
||
int ret;
|
||
|
||
va_start (ap, fmt);
|
||
ret = js_parse_error_v (s, ptr, fmt, ap);
|
||
va_end (ap);
|
||
return ret;
|
||
}
|
||
|
||
static __attribute__ ((format (printf, 2, 3))) int
|
||
js_parse_error (JSParseState *s, const char *fmt, ...) {
|
||
va_list ap;
|
||
int ret;
|
||
|
||
va_start (ap, fmt);
|
||
ret = js_parse_error_v (s, s->token.ptr, fmt, ap);
|
||
va_end (ap);
|
||
return ret;
|
||
}
|
||
|
||
static int js_parse_expect (JSParseState *s, int tok) {
|
||
if (s->token.val != tok) {
|
||
/* XXX: dump token correctly in all cases */
|
||
return js_parse_error (s, "expecting '%c'", tok);
|
||
}
|
||
return next_token (s);
|
||
}
|
||
|
||
static int js_parse_expect_semi (JSParseState *s) {
|
||
if (s->token.val != ';') {
|
||
/* automatic insertion of ';' */
|
||
if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf
|
||
|| s->token.val == TOK_ELSE) {
|
||
return 0;
|
||
}
|
||
return js_parse_error (s, "expecting '%c'", ';');
|
||
}
|
||
return next_token (s);
|
||
}
|
||
|
||
static int js_parse_error_reserved_identifier (JSParseState *s) {
|
||
char buf1[KEY_GET_STR_BUF_SIZE];
|
||
return js_parse_error (
|
||
s, "'%s' is a reserved identifier", JS_KeyGetStr (s->ctx, buf1, sizeof (buf1), s->token.u.ident.str));
|
||
}
|
||
|
||
static __exception int js_parse_template_part (JSParseState *s,
|
||
const uint8_t *p) {
|
||
uint32_t c;
|
||
PPretext *b;
|
||
JSValue str;
|
||
|
||
/* p points to the first byte of the template part */
|
||
b = ppretext_init (32);
|
||
if (!b) goto fail;
|
||
for (;;) {
|
||
if (p >= s->buf_end) goto unexpected_eof;
|
||
c = *p++;
|
||
if (c == '`') {
|
||
/* template end part */
|
||
break;
|
||
}
|
||
if (c == '$' && *p == '{') {
|
||
/* template start or middle part */
|
||
p++;
|
||
break;
|
||
}
|
||
if (c == '\\') {
|
||
b = ppretext_putc (b, c);
|
||
if (!b) goto fail;
|
||
if (p >= s->buf_end) goto unexpected_eof;
|
||
c = *p++;
|
||
}
|
||
/* newline sequences are normalized as single '\n' bytes */
|
||
if (c == '\r') {
|
||
if (*p == '\n') p++;
|
||
c = '\n';
|
||
}
|
||
if (c >= 0x80) {
|
||
const uint8_t *p_next;
|
||
c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) {
|
||
js_parse_error_pos (s, p - 1, "invalid UTF-8 sequence");
|
||
goto fail;
|
||
}
|
||
p = p_next;
|
||
}
|
||
b = ppretext_putc (b, c);
|
||
if (!b) goto fail;
|
||
}
|
||
str = ppretext_end (s->ctx, b);
|
||
if (JS_IsException (str)) return -1;
|
||
s->token.val = TOK_TEMPLATE;
|
||
s->token.u.str.sep = c;
|
||
s->token.u.str.str = str;
|
||
s->buf_ptr = p;
|
||
return 0;
|
||
|
||
unexpected_eof:
|
||
js_parse_error (s, "unexpected end of string");
|
||
fail:
|
||
ppretext_free (b);
|
||
return -1;
|
||
}
|
||
|
||
static __exception int js_parse_string (JSParseState *s, int sep, BOOL do_throw, const uint8_t *p, JSToken *token, const uint8_t **pp) {
|
||
int ret;
|
||
uint32_t c;
|
||
PPretext *b;
|
||
const uint8_t *p_escape;
|
||
JSValue str;
|
||
|
||
/* string */
|
||
b = ppretext_init (32);
|
||
if (!b) goto fail;
|
||
for (;;) {
|
||
if (p >= s->buf_end) goto invalid_char;
|
||
c = *p;
|
||
if (c < 0x20) {
|
||
if (sep == '`') {
|
||
if (c == '\r') {
|
||
if (p[1] == '\n') p++;
|
||
c = '\n';
|
||
}
|
||
/* do not update s->line_num */
|
||
} else if (c == '\n' || c == '\r')
|
||
goto invalid_char;
|
||
}
|
||
p++;
|
||
if (c == sep) break;
|
||
if (c == '$' && *p == '{' && sep == '`') {
|
||
/* template start or middle part */
|
||
p++;
|
||
break;
|
||
}
|
||
if (c == '\\') {
|
||
p_escape = p - 1;
|
||
c = *p;
|
||
/* XXX: need a specific JSON case to avoid
|
||
accepting invalid escapes */
|
||
switch (c) {
|
||
case '\0':
|
||
if (p >= s->buf_end) goto invalid_char;
|
||
p++;
|
||
break;
|
||
case '\'':
|
||
case '\"':
|
||
case '\\':
|
||
p++;
|
||
break;
|
||
case '\r': /* accept DOS and MAC newline sequences */
|
||
if (p[1] == '\n') { p++; }
|
||
/* fall thru */
|
||
case '\n':
|
||
/* ignore escaped newline sequence */
|
||
p++;
|
||
continue;
|
||
default:
|
||
if (c >= '0' && c <= '9') {
|
||
if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) {
|
||
p++;
|
||
c = '\0';
|
||
} else {
|
||
if (c >= '8' || sep == '`') {
|
||
/* Note: according to ES2021, \8 and \9 are not
|
||
accepted in strict mode or in templates. */
|
||
goto invalid_escape;
|
||
} else {
|
||
if (do_throw)
|
||
js_parse_error_pos (
|
||
s, p_escape, "octal escape sequences are not allowed in strict mode");
|
||
}
|
||
goto fail;
|
||
}
|
||
} else if (c >= 0x80) {
|
||
const uint8_t *p_next;
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) { goto invalid_utf8; }
|
||
p = p_next;
|
||
/* LS or PS are skipped */
|
||
if (c == CP_LS || c == CP_PS) continue;
|
||
} else {
|
||
ret = lre_parse_escape (&p, TRUE);
|
||
if (ret == -1) {
|
||
invalid_escape:
|
||
if (do_throw)
|
||
js_parse_error_pos (
|
||
s, p_escape, "malformed escape sequence in string literal");
|
||
goto fail;
|
||
} else if (ret < 0) {
|
||
/* ignore the '\' (could output a warning) */
|
||
p++;
|
||
} else {
|
||
c = ret;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
} else if (c >= 0x80) {
|
||
const uint8_t *p_next;
|
||
c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) goto invalid_utf8;
|
||
p = p_next;
|
||
}
|
||
b = ppretext_putc (b, c);
|
||
if (!b) goto fail;
|
||
}
|
||
str = ppretext_end (s->ctx, b);
|
||
if (JS_IsException (str)) return -1;
|
||
token->val = TOK_STRING;
|
||
token->u.str.sep = c;
|
||
token->u.str.str = str;
|
||
*pp = p;
|
||
return 0;
|
||
|
||
invalid_utf8:
|
||
if (do_throw) js_parse_error (s, "invalid UTF-8 sequence");
|
||
goto fail;
|
||
invalid_char:
|
||
if (do_throw) js_parse_error (s, "unexpected end of string");
|
||
fail:
|
||
ppretext_free (b);
|
||
return -1;
|
||
}
|
||
|
||
static inline BOOL token_is_pseudo_keyword (JSParseState *s, JSValue key) {
|
||
return s->token.val == TOK_IDENT && js_key_equal (s->token.u.ident.str, key)
|
||
&& !s->token.u.ident.has_escape;
|
||
}
|
||
|
||
static __exception int js_parse_regexp (JSParseState *s) {
|
||
const uint8_t *p;
|
||
BOOL in_class;
|
||
PPretext *b = NULL;
|
||
PPretext *b2 = NULL;
|
||
uint32_t c;
|
||
JSValue body_str, flags_str;
|
||
|
||
p = s->buf_ptr;
|
||
p++;
|
||
in_class = FALSE;
|
||
b = ppretext_init (32);
|
||
if (!b) return -1;
|
||
b2 = ppretext_init (1);
|
||
if (!b2) goto fail;
|
||
for (;;) {
|
||
if (p >= s->buf_end) {
|
||
eof_error:
|
||
js_parse_error (s, "unexpected end of regexp");
|
||
goto fail;
|
||
}
|
||
c = *p++;
|
||
if (c == '\n' || c == '\r') {
|
||
goto eol_error;
|
||
} else if (c == '/') {
|
||
if (!in_class) break;
|
||
} else if (c == '[') {
|
||
in_class = TRUE;
|
||
} else if (c == ']') {
|
||
/* XXX: incorrect as the first character in a class */
|
||
in_class = FALSE;
|
||
} else if (c == '\\') {
|
||
b = ppretext_putc (b, c);
|
||
if (!b) goto fail;
|
||
c = *p++;
|
||
if (c == '\n' || c == '\r')
|
||
goto eol_error;
|
||
else if (c == '\0' && p >= s->buf_end)
|
||
goto eof_error;
|
||
else if (c >= 0x80) {
|
||
const uint8_t *p_next;
|
||
c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) { goto invalid_utf8; }
|
||
p = p_next;
|
||
if (c == CP_LS || c == CP_PS) goto eol_error;
|
||
}
|
||
} else if (c >= 0x80) {
|
||
const uint8_t *p_next;
|
||
c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) {
|
||
invalid_utf8:
|
||
js_parse_error_pos (s, p - 1, "invalid UTF-8 sequence");
|
||
goto fail;
|
||
}
|
||
/* LS or PS are considered as line terminator */
|
||
if (c == CP_LS || c == CP_PS) {
|
||
eol_error:
|
||
js_parse_error_pos (s, p - 1, "unexpected line terminator in regexp");
|
||
goto fail;
|
||
}
|
||
p = p_next;
|
||
}
|
||
b = ppretext_putc (b, c);
|
||
if (!b) goto fail;
|
||
}
|
||
|
||
/* flags */
|
||
for (;;) {
|
||
const uint8_t *p_next = p;
|
||
c = *p_next++;
|
||
if (c >= 0x80) {
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) {
|
||
p++;
|
||
goto invalid_utf8;
|
||
}
|
||
}
|
||
if (!lre_js_is_ident_next (c)) break;
|
||
b2 = ppretext_putc (b2, c);
|
||
if (!b2) goto fail;
|
||
p = p_next;
|
||
}
|
||
|
||
body_str = ppretext_end (s->ctx, b);
|
||
flags_str = ppretext_end (s->ctx, b2);
|
||
if (JS_IsException (body_str) || JS_IsException (flags_str)) {
|
||
return -1;
|
||
}
|
||
s->token.val = TOK_REGEXP;
|
||
s->token.u.regexp.body = body_str;
|
||
s->token.u.regexp.flags = flags_str;
|
||
s->buf_ptr = p;
|
||
return 0;
|
||
fail:
|
||
ppretext_free (b);
|
||
ppretext_free (b2);
|
||
return -1;
|
||
}
|
||
|
||
static __exception int ident_realloc (JSContext *ctx, char **pbuf, size_t *psize, char *static_buf) {
|
||
(void)ctx; /* unused - uses system allocator */
|
||
char *buf, *new_buf;
|
||
size_t size, new_size;
|
||
|
||
buf = *pbuf;
|
||
size = *psize;
|
||
if (size >= (SIZE_MAX / 3) * 2)
|
||
new_size = SIZE_MAX;
|
||
else
|
||
new_size = size + (size >> 1);
|
||
if (buf == static_buf) {
|
||
new_buf = pjs_malloc (new_size);
|
||
if (!new_buf) return -1;
|
||
memcpy (new_buf, buf, size);
|
||
} else {
|
||
new_buf = pjs_realloc (buf, new_size);
|
||
if (!new_buf) return -1;
|
||
}
|
||
*pbuf = new_buf;
|
||
*psize = new_size;
|
||
return 0;
|
||
}
|
||
|
||
/* keyword lookup table */
|
||
typedef struct {
|
||
const char *name;
|
||
int token;
|
||
BOOL is_strict_reserved; /* TRUE if reserved only in strict mode */
|
||
} JSKeywordEntry;
|
||
|
||
static const JSKeywordEntry js_keywords[] = {
|
||
{ "null", TOK_NULL, FALSE },
|
||
{ "false", TOK_FALSE, FALSE },
|
||
{ "true", TOK_TRUE, FALSE },
|
||
{ "if", TOK_IF, FALSE },
|
||
{ "else", TOK_ELSE, FALSE },
|
||
{ "return", TOK_RETURN, FALSE },
|
||
{ "go", TOK_GO, FALSE },
|
||
{ "var", TOK_VAR, FALSE },
|
||
{ "def", TOK_DEF, FALSE },
|
||
{ "this", TOK_THIS, FALSE },
|
||
{ "delete", TOK_DELETE, FALSE },
|
||
{ "in", TOK_IN, FALSE },
|
||
{ "do", TOK_DO, FALSE },
|
||
{ "while", TOK_WHILE, FALSE },
|
||
{ "for", TOK_FOR, FALSE },
|
||
{ "break", TOK_BREAK, FALSE },
|
||
{ "continue", TOK_CONTINUE, FALSE },
|
||
{ "disrupt", TOK_DISRUPT, FALSE },
|
||
{ "disruption", TOK_DISRUPTION, FALSE },
|
||
{ "function", TOK_FUNCTION, FALSE },
|
||
{ "debugger", TOK_DEBUGGER, FALSE },
|
||
{ "with", TOK_WITH, FALSE },
|
||
{ "class", TOK_CLASS, FALSE },
|
||
{ "const", TOK_CONST, FALSE },
|
||
{ "enum", TOK_ENUM, FALSE },
|
||
{ "export", TOK_EXPORT, FALSE },
|
||
{ "extends", TOK_EXTENDS, FALSE },
|
||
{ "import", TOK_IMPORT, FALSE },
|
||
{ "super", TOK_SUPER, FALSE },
|
||
/* strict mode future reserved words */
|
||
{ "implements", TOK_IMPLEMENTS, TRUE },
|
||
{ "interface", TOK_INTERFACE, TRUE },
|
||
{ "let", TOK_LET, TRUE },
|
||
{ "private", TOK_PRIVATE, TRUE },
|
||
{ "protected", TOK_PROTECTED, TRUE },
|
||
{ "public", TOK_PUBLIC, TRUE },
|
||
{ "static", TOK_STATIC, TRUE },
|
||
{ "yield", TOK_YIELD, TRUE },
|
||
{ "await", TOK_AWAIT, TRUE },
|
||
};
|
||
|
||
#define JS_KEYWORD_COUNT (sizeof (js_keywords) / sizeof (js_keywords[0]))
|
||
|
||
/* lookup keyword by JSValue string, return token or -1 if not found */
|
||
static int js_keyword_lookup (JSValue str, BOOL *is_strict_reserved) {
|
||
char buf[16];
|
||
const char *cstr;
|
||
size_t len;
|
||
|
||
if (MIST_IsImmediateASCII (str)) {
|
||
len = MIST_GetImmediateASCIILen (str);
|
||
if (len >= sizeof (buf)) return -1;
|
||
for (size_t i = 0; i < len; i++) {
|
||
buf[i] = MIST_GetImmediateASCIIChar (str, i);
|
||
}
|
||
buf[len] = '\0';
|
||
cstr = buf;
|
||
} else if (JS_IsPtr (str)) {
|
||
/* Handle heap strings (JSText) - keywords like "function" are 8 chars */
|
||
JSText *text = (JSText *)JS_VALUE_GET_PTR (str);
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: JS_IsPtr branch, ptr=%p, hdr_type=%d\n", (void*)text, objhdr_type(text->hdr));
|
||
#endif
|
||
if (objhdr_type (text->hdr) != OBJ_TEXT) return -1;
|
||
len = JSText_len (text);
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: text len=%zu\n", len);
|
||
#endif
|
||
if (len >= sizeof (buf)) return -1;
|
||
for (size_t i = 0; i < len; i++) {
|
||
uint32_t c = string_get (text, i);
|
||
if (c >= 128) return -1; /* Keywords are ASCII only */
|
||
buf[i] = (char)c;
|
||
}
|
||
buf[len] = '\0';
|
||
cstr = buf;
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: extracted string='%s' len=%zu\n", cstr, len);
|
||
#endif
|
||
} else {
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: unknown str type, tag=%llx\n", (unsigned long long)(str & 0xFF));
|
||
#endif
|
||
return -1;
|
||
}
|
||
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: looking up '%s' in %zu keywords\n", cstr, (size_t)JS_KEYWORD_COUNT);
|
||
#endif
|
||
for (size_t i = 0; i < JS_KEYWORD_COUNT; i++) {
|
||
if (strcmp (cstr, js_keywords[i].name) == 0) {
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: matched keyword '%s' -> token %d\n", js_keywords[i].name, js_keywords[i].token);
|
||
#endif
|
||
if (is_strict_reserved)
|
||
*is_strict_reserved = js_keywords[i].is_strict_reserved;
|
||
return js_keywords[i].token;
|
||
}
|
||
}
|
||
#ifdef PRINT_LEXER
|
||
fprintf(stderr, "DEBUG: no keyword match for '%s'\n", cstr);
|
||
#endif
|
||
return -1;
|
||
}
|
||
|
||
/* convert a TOK_IDENT to a keyword when needed */
|
||
static void update_token_ident (JSParseState *s) {
|
||
BOOL is_strict_reserved = FALSE;
|
||
int token = js_keyword_lookup (s->token.u.ident.str, &is_strict_reserved);
|
||
|
||
if (token != -1) {
|
||
if (s->token.u.ident.has_escape) {
|
||
/* identifiers with escape sequences stay identifiers */
|
||
s->token.u.ident.is_reserved = TRUE;
|
||
s->token.val = TOK_IDENT;
|
||
} else {
|
||
s->token.val = token;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* if the current token is an identifier or keyword, reparse it
|
||
according to the current function type */
|
||
static void reparse_ident_token (JSParseState *s) {
|
||
if (s->token.val == TOK_IDENT
|
||
|| (s->token.val >= TOK_FIRST_KEYWORD
|
||
&& s->token.val <= TOK_LAST_KEYWORD)) {
|
||
s->token.val = TOK_IDENT;
|
||
s->token.u.ident.is_reserved = FALSE;
|
||
update_token_ident (s);
|
||
}
|
||
}
|
||
|
||
/* 'c' is the first character. Return JS_NULL in case of error */
|
||
static JSValue parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, int c) {
|
||
const uint8_t *p, *p1;
|
||
char ident_buf[128], *buf;
|
||
size_t ident_size, ident_pos;
|
||
JSValue str;
|
||
|
||
p = *pp;
|
||
buf = ident_buf;
|
||
ident_size = sizeof (ident_buf);
|
||
ident_pos = 0;
|
||
for (;;) {
|
||
p1 = p;
|
||
|
||
if (c < 128) {
|
||
buf[ident_pos++] = c;
|
||
} else {
|
||
ident_pos += unicode_to_utf8 ((uint8_t *)buf + ident_pos, c);
|
||
}
|
||
c = *p1++;
|
||
if (c == '\\' && *p1 == 'u') {
|
||
c = lre_parse_escape (&p1, TRUE);
|
||
*pident_has_escape = TRUE;
|
||
} else if (c >= 128) {
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1);
|
||
}
|
||
if (!lre_js_is_ident_next (c) && c != '?' && c != '!') break;
|
||
p = p1;
|
||
if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) {
|
||
if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) {
|
||
str = JS_NULL;
|
||
goto done;
|
||
}
|
||
}
|
||
}
|
||
/* Create interned JSValue key */
|
||
str = js_key_new_len (s->ctx, buf, ident_pos);
|
||
done:
|
||
if (unlikely (buf != ident_buf)) pjs_free (buf);
|
||
*pp = p;
|
||
return str;
|
||
}
|
||
|
||
static __exception int next_token (JSParseState *s) {
|
||
const uint8_t *p;
|
||
int c;
|
||
BOOL ident_has_escape;
|
||
JSValue ident_str;
|
||
|
||
if (js_check_stack_overflow (s->ctx, 0)) {
|
||
return js_parse_error (s, "stack overflow");
|
||
}
|
||
|
||
free_token (s, &s->token);
|
||
|
||
p = s->last_ptr = s->buf_ptr;
|
||
s->got_lf = FALSE;
|
||
redo:
|
||
s->token.ptr = p;
|
||
c = *p;
|
||
switch (c) {
|
||
case 0:
|
||
if (p >= s->buf_end) {
|
||
s->token.val = TOK_EOF;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '`':
|
||
if (js_parse_template_part (s, p + 1)) goto fail;
|
||
p = s->buf_ptr;
|
||
break;
|
||
case '\'':
|
||
case '\"':
|
||
if (js_parse_string (s, c, TRUE, p + 1, &s->token, &p)) goto fail;
|
||
break;
|
||
case '\r': /* accept DOS and MAC newline sequences */
|
||
if (p[1] == '\n') { p++; }
|
||
/* fall thru */
|
||
case '\n':
|
||
p++;
|
||
line_terminator:
|
||
s->got_lf = TRUE;
|
||
goto redo;
|
||
case '\f':
|
||
case '\v':
|
||
case ' ':
|
||
case '\t':
|
||
p++;
|
||
goto redo;
|
||
case '/':
|
||
if (p[1] == '*') {
|
||
/* comment */
|
||
p += 2;
|
||
for (;;) {
|
||
if (*p == '\0' && p >= s->buf_end) {
|
||
js_parse_error (s, "unexpected end of comment");
|
||
goto fail;
|
||
}
|
||
if (p[0] == '*' && p[1] == '/') {
|
||
p += 2;
|
||
break;
|
||
}
|
||
if (*p == '\n' || *p == '\r') {
|
||
s->got_lf = TRUE; /* considered as LF for ASI */
|
||
p++;
|
||
} else if (*p >= 0x80) {
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p);
|
||
if (c == CP_LS || c == CP_PS) {
|
||
s->got_lf = TRUE; /* considered as LF for ASI */
|
||
} else if (c == -1) {
|
||
p++; /* skip invalid UTF-8 */
|
||
}
|
||
} else {
|
||
p++;
|
||
}
|
||
}
|
||
goto redo;
|
||
} else if (p[1] == '/') {
|
||
/* line comment */
|
||
p += 2;
|
||
for (;;) {
|
||
if (*p == '\0' && p >= s->buf_end) break;
|
||
if (*p == '\r' || *p == '\n') break;
|
||
if (*p >= 0x80) {
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p);
|
||
/* LS or PS are considered as line terminator */
|
||
if (c == CP_LS || c == CP_PS) {
|
||
break;
|
||
} else if (c == -1) {
|
||
p++; /* skip invalid UTF-8 */
|
||
}
|
||
} else {
|
||
p++;
|
||
}
|
||
}
|
||
goto redo;
|
||
} else if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_DIV_ASSIGN;
|
||
} else {
|
||
p++;
|
||
s->token.val = c;
|
||
}
|
||
break;
|
||
case '\\':
|
||
if (p[1] == 'u') {
|
||
const uint8_t *p1 = p + 1;
|
||
int c1 = lre_parse_escape (&p1, TRUE);
|
||
if (c1 >= 0 && lre_js_is_ident_first (c1)) {
|
||
c = c1;
|
||
p = p1;
|
||
ident_has_escape = TRUE;
|
||
goto has_ident;
|
||
} else {
|
||
/* XXX: syntax error? */
|
||
}
|
||
}
|
||
goto def_token;
|
||
case 'a':
|
||
case 'b':
|
||
case 'c':
|
||
case 'd':
|
||
case 'e':
|
||
case 'f':
|
||
case 'g':
|
||
case 'h':
|
||
case 'i':
|
||
case 'j':
|
||
case 'k':
|
||
case 'l':
|
||
case 'm':
|
||
case 'n':
|
||
case 'o':
|
||
case 'p':
|
||
case 'q':
|
||
case 'r':
|
||
case 's':
|
||
case 't':
|
||
case 'u':
|
||
case 'v':
|
||
case 'w':
|
||
case 'x':
|
||
case 'y':
|
||
case 'z':
|
||
case 'A':
|
||
case 'B':
|
||
case 'C':
|
||
case 'D':
|
||
case 'E':
|
||
case 'F':
|
||
case 'G':
|
||
case 'H':
|
||
case 'I':
|
||
case 'J':
|
||
case 'K':
|
||
case 'L':
|
||
case 'M':
|
||
case 'N':
|
||
case 'O':
|
||
case 'P':
|
||
case 'Q':
|
||
case 'R':
|
||
case 'S':
|
||
case 'T':
|
||
case 'U':
|
||
case 'V':
|
||
case 'W':
|
||
case 'X':
|
||
case 'Y':
|
||
case 'Z':
|
||
case '_':
|
||
case '$':
|
||
/* identifier */
|
||
p++;
|
||
ident_has_escape = FALSE;
|
||
has_ident:
|
||
ident_str = parse_ident (s, &p, &ident_has_escape, c);
|
||
if (JS_IsNull (ident_str)) goto fail;
|
||
s->token.u.ident.str = ident_str;
|
||
s->token.u.ident.has_escape = ident_has_escape;
|
||
s->token.u.ident.is_reserved = FALSE;
|
||
s->token.val = TOK_IDENT;
|
||
update_token_ident (s);
|
||
break;
|
||
case '.':
|
||
if (p[1] == '.' && p[2] == '.') {
|
||
js_parse_error (s, "ellipsis (...) is not supported");
|
||
goto fail;
|
||
}
|
||
if (p[1] >= '0' && p[1] <= '9') {
|
||
goto parse_number;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '0':
|
||
/* in strict mode, octal literals are not accepted */
|
||
if (is_digit (p[1])) {
|
||
js_parse_error (s, "octal literals are not allowed");
|
||
goto fail;
|
||
}
|
||
goto parse_number;
|
||
case '1':
|
||
case '2':
|
||
case '3':
|
||
case '4':
|
||
case '5':
|
||
case '6':
|
||
case '7':
|
||
case '8':
|
||
case '9':
|
||
/* number */
|
||
parse_number: {
|
||
JSValue ret;
|
||
const uint8_t *p1;
|
||
int flags;
|
||
flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL
|
||
| ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX;
|
||
ret = js_atof (s->ctx, (const char *)p, (const char **)&p, 0, flags);
|
||
if (JS_IsException (ret)) goto fail;
|
||
/* reject number immediately followed by identifier */
|
||
if (JS_IsNull (ret)
|
||
|| lre_js_is_ident_next (
|
||
unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1))) {
|
||
js_parse_error (s, "invalid number literal");
|
||
goto fail;
|
||
}
|
||
s->token.val = TOK_NUMBER;
|
||
s->token.u.num.val = ret;
|
||
} break;
|
||
case '*':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_MUL_ASSIGN;
|
||
} else if (p[1] == '*') {
|
||
if (p[2] == '=') {
|
||
p += 3;
|
||
s->token.val = TOK_POW_ASSIGN;
|
||
} else {
|
||
p += 2;
|
||
s->token.val = TOK_POW;
|
||
}
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '%':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_MOD_ASSIGN;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '+':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_PLUS_ASSIGN;
|
||
} else if (p[1] == '+') {
|
||
p += 2;
|
||
s->token.val = TOK_INC;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '-':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_MINUS_ASSIGN;
|
||
} else if (p[1] == '-') {
|
||
p += 2;
|
||
s->token.val = TOK_DEC;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '<':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_LTE;
|
||
} else if (p[1] == '<') {
|
||
if (p[2] == '=') {
|
||
p += 3;
|
||
s->token.val = TOK_SHL_ASSIGN;
|
||
} else {
|
||
p += 2;
|
||
s->token.val = TOK_SHL;
|
||
}
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '>':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_GTE;
|
||
} else if (p[1] == '>') {
|
||
if (p[2] == '>') {
|
||
if (p[3] == '=') {
|
||
p += 4;
|
||
s->token.val = TOK_SHR_ASSIGN;
|
||
} else {
|
||
p += 3;
|
||
s->token.val = TOK_SHR;
|
||
}
|
||
} else if (p[2] == '=') {
|
||
p += 3;
|
||
s->token.val = TOK_SAR_ASSIGN;
|
||
} else {
|
||
p += 2;
|
||
s->token.val = TOK_SAR;
|
||
}
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '=':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_STRICT_EQ;
|
||
} else if (p[1] == '>') {
|
||
p += 2;
|
||
s->token.val = TOK_ARROW;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '!':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_STRICT_NEQ;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '&':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_AND_ASSIGN;
|
||
} else if (p[1] == '&') {
|
||
if (p[2] == '=') {
|
||
p += 3;
|
||
s->token.val = TOK_LAND_ASSIGN;
|
||
} else {
|
||
p += 2;
|
||
s->token.val = TOK_LAND;
|
||
}
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '^':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_XOR_ASSIGN;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '|':
|
||
if (p[1] == '=') {
|
||
p += 2;
|
||
s->token.val = TOK_OR_ASSIGN;
|
||
} else if (p[1] == '|') {
|
||
if (p[2] == '=') {
|
||
p += 3;
|
||
s->token.val = TOK_LOR_ASSIGN;
|
||
} else {
|
||
p += 2;
|
||
s->token.val = TOK_LOR;
|
||
}
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '?':
|
||
goto def_token;
|
||
default:
|
||
if (c >= 128) {
|
||
/* unicode value */
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p);
|
||
switch (c) {
|
||
case CP_PS:
|
||
case CP_LS:
|
||
/* XXX: should avoid incrementing line_number, but
|
||
needed to handle HTML comments */
|
||
goto line_terminator;
|
||
default:
|
||
if (lre_is_space (c)) {
|
||
goto redo;
|
||
} else if (lre_js_is_ident_first (c)) {
|
||
ident_has_escape = FALSE;
|
||
goto has_ident;
|
||
} else {
|
||
js_parse_error (s, "unexpected character");
|
||
goto fail;
|
||
}
|
||
}
|
||
}
|
||
def_token:
|
||
s->token.val = c;
|
||
p++;
|
||
break;
|
||
}
|
||
s->buf_ptr = p;
|
||
|
||
// dump_token(s, &s->token);
|
||
return 0;
|
||
|
||
fail:
|
||
s->token.val = TOK_ERROR;
|
||
return -1;
|
||
}
|
||
|
||
/* 'c' is the first character. Return JS_NULL in case of error */
|
||
/* XXX: accept unicode identifiers as JSON5 ? */
|
||
static JSValue json_parse_ident (JSParseState *s, const uint8_t **pp, int c) {
|
||
const uint8_t *p;
|
||
char ident_buf[128], *buf;
|
||
size_t ident_size, ident_pos;
|
||
JSValue str;
|
||
|
||
p = *pp;
|
||
buf = ident_buf;
|
||
ident_size = sizeof (ident_buf);
|
||
ident_pos = 0;
|
||
for (;;) {
|
||
buf[ident_pos++] = c;
|
||
c = *p;
|
||
if (c >= 128 || !lre_is_id_continue_byte (c)) break;
|
||
p++;
|
||
if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) {
|
||
if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) {
|
||
str = JS_NULL;
|
||
goto done;
|
||
}
|
||
}
|
||
}
|
||
/* Create interned JSValue key */
|
||
str = js_key_new_len (s->ctx, buf, ident_pos);
|
||
done:
|
||
if (unlikely (buf != ident_buf)) pjs_free (buf);
|
||
*pp = p;
|
||
return str;
|
||
}
|
||
|
||
static int json_parse_string (JSParseState *s, const uint8_t **pp, int sep) {
|
||
const uint8_t *p, *p_next;
|
||
int i;
|
||
uint32_t c;
|
||
PPretext *b;
|
||
|
||
b = ppretext_init (32);
|
||
if (!b) goto fail;
|
||
|
||
p = *pp;
|
||
for (;;) {
|
||
if (p >= s->buf_end) { goto end_of_input; }
|
||
c = *p++;
|
||
if (c == sep) break;
|
||
if (c < 0x20) {
|
||
js_parse_error_pos (s, p - 1, "Bad control character in string literal");
|
||
goto fail;
|
||
}
|
||
if (c == '\\') {
|
||
c = *p++;
|
||
switch (c) {
|
||
case 'b':
|
||
c = '\b';
|
||
break;
|
||
case 'f':
|
||
c = '\f';
|
||
break;
|
||
case 'n':
|
||
c = '\n';
|
||
break;
|
||
case 'r':
|
||
c = '\r';
|
||
break;
|
||
case 't':
|
||
c = '\t';
|
||
break;
|
||
case '\\':
|
||
break;
|
||
case '/':
|
||
break;
|
||
case 'u':
|
||
c = 0;
|
||
for (i = 0; i < 4; i++) {
|
||
int h = from_hex (*p++);
|
||
if (h < 0) {
|
||
js_parse_error_pos (s, p - 1, "Bad Unicode escape");
|
||
goto fail;
|
||
}
|
||
c = (c << 4) | h;
|
||
}
|
||
break;
|
||
case '\n':
|
||
if (s->ext_json) continue;
|
||
goto bad_escape;
|
||
case 'v':
|
||
if (s->ext_json) {
|
||
c = '\v';
|
||
break;
|
||
}
|
||
goto bad_escape;
|
||
default:
|
||
if (c == sep) break;
|
||
if (p > s->buf_end) goto end_of_input;
|
||
bad_escape:
|
||
js_parse_error_pos (s, p - 1, "Bad escaped character");
|
||
goto fail;
|
||
}
|
||
} else if (c >= 0x80) {
|
||
c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next);
|
||
if (c > 0x10FFFF) {
|
||
js_parse_error_pos (s, p - 1, "Bad UTF-8 sequence");
|
||
goto fail;
|
||
}
|
||
p = p_next;
|
||
}
|
||
b = ppretext_putc (b, c);
|
||
if (!b) goto fail;
|
||
}
|
||
s->token.val = TOK_STRING;
|
||
s->token.u.str.sep = sep;
|
||
s->token.u.str.str = ppretext_end (s->ctx, b);
|
||
*pp = p;
|
||
return 0;
|
||
|
||
end_of_input:
|
||
js_parse_error (s, "Unexpected end of JSON input");
|
||
fail:
|
||
ppretext_free (b);
|
||
return -1;
|
||
}
|
||
|
||
static int json_parse_number (JSParseState *s, const uint8_t **pp) {
|
||
const uint8_t *p = *pp;
|
||
const uint8_t *p_start = p;
|
||
int radix;
|
||
double d;
|
||
JSATODTempMem atod_mem;
|
||
|
||
if (*p == '+' || *p == '-') p++;
|
||
|
||
if (!is_digit (*p)) {
|
||
if (s->ext_json) {
|
||
if (strstart ((const char *)p, "Infinity", (const char **)&p)) {
|
||
d = 1.0 / 0.0;
|
||
if (*p_start == '-') d = -d;
|
||
goto done;
|
||
} else if (strstart ((const char *)p, "NaN", (const char **)&p)) {
|
||
d = NAN;
|
||
goto done;
|
||
} else if (*p != '.') {
|
||
goto unexpected_token;
|
||
}
|
||
} else {
|
||
goto unexpected_token;
|
||
}
|
||
}
|
||
|
||
if (p[0] == '0') {
|
||
if (s->ext_json) {
|
||
/* also accepts base 16, 8 and 2 prefix for integers */
|
||
radix = 10;
|
||
if (p[1] == 'x' || p[1] == 'X') {
|
||
p += 2;
|
||
radix = 16;
|
||
} else if ((p[1] == 'o' || p[1] == 'O')) {
|
||
p += 2;
|
||
radix = 8;
|
||
} else if ((p[1] == 'b' || p[1] == 'B')) {
|
||
p += 2;
|
||
radix = 2;
|
||
}
|
||
if (radix != 10) {
|
||
/* prefix is present */
|
||
if (to_digit (*p) >= radix) {
|
||
unexpected_token:
|
||
return js_parse_error_pos (s, p, "Unexpected token '%c'", *p);
|
||
}
|
||
d = js_atod ((const char *)p_start, (const char **)&p, 0, JS_ATOD_INT_ONLY | JS_ATOD_ACCEPT_BIN_OCT, &atod_mem);
|
||
goto done;
|
||
}
|
||
}
|
||
if (is_digit (p[1])) return js_parse_error_pos (s, p, "Unexpected number");
|
||
}
|
||
|
||
while (is_digit (*p))
|
||
p++;
|
||
|
||
if (*p == '.') {
|
||
p++;
|
||
if (!is_digit (*p))
|
||
return js_parse_error_pos (s, p, "Unterminated fractional number");
|
||
while (is_digit (*p))
|
||
p++;
|
||
}
|
||
if (*p == 'e' || *p == 'E') {
|
||
p++;
|
||
if (*p == '+' || *p == '-') p++;
|
||
if (!is_digit (*p))
|
||
return js_parse_error_pos (s, p, "Exponent part is missing a number");
|
||
while (is_digit (*p))
|
||
p++;
|
||
}
|
||
d = js_atod ((const char *)p_start, NULL, 10, 0, &atod_mem);
|
||
done:
|
||
s->token.val = TOK_NUMBER;
|
||
s->token.u.num.val = JS_NewFloat64 (s->ctx, d);
|
||
*pp = p;
|
||
return 0;
|
||
}
|
||
|
||
static __exception int json_next_token (JSParseState *s) {
|
||
const uint8_t *p;
|
||
int c;
|
||
JSValue ident_str;
|
||
|
||
if (js_check_stack_overflow (s->ctx, 0)) {
|
||
return js_parse_error (s, "stack overflow");
|
||
}
|
||
|
||
free_token (s, &s->token);
|
||
|
||
p = s->last_ptr = s->buf_ptr;
|
||
redo:
|
||
s->token.ptr = p;
|
||
c = *p;
|
||
switch (c) {
|
||
case 0:
|
||
if (p >= s->buf_end) {
|
||
s->token.val = TOK_EOF;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case '\'':
|
||
if (!s->ext_json) {
|
||
/* JSON does not accept single quoted strings */
|
||
goto def_token;
|
||
}
|
||
/* fall through */
|
||
case '\"':
|
||
p++;
|
||
if (json_parse_string (s, &p, c)) goto fail;
|
||
break;
|
||
case '\r': /* accept DOS and MAC newline sequences */
|
||
if (p[1] == '\n') { p++; }
|
||
/* fall thru */
|
||
case '\n':
|
||
p++;
|
||
goto redo;
|
||
case '\f':
|
||
case '\v':
|
||
if (!s->ext_json) {
|
||
/* JSONWhitespace does not match <VT>, nor <FF> */
|
||
goto def_token;
|
||
}
|
||
/* fall through */
|
||
case ' ':
|
||
case '\t':
|
||
p++;
|
||
goto redo;
|
||
case '/':
|
||
if (!s->ext_json) {
|
||
/* JSON does not accept comments */
|
||
goto def_token;
|
||
}
|
||
if (p[1] == '*') {
|
||
/* comment */
|
||
p += 2;
|
||
for (;;) {
|
||
if (*p == '\0' && p >= s->buf_end) {
|
||
js_parse_error (s, "unexpected end of comment");
|
||
goto fail;
|
||
}
|
||
if (p[0] == '*' && p[1] == '/') {
|
||
p += 2;
|
||
break;
|
||
}
|
||
if (*p >= 0x80) {
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p);
|
||
if (c == -1) { p++; /* skip invalid UTF-8 */ }
|
||
} else {
|
||
p++;
|
||
}
|
||
}
|
||
goto redo;
|
||
} else if (p[1] == '/') {
|
||
/* line comment */
|
||
p += 2;
|
||
for (;;) {
|
||
if (*p == '\0' && p >= s->buf_end) break;
|
||
if (*p == '\r' || *p == '\n') break;
|
||
if (*p >= 0x80) {
|
||
c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p);
|
||
/* LS or PS are considered as line terminator */
|
||
if (c == CP_LS || c == CP_PS) {
|
||
break;
|
||
} else if (c == -1) {
|
||
p++; /* skip invalid UTF-8 */
|
||
}
|
||
} else {
|
||
p++;
|
||
}
|
||
}
|
||
goto redo;
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
break;
|
||
case 'a':
|
||
case 'b':
|
||
case 'c':
|
||
case 'd':
|
||
case 'e':
|
||
case 'f':
|
||
case 'g':
|
||
case 'h':
|
||
case 'i':
|
||
case 'j':
|
||
case 'k':
|
||
case 'l':
|
||
case 'm':
|
||
case 'n':
|
||
case 'o':
|
||
case 'p':
|
||
case 'q':
|
||
case 'r':
|
||
case 's':
|
||
case 't':
|
||
case 'u':
|
||
case 'v':
|
||
case 'w':
|
||
case 'x':
|
||
case 'y':
|
||
case 'z':
|
||
case 'A':
|
||
case 'B':
|
||
case 'C':
|
||
case 'D':
|
||
case 'E':
|
||
case 'F':
|
||
case 'G':
|
||
case 'H':
|
||
case 'I':
|
||
case 'J':
|
||
case 'K':
|
||
case 'L':
|
||
case 'M':
|
||
case 'N':
|
||
case 'O':
|
||
case 'P':
|
||
case 'Q':
|
||
case 'R':
|
||
case 'S':
|
||
case 'T':
|
||
case 'U':
|
||
case 'V':
|
||
case 'W':
|
||
case 'X':
|
||
case 'Y':
|
||
case 'Z':
|
||
case '_':
|
||
case '$':
|
||
p++;
|
||
ident_str = json_parse_ident (s, &p, c);
|
||
if (JS_IsNull (ident_str)) goto fail;
|
||
s->token.u.ident.str = ident_str;
|
||
s->token.u.ident.has_escape = FALSE;
|
||
s->token.u.ident.is_reserved = FALSE;
|
||
s->token.val = TOK_IDENT;
|
||
break;
|
||
case '+':
|
||
if (!s->ext_json) goto def_token;
|
||
goto parse_number;
|
||
case '.':
|
||
if (s->ext_json && is_digit (p[1]))
|
||
goto parse_number;
|
||
else
|
||
goto def_token;
|
||
case '-':
|
||
case '0':
|
||
case '1':
|
||
case '2':
|
||
case '3':
|
||
case '4':
|
||
case '5':
|
||
case '6':
|
||
case '7':
|
||
case '8':
|
||
case '9':
|
||
/* number */
|
||
parse_number:
|
||
if (json_parse_number (s, &p)) goto fail;
|
||
break;
|
||
default:
|
||
if (c >= 128) {
|
||
js_parse_error (s, "unexpected character");
|
||
goto fail;
|
||
}
|
||
def_token:
|
||
s->token.val = c;
|
||
p++;
|
||
break;
|
||
}
|
||
s->buf_ptr = p;
|
||
|
||
// dump_token(s, &s->token);
|
||
return 0;
|
||
|
||
fail:
|
||
s->token.val = TOK_ERROR;
|
||
return -1;
|
||
}
|
||
|
||
static int match_identifier (const uint8_t *p, const char *s) {
|
||
uint32_t c;
|
||
while (*s) {
|
||
if ((uint8_t)*s++ != *p++) return 0;
|
||
}
|
||
c = *p;
|
||
if (c >= 128) c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p);
|
||
return !lre_js_is_ident_next (c);
|
||
}
|
||
|
||
/* simple_next_token() is used to check for the next token in simple cases.
|
||
It is only used for ':' and '=>', 'let' or 'function' look-ahead.
|
||
(*pp) is only set if TOK_IMPORT is returned for JS_DetectModule()
|
||
Whitespace and comments are skipped correctly.
|
||
Then the next token is analyzed, only for specific words.
|
||
Return values:
|
||
- '\n' if !no_line_terminator
|
||
- TOK_ARROW, TOK_IN, TOK_IMPORT, TOK_OF, TOK_EXPORT, TOK_FUNCTION
|
||
- TOK_IDENT is returned for other identifiers and keywords
|
||
- otherwise the next character or unicode codepoint is returned.
|
||
*/
|
||
static int simple_next_token (const uint8_t **pp, BOOL no_line_terminator) {
|
||
const uint8_t *p;
|
||
uint32_t c;
|
||
|
||
/* skip spaces and comments */
|
||
p = *pp;
|
||
for (;;) {
|
||
switch (c = *p++) {
|
||
case '\r':
|
||
case '\n':
|
||
if (no_line_terminator) return '\n';
|
||
continue;
|
||
case ' ':
|
||
case '\t':
|
||
case '\v':
|
||
case '\f':
|
||
continue;
|
||
case '/':
|
||
if (*p == '/') {
|
||
if (no_line_terminator) return '\n';
|
||
while (*p && *p != '\r' && *p != '\n')
|
||
p++;
|
||
continue;
|
||
}
|
||
if (*p == '*') {
|
||
while (*++p) {
|
||
if ((*p == '\r' || *p == '\n') && no_line_terminator) return '\n';
|
||
if (*p == '*' && p[1] == '/') {
|
||
p += 2;
|
||
break;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
break;
|
||
case '=':
|
||
if (*p == '>') return TOK_ARROW;
|
||
break;
|
||
case 'i':
|
||
if (match_identifier (p, "n")) return TOK_IN;
|
||
if (match_identifier (p, "mport")) {
|
||
*pp = p + 5;
|
||
return TOK_IMPORT;
|
||
}
|
||
return TOK_IDENT;
|
||
case 'o':
|
||
if (match_identifier (p, "f")) return TOK_OF;
|
||
return TOK_IDENT;
|
||
case 'e':
|
||
if (match_identifier (p, "xport")) return TOK_EXPORT;
|
||
return TOK_IDENT;
|
||
case 'f':
|
||
if (match_identifier (p, "unction")) return TOK_FUNCTION;
|
||
return TOK_IDENT;
|
||
case '\\':
|
||
if (*p == 'u') {
|
||
if (lre_js_is_ident_first (lre_parse_escape (&p, TRUE)))
|
||
return TOK_IDENT;
|
||
}
|
||
break;
|
||
default:
|
||
if (c >= 128) {
|
||
c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p);
|
||
if (no_line_terminator && (c == CP_PS || c == CP_LS)) return '\n';
|
||
}
|
||
if (lre_is_space (c)) continue;
|
||
if (lre_js_is_ident_first (c)) return TOK_IDENT;
|
||
break;
|
||
}
|
||
return c;
|
||
}
|
||
}
|
||
|
||
static int peek_token (JSParseState *s, BOOL no_line_terminator) {
|
||
const uint8_t *p = s->buf_ptr;
|
||
return simple_next_token (&p, no_line_terminator);
|
||
}
|
||
|
||
static inline int get_prev_opcode (JSFunctionDef *fd) {
|
||
if (fd->last_opcode_pos < 0 || dbuf_error (&fd->byte_code))
|
||
return OP_invalid;
|
||
else
|
||
return fd->byte_code.buf[fd->last_opcode_pos];
|
||
}
|
||
|
||
static BOOL js_is_live_code (JSParseState *s) {
|
||
switch (get_prev_opcode (s->cur_func)) {
|
||
case OP_tail_call:
|
||
case OP_tail_call_method:
|
||
case OP_return:
|
||
case OP_return_undef:
|
||
case OP_throw:
|
||
case OP_throw_error:
|
||
case OP_goto:
|
||
#if SHORT_OPCODES
|
||
case OP_goto8:
|
||
case OP_goto16:
|
||
#endif
|
||
case OP_ret:
|
||
return FALSE;
|
||
default:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
static void emit_u8 (JSParseState *s, uint8_t val) {
|
||
dbuf_putc (&s->cur_func->byte_code, val);
|
||
}
|
||
|
||
static void emit_u16 (JSParseState *s, uint16_t val) {
|
||
dbuf_put_u16 (&s->cur_func->byte_code, val);
|
||
}
|
||
|
||
static void emit_u32 (JSParseState *s, uint32_t val) {
|
||
dbuf_put_u32 (&s->cur_func->byte_code, val);
|
||
}
|
||
|
||
static void emit_source_pos (JSParseState *s, const uint8_t *source_ptr) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
DynBuf *bc = &fd->byte_code;
|
||
|
||
if (unlikely (fd->last_opcode_source_ptr != source_ptr)) {
|
||
dbuf_putc (bc, OP_line_num);
|
||
dbuf_put_u32 (bc, source_ptr - s->buf_start);
|
||
fd->last_opcode_source_ptr = source_ptr;
|
||
}
|
||
}
|
||
|
||
static void emit_op (JSParseState *s, uint8_t val) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
DynBuf *bc = &fd->byte_code;
|
||
|
||
fd->last_opcode_pos = bc->size;
|
||
dbuf_putc (bc, val);
|
||
}
|
||
|
||
/* Forward declarations for cpool */
|
||
static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val);
|
||
static int cpool_add (JSParseState *s, JSValue val);
|
||
|
||
/* Emit a key (JSValue) by adding to cpool and emitting the index.
|
||
Used for variable opcodes (get_var, put_var, etc.) and property ops. */
|
||
static int emit_key (JSParseState *s, JSValue name) {
|
||
int idx = cpool_add (s, name);
|
||
if (idx < 0) return -1;
|
||
emit_u32 (s, idx);
|
||
return 0;
|
||
}
|
||
|
||
/* Emit a property key for field access opcodes. */
|
||
static int emit_prop_key (JSParseState *s, JSValue key) {
|
||
return emit_key (s, key);
|
||
}
|
||
|
||
static int update_label (JSFunctionDef *s, int label, int delta) {
|
||
LabelSlot *ls;
|
||
|
||
assert (label >= 0 && label < s->label_count);
|
||
ls = &s->label_slots[label];
|
||
ls->ref_count += delta;
|
||
assert (ls->ref_count >= 0);
|
||
return ls->ref_count;
|
||
}
|
||
|
||
static int new_label_fd (JSFunctionDef *fd) {
|
||
int label;
|
||
LabelSlot *ls;
|
||
|
||
if (pjs_resize_array ((void **)&fd->label_slots, sizeof (fd->label_slots[0]), &fd->label_size, fd->label_count + 1))
|
||
return -1;
|
||
label = fd->label_count++;
|
||
ls = &fd->label_slots[label];
|
||
ls->ref_count = 0;
|
||
ls->pos = -1;
|
||
ls->pos2 = -1;
|
||
ls->addr = -1;
|
||
ls->first_reloc = NULL;
|
||
return label;
|
||
}
|
||
|
||
static int new_label (JSParseState *s) {
|
||
int label;
|
||
label = new_label_fd (s->cur_func);
|
||
if (unlikely (label < 0)) { dbuf_set_error (&s->cur_func->byte_code); }
|
||
return label;
|
||
}
|
||
|
||
/* don't update the last opcode and don't emit line number info */
|
||
static void emit_label_raw (JSParseState *s, int label) {
|
||
emit_u8 (s, OP_label);
|
||
emit_u32 (s, label);
|
||
s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
|
||
}
|
||
|
||
/* return the label ID offset */
|
||
static int emit_label (JSParseState *s, int label) {
|
||
if (label >= 0) {
|
||
emit_op (s, OP_label);
|
||
emit_u32 (s, label);
|
||
s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
|
||
return s->cur_func->byte_code.size - 4;
|
||
} else {
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/* return label or -1 if dead code */
|
||
static int emit_goto (JSParseState *s, int opcode, int label) {
|
||
if (js_is_live_code (s)) {
|
||
if (label < 0) {
|
||
label = new_label (s);
|
||
if (label < 0) return -1;
|
||
}
|
||
emit_op (s, opcode);
|
||
emit_u32 (s, label);
|
||
s->cur_func->label_slots[label].ref_count++;
|
||
return label;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/* return the constant pool index. 'val' is not duplicated. */
|
||
static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val) {
|
||
(void)ctx;
|
||
if (pjs_resize_array ((void **)&fd->cpool, sizeof (fd->cpool[0]), &fd->cpool_size, fd->cpool_count + 1))
|
||
return -1;
|
||
fd->cpool[fd->cpool_count++] = val;
|
||
return fd->cpool_count - 1;
|
||
}
|
||
|
||
/* return the constant pool index. 'val' is not duplicated. */
|
||
static int cpool_add (JSParseState *s, JSValue val) {
|
||
return fd_cpool_add (s->ctx, s->cur_func, val);
|
||
}
|
||
|
||
static __exception int emit_push_const (JSParseState *s, JSValue val) {
|
||
int idx;
|
||
idx = cpool_add (s, val);
|
||
if (idx < 0) return -1;
|
||
emit_op (s, OP_push_const);
|
||
emit_u32 (s, idx);
|
||
return 0;
|
||
}
|
||
|
||
/* return the variable index or -1 if not found,
|
||
add ARGUMENT_VAR_OFFSET for argument variables */
|
||
static int find_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) {
|
||
int i;
|
||
for (i = fd->arg_count; i-- > 0;) {
|
||
if (js_key_equal (fd->args[i].var_name, name))
|
||
return i | ARGUMENT_VAR_OFFSET;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static int find_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) {
|
||
int i;
|
||
for (i = fd->var_count; i-- > 0;) {
|
||
if (js_key_equal (fd->vars[i].var_name, name) && fd->vars[i].scope_level == 0)
|
||
return i;
|
||
}
|
||
return find_arg (ctx, fd, name);
|
||
}
|
||
|
||
/* return true if scope == parent_scope or if scope is a child of
|
||
parent_scope */
|
||
static BOOL is_child_scope (JSContext *ctx, JSFunctionDef *fd, int scope, int parent_scope) {
|
||
while (scope >= 0) {
|
||
if (scope == parent_scope) return TRUE;
|
||
scope = fd->scopes[scope].parent;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
/* find a 'var' declaration in the same scope or a child scope */
|
||
static int find_var_in_child_scope (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_level) {
|
||
int i;
|
||
for (i = 0; i < fd->var_count; i++) {
|
||
JSVarDef *vd = &fd->vars[i];
|
||
if (js_key_equal (vd->var_name, name) && vd->scope_level == 0) {
|
||
if (is_child_scope (ctx, fd, vd->scope_next, scope_level)) return i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static JSGlobalVar *find_global_var (JSFunctionDef *fd, JSValue name) {
|
||
int i;
|
||
for (i = 0; i < fd->global_var_count; i++) {
|
||
JSGlobalVar *hf = &fd->global_vars[i];
|
||
if (js_key_equal (hf->var_name, name)) return hf;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static JSGlobalVar *find_lexical_global_var (JSFunctionDef *fd, JSValue name) {
|
||
JSGlobalVar *hf = find_global_var (fd, name);
|
||
if (hf && hf->is_lexical)
|
||
return hf;
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
static int find_lexical_decl (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_idx, BOOL check_catch_var) {
|
||
while (scope_idx >= 0) {
|
||
JSVarDef *vd = &fd->vars[scope_idx];
|
||
if (js_key_equal (vd->var_name, name)
|
||
&& (vd->is_lexical
|
||
|| (vd->var_kind == JS_VAR_CATCH && check_catch_var)))
|
||
return scope_idx;
|
||
scope_idx = vd->scope_next;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
static int push_scope (JSParseState *s) {
|
||
if (s->cur_func) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int scope = fd->scope_count;
|
||
/* XXX: should check for scope overflow */
|
||
if ((fd->scope_count + 1) > fd->scope_size) {
|
||
int new_size;
|
||
JSVarScope *new_buf;
|
||
/* XXX: potential arithmetic overflow */
|
||
new_size = max_int (fd->scope_count + 1, fd->scope_size * 3 / 2);
|
||
if (fd->scopes == fd->def_scope_array) {
|
||
new_buf = pjs_malloc (new_size * sizeof (*fd->scopes));
|
||
if (!new_buf) return -1;
|
||
memcpy (new_buf, fd->scopes, fd->scope_count * sizeof (*fd->scopes));
|
||
} else {
|
||
new_buf = pjs_realloc (fd->scopes, new_size * sizeof (*fd->scopes));
|
||
if (!new_buf) return -1;
|
||
}
|
||
fd->scopes = new_buf;
|
||
fd->scope_size = new_size;
|
||
}
|
||
fd->scope_count++;
|
||
fd->scopes[scope].parent = fd->scope_level;
|
||
fd->scopes[scope].first = fd->scope_first;
|
||
emit_op (s, OP_enter_scope);
|
||
emit_u16 (s, scope);
|
||
return fd->scope_level = scope;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int get_first_lexical_var (JSFunctionDef *fd, int scope) {
|
||
while (scope >= 0) {
|
||
int scope_idx = fd->scopes[scope].first;
|
||
if (scope_idx >= 0) return scope_idx;
|
||
scope = fd->scopes[scope].parent;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static void pop_scope (JSParseState *s) {
|
||
if (s->cur_func) {
|
||
/* disable scoped variables */
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int scope = fd->scope_level;
|
||
emit_op (s, OP_leave_scope);
|
||
emit_u16 (s, scope);
|
||
fd->scope_level = fd->scopes[scope].parent;
|
||
fd->scope_first = get_first_lexical_var (fd, fd->scope_level);
|
||
}
|
||
}
|
||
|
||
static void close_scopes (JSParseState *s, int scope, int scope_stop) {
|
||
while (scope > scope_stop) {
|
||
emit_op (s, OP_leave_scope);
|
||
emit_u16 (s, scope);
|
||
scope = s->cur_func->scopes[scope].parent;
|
||
}
|
||
}
|
||
|
||
/* return the variable index or -1 if error */
|
||
static int add_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) {
|
||
JSVarDef *vd;
|
||
|
||
/* the local variable indexes are currently stored on 16 bits */
|
||
if (fd->var_count >= JS_MAX_LOCAL_VARS) {
|
||
JS_ThrowInternalError (ctx, "too many local variables");
|
||
return -1;
|
||
}
|
||
if (pjs_resize_array ((void **)&fd->vars, sizeof (fd->vars[0]), &fd->var_size, fd->var_count + 1))
|
||
return -1;
|
||
vd = &fd->vars[fd->var_count++];
|
||
memset (vd, 0, sizeof (*vd));
|
||
vd->var_name = name;
|
||
vd->func_pool_idx = -1;
|
||
return fd->var_count - 1;
|
||
}
|
||
|
||
static int add_scope_var (JSContext *ctx, JSFunctionDef *fd, JSValue name, JSVarKindEnum var_kind) {
|
||
int idx = add_var (ctx, fd, name);
|
||
if (idx >= 0) {
|
||
JSVarDef *vd = &fd->vars[idx];
|
||
vd->var_kind = var_kind;
|
||
vd->scope_level = fd->scope_level;
|
||
vd->scope_next = fd->scope_first;
|
||
fd->scopes[fd->scope_level].first = idx;
|
||
fd->scope_first = idx;
|
||
}
|
||
return idx;
|
||
}
|
||
|
||
static int add_func_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) {
|
||
int idx = fd->func_var_idx;
|
||
if (idx < 0 && (idx = add_var (ctx, fd, name)) >= 0) {
|
||
fd->func_var_idx = idx;
|
||
fd->vars[idx].var_kind = JS_VAR_FUNCTION_NAME;
|
||
fd->vars[idx].is_const = TRUE;
|
||
}
|
||
return idx;
|
||
}
|
||
|
||
static int add_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) {
|
||
JSVarDef *vd;
|
||
|
||
/* the local variable indexes are currently stored on 16 bits */
|
||
if (fd->arg_count >= JS_MAX_LOCAL_VARS) {
|
||
JS_ThrowInternalError (ctx, "too many arguments");
|
||
return -1;
|
||
}
|
||
if (pjs_resize_array ((void **)&fd->args, sizeof (fd->args[0]), &fd->arg_size, fd->arg_count + 1))
|
||
return -1;
|
||
vd = &fd->args[fd->arg_count++];
|
||
memset (vd, 0, sizeof (*vd));
|
||
vd->var_name = name;
|
||
vd->func_pool_idx = -1;
|
||
return fd->arg_count - 1;
|
||
}
|
||
|
||
/* add a global variable definition */
|
||
static JSGlobalVar *add_global_var (JSContext *ctx, JSFunctionDef *s, JSValue name) {
|
||
JSGlobalVar *hf;
|
||
(void)ctx;
|
||
|
||
if (pjs_resize_array ((void **)&s->global_vars, sizeof (s->global_vars[0]), &s->global_var_size, s->global_var_count + 1))
|
||
return NULL;
|
||
hf = &s->global_vars[s->global_var_count++];
|
||
hf->cpool_idx = -1;
|
||
hf->force_init = FALSE;
|
||
hf->is_lexical = FALSE;
|
||
hf->is_const = FALSE;
|
||
hf->scope_level = s->scope_level;
|
||
hf->var_name = name;
|
||
return hf;
|
||
}
|
||
|
||
typedef enum {
|
||
JS_VAR_DEF_WITH,
|
||
JS_VAR_DEF_LET,
|
||
JS_VAR_DEF_CONST,
|
||
JS_VAR_DEF_FUNCTION_DECL, /* function declaration */
|
||
JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */
|
||
JS_VAR_DEF_CATCH,
|
||
JS_VAR_DEF_VAR,
|
||
} JSVarDefEnum;
|
||
|
||
static int define_var (JSParseState *s, JSFunctionDef *fd, JSValue name, JSVarDefEnum var_def_type) {
|
||
JSContext *ctx = s->ctx;
|
||
JSVarDef *vd;
|
||
int idx;
|
||
|
||
switch (var_def_type) {
|
||
case JS_VAR_DEF_WITH:
|
||
idx = add_scope_var (ctx, fd, name, JS_VAR_NORMAL);
|
||
break;
|
||
|
||
case JS_VAR_DEF_LET:
|
||
case JS_VAR_DEF_CONST:
|
||
case JS_VAR_DEF_FUNCTION_DECL:
|
||
case JS_VAR_DEF_NEW_FUNCTION_DECL:
|
||
idx = find_lexical_decl (ctx, fd, name, fd->scope_first, TRUE);
|
||
if (idx >= 0) {
|
||
if (idx < GLOBAL_VAR_OFFSET) {
|
||
if (fd->vars[idx].scope_level == fd->scope_level) {
|
||
goto redef_lex_error;
|
||
} else if (fd->vars[idx].var_kind == JS_VAR_CATCH
|
||
&& (fd->vars[idx].scope_level + 2) == fd->scope_level) {
|
||
goto redef_lex_error;
|
||
}
|
||
} else {
|
||
if (fd->scope_level == fd->body_scope) {
|
||
redef_lex_error:
|
||
return js_parse_error (s,
|
||
"invalid redefinition of lexical identifier");
|
||
}
|
||
}
|
||
}
|
||
if (var_def_type != JS_VAR_DEF_FUNCTION_DECL
|
||
&& var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL
|
||
&& fd->scope_level == fd->body_scope
|
||
&& find_arg (ctx, fd, name) >= 0) {
|
||
/* lexical variable redefines a parameter name */
|
||
return js_parse_error (s, "invalid redefinition of parameter name");
|
||
}
|
||
|
||
if (find_var_in_child_scope (ctx, fd, name, fd->scope_level) >= 0) {
|
||
return js_parse_error (s, "invalid redefinition of a variable");
|
||
}
|
||
|
||
if (fd->is_global_var) {
|
||
JSGlobalVar *hf;
|
||
hf = find_global_var (fd, name);
|
||
if (hf && is_child_scope (ctx, fd, hf->scope_level, fd->scope_level)) {
|
||
return js_parse_error (s, "invalid redefinition of global identifier");
|
||
}
|
||
}
|
||
|
||
/* Global object is immutable - always use local variables */
|
||
{
|
||
JSVarKindEnum var_kind;
|
||
if (var_def_type == JS_VAR_DEF_FUNCTION_DECL)
|
||
var_kind = JS_VAR_FUNCTION_DECL;
|
||
else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL)
|
||
var_kind = JS_VAR_NEW_FUNCTION_DECL;
|
||
else
|
||
var_kind = JS_VAR_NORMAL;
|
||
idx = add_scope_var (ctx, fd, name, var_kind);
|
||
if (idx >= 0) {
|
||
vd = &fd->vars[idx];
|
||
vd->is_lexical = 1;
|
||
vd->is_const = (var_def_type == JS_VAR_DEF_CONST);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case JS_VAR_DEF_CATCH:
|
||
idx = add_scope_var (ctx, fd, name, JS_VAR_CATCH);
|
||
break;
|
||
|
||
case JS_VAR_DEF_VAR:
|
||
if (find_lexical_decl (ctx, fd, name, fd->scope_first, FALSE) >= 0) {
|
||
/* error to redefine a var that inside a lexical scope */
|
||
return js_parse_error (s, "invalid redefinition of lexical identifier");
|
||
}
|
||
if (fd->is_global_var) {
|
||
JSGlobalVar *hf;
|
||
hf = find_global_var (fd, name);
|
||
hf = add_global_var (s->ctx, fd, name);
|
||
if (!hf) return -1;
|
||
idx = GLOBAL_VAR_OFFSET;
|
||
} else {
|
||
/* if the variable already exists, don't add it again */
|
||
idx = find_var (ctx, fd, name);
|
||
if (idx >= 0) break;
|
||
idx = add_var (ctx, fd, name);
|
||
if (idx >= 0) { fd->vars[idx].scope_next = fd->scope_level; }
|
||
}
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
return idx;
|
||
}
|
||
|
||
static __exception int js_parse_expr (JSParseState *s);
|
||
static __exception int js_parse_function_decl (JSParseState *s,
|
||
JSParseFunctionEnum func_type,
|
||
JSValue func_name,
|
||
const uint8_t *ptr);
|
||
static __exception int js_parse_function_decl2 (JSParseState *s,
|
||
JSParseFunctionEnum func_type,
|
||
JSValue func_name,
|
||
const uint8_t *ptr,
|
||
JSFunctionDef **pfd);
|
||
static __exception int js_parse_assign_expr2 (JSParseState *s,
|
||
int parse_flags);
|
||
static __exception int js_parse_assign_expr (JSParseState *s);
|
||
static __exception int js_parse_unary (JSParseState *s, int parse_flags);
|
||
static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count);
|
||
static void pop_break_entry (JSFunctionDef *fd);
|
||
|
||
static __exception int js_parse_template (JSParseState *s) {
|
||
JSContext *ctx = s->ctx;
|
||
PPretext *fmt = ppretext_init (64);
|
||
JSToken cooked;
|
||
int expr_count = 0;
|
||
|
||
if (!fmt) return -1;
|
||
|
||
while (s->token.val == TOK_TEMPLATE) {
|
||
const uint8_t *p = s->token.ptr + 1;
|
||
|
||
/* re-parse the string with escape sequences and throw a
|
||
syntax error if it contains invalid sequences */
|
||
s->token.u.str.str = JS_NULL;
|
||
if (js_parse_string (s, '`', TRUE, p, &cooked, &p)) {
|
||
ppretext_free (fmt);
|
||
return -1;
|
||
}
|
||
|
||
/* Append cooked string to format string */
|
||
fmt = ppretext_append_jsvalue (fmt, cooked.u.str.str);
|
||
if (!fmt) return -1;
|
||
|
||
/* Check for end of template */
|
||
if (s->token.u.str.sep == '`') break;
|
||
|
||
/* Append placeholder {N} */
|
||
fmt = ppretext_putc (fmt, '{');
|
||
if (!fmt) return -1;
|
||
fmt = ppretext_append_int (fmt, expr_count);
|
||
if (!fmt) return -1;
|
||
fmt = ppretext_putc (fmt, '}');
|
||
if (!fmt) return -1;
|
||
|
||
/* Parse expression */
|
||
if (next_token (s)) { ppretext_free (fmt); return -1; }
|
||
if (js_parse_expr (s)) { ppretext_free (fmt); return -1; }
|
||
expr_count++;
|
||
|
||
if (s->token.val != '}') {
|
||
ppretext_free (fmt);
|
||
return js_parse_error (s, "expected '}' after template expression");
|
||
}
|
||
|
||
free_token (s, &s->token);
|
||
s->got_lf = FALSE;
|
||
if (js_parse_template_part (s, s->buf_ptr)) {
|
||
ppretext_free (fmt);
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/* Finalize format string */
|
||
JSValue format_str = ppretext_end (ctx, fmt);
|
||
if (JS_IsException (format_str)) return -1;
|
||
|
||
int idx = cpool_add (s, format_str);
|
||
if (idx < 0) return -1;
|
||
|
||
if (expr_count == 0) {
|
||
/* Simple template - just push the string */
|
||
emit_op (s, OP_push_const);
|
||
emit_u32 (s, idx);
|
||
} else {
|
||
/* Template with expressions - emit u16 first for stack size computation */
|
||
emit_op (s, OP_format_template);
|
||
emit_u16 (s, expr_count);
|
||
emit_u32 (s, idx);
|
||
}
|
||
|
||
return next_token (s);
|
||
}
|
||
|
||
#define PROP_TYPE_IDENT 0
|
||
#define PROP_TYPE_VAR 1
|
||
|
||
static BOOL token_is_ident (int tok) {
|
||
/* Accept keywords and reserved words as property names */
|
||
return (tok == TOK_IDENT
|
||
|| (tok >= TOK_FIRST_KEYWORD && tok <= TOK_LAST_KEYWORD));
|
||
}
|
||
|
||
/* if the property is an expression, name = JS_NULL */
|
||
static int __exception js_parse_property_name (JSParseState *s, JSValue *pname, BOOL allow_method, BOOL allow_var) {
|
||
BOOL is_non_reserved_ident;
|
||
JSValue name;
|
||
int prop_type;
|
||
|
||
prop_type = PROP_TYPE_IDENT;
|
||
if (token_is_ident (s->token.val)) {
|
||
/* variable can only be a non-reserved identifier */
|
||
is_non_reserved_ident
|
||
= (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved);
|
||
/* keywords and reserved words have a valid JSValue key */
|
||
name = s->token.u.ident.str;
|
||
if (next_token (s)) goto fail1;
|
||
if (is_non_reserved_ident && prop_type == PROP_TYPE_IDENT && allow_var) {
|
||
if (!(s->token.val == ':' || (s->token.val == '(' && allow_method))) {
|
||
prop_type = PROP_TYPE_VAR;
|
||
}
|
||
}
|
||
} else if (s->token.val == TOK_STRING) {
|
||
/* String literal as property name - already interned from parsing */
|
||
name = s->token.u.str.str;
|
||
if (next_token (s)) goto fail1;
|
||
} else if (s->token.val == TOK_NUMBER) {
|
||
/* Numeric property name - convert to string key */
|
||
JSValue val = s->token.u.num.val;
|
||
JSValue str = JS_ToString (s->ctx, val);
|
||
if (JS_IsException (str)) goto fail;
|
||
name = str;
|
||
if (next_token (s)) goto fail1;
|
||
} else if (s->token.val == '[') {
|
||
if (next_token (s)) goto fail;
|
||
if (js_parse_expr (s)) goto fail;
|
||
if (js_parse_expect (s, ']')) goto fail;
|
||
name = JS_NULL;
|
||
} else {
|
||
goto invalid_prop;
|
||
}
|
||
if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR
|
||
&& s->token.val != '(') {
|
||
invalid_prop:
|
||
js_parse_error (s, "invalid property name");
|
||
goto fail;
|
||
}
|
||
*pname = name;
|
||
return prop_type;
|
||
fail1:
|
||
fail:
|
||
*pname = JS_NULL;
|
||
return -1;
|
||
}
|
||
|
||
typedef struct JSParsePos {
|
||
BOOL got_lf;
|
||
const uint8_t *ptr;
|
||
} JSParsePos;
|
||
|
||
static int js_parse_get_pos (JSParseState *s, JSParsePos *sp) {
|
||
sp->ptr = s->token.ptr;
|
||
sp->got_lf = s->got_lf;
|
||
return 0;
|
||
}
|
||
|
||
static __exception int js_parse_seek_token (JSParseState *s,
|
||
const JSParsePos *sp) {
|
||
s->buf_ptr = sp->ptr;
|
||
s->got_lf = sp->got_lf;
|
||
return next_token (s);
|
||
}
|
||
|
||
/* return TRUE if a regexp literal is allowed after this token */
|
||
static BOOL is_regexp_allowed (int tok) {
|
||
switch (tok) {
|
||
case TOK_NUMBER:
|
||
case TOK_STRING:
|
||
case TOK_REGEXP:
|
||
case TOK_DEC:
|
||
case TOK_INC:
|
||
case TOK_NULL:
|
||
case TOK_FALSE:
|
||
case TOK_TRUE:
|
||
case TOK_THIS:
|
||
case ')':
|
||
case ']':
|
||
case '}': /* XXX: regexp may occur after */
|
||
case TOK_IDENT:
|
||
return FALSE;
|
||
default:
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
#define SKIP_HAS_SEMI (1 << 0)
|
||
#define SKIP_HAS_ASSIGNMENT (1 << 1)
|
||
|
||
static BOOL has_lf_in_range (const uint8_t *p1, const uint8_t *p2) {
|
||
const uint8_t *tmp;
|
||
if (p1 > p2) {
|
||
tmp = p1;
|
||
p1 = p2;
|
||
p2 = tmp;
|
||
}
|
||
return (memchr (p1, '\n', p2 - p1) != NULL);
|
||
}
|
||
|
||
/* XXX: improve speed with early bailout */
|
||
/* XXX: no longer works if regexps are present. Could use previous
|
||
regexp parsing heuristics to handle most cases */
|
||
static int js_parse_skip_parens_token (JSParseState *s, int *pbits, BOOL no_line_terminator) {
|
||
char state[256];
|
||
size_t level = 0;
|
||
JSParsePos pos;
|
||
int last_tok, tok = TOK_EOF;
|
||
int c, tok_len, bits = 0;
|
||
const uint8_t *last_token_ptr;
|
||
|
||
/* protect from underflow */
|
||
state[level++] = 0;
|
||
|
||
js_parse_get_pos (s, &pos);
|
||
last_tok = 0;
|
||
for (;;) {
|
||
switch (s->token.val) {
|
||
case '(':
|
||
case '[':
|
||
case '{':
|
||
if (level >= sizeof (state)) goto done;
|
||
state[level++] = s->token.val;
|
||
break;
|
||
case ')':
|
||
if (state[--level] != '(') goto done;
|
||
break;
|
||
case ']':
|
||
if (state[--level] != '[') goto done;
|
||
break;
|
||
case '}':
|
||
c = state[--level];
|
||
if (c == '`') {
|
||
/* continue the parsing of the template */
|
||
free_token (s, &s->token);
|
||
/* Resume TOK_TEMPLATE parsing (s->token.line_num and
|
||
* s->token.ptr are OK) */
|
||
s->got_lf = FALSE;
|
||
if (js_parse_template_part (s, s->buf_ptr)) goto done;
|
||
goto handle_template;
|
||
} else if (c != '{') {
|
||
goto done;
|
||
}
|
||
break;
|
||
case TOK_TEMPLATE:
|
||
handle_template:
|
||
if (s->token.u.str.sep != '`') {
|
||
/* '${' inside the template : closing '}' and continue
|
||
parsing the template */
|
||
if (level >= sizeof (state)) goto done;
|
||
state[level++] = '`';
|
||
}
|
||
break;
|
||
case TOK_EOF:
|
||
goto done;
|
||
case ';':
|
||
if (level == 2) { bits |= SKIP_HAS_SEMI; }
|
||
break;
|
||
case '=':
|
||
bits |= SKIP_HAS_ASSIGNMENT;
|
||
break;
|
||
|
||
case TOK_DIV_ASSIGN:
|
||
tok_len = 2;
|
||
goto parse_regexp;
|
||
case '/':
|
||
tok_len = 1;
|
||
parse_regexp:
|
||
if (is_regexp_allowed (last_tok)) {
|
||
s->buf_ptr -= tok_len;
|
||
if (js_parse_regexp (s)) {
|
||
/* XXX: should clear the exception */
|
||
goto done;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
/* last_tok is only used to recognize regexps */
|
||
if (s->token.val == TOK_IDENT
|
||
&& (token_is_pseudo_keyword (s, JS_KEY_of)
|
||
|| token_is_pseudo_keyword (s, JS_KEY_yield))) {
|
||
last_tok = TOK_OF;
|
||
} else {
|
||
last_tok = s->token.val;
|
||
}
|
||
last_token_ptr = s->token.ptr;
|
||
if (next_token (s)) {
|
||
/* XXX: should clear the exception generated by next_token() */
|
||
break;
|
||
}
|
||
if (level <= 1) {
|
||
tok = s->token.val;
|
||
if (token_is_pseudo_keyword (s, JS_KEY_of)) tok = TOK_OF;
|
||
if (no_line_terminator && has_lf_in_range (last_token_ptr, s->token.ptr))
|
||
tok = '\n';
|
||
break;
|
||
}
|
||
}
|
||
done:
|
||
if (pbits) { *pbits = bits; }
|
||
if (js_parse_seek_token (s, &pos)) return -1;
|
||
return tok;
|
||
}
|
||
|
||
static void set_object_name (JSParseState *s, JSValue name) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int opcode;
|
||
|
||
opcode = get_prev_opcode (fd);
|
||
if (opcode == OP_set_name) {
|
||
/* XXX: should free key after OP_set_name? */
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
fd->last_opcode_pos = -1;
|
||
emit_op (s, OP_set_name);
|
||
emit_key (s, name);
|
||
} else if (opcode == OP_set_class_name) {
|
||
int define_class_pos;
|
||
uint32_t cpool_idx;
|
||
define_class_pos = fd->last_opcode_pos + 1
|
||
- get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
|
||
assert (fd->byte_code.buf[define_class_pos] == OP_define_class);
|
||
/* Update the cpool index in the bytecode to point to the new name */
|
||
cpool_idx = cpool_add (s, name);
|
||
put_u32 (fd->byte_code.buf + define_class_pos + 1, cpool_idx);
|
||
fd->last_opcode_pos = -1;
|
||
}
|
||
}
|
||
|
||
static void set_object_name_computed (JSParseState *s) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int opcode;
|
||
|
||
opcode = get_prev_opcode (fd);
|
||
if (opcode == OP_set_name) {
|
||
/* XXX: should free atom after OP_set_name? */
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
fd->last_opcode_pos = -1;
|
||
emit_op (s, OP_set_name_computed);
|
||
} else if (opcode == OP_set_class_name) {
|
||
int define_class_pos;
|
||
define_class_pos = fd->last_opcode_pos + 1
|
||
- get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
|
||
assert (fd->byte_code.buf[define_class_pos] == OP_define_class);
|
||
fd->byte_code.buf[define_class_pos] = OP_define_class_computed;
|
||
fd->last_opcode_pos = -1;
|
||
}
|
||
}
|
||
|
||
static __exception int js_parse_object_literal (JSParseState *s) {
|
||
JSValue name = JS_NULL;
|
||
const uint8_t *start_ptr;
|
||
int prop_type;
|
||
|
||
if (next_token (s)) goto fail;
|
||
/* XXX: add an initial length that will be patched back */
|
||
emit_op (s, OP_object);
|
||
|
||
while (s->token.val != '}') {
|
||
/* specific case for getter/setter */
|
||
start_ptr = s->token.ptr;
|
||
|
||
prop_type = js_parse_property_name (s, &name, TRUE, TRUE);
|
||
if (prop_type < 0) goto fail;
|
||
|
||
if (prop_type == PROP_TYPE_VAR) {
|
||
/* shortcut for x: x */
|
||
emit_op (s, OP_scope_get_var);
|
||
emit_key (s, name);
|
||
emit_u16 (s, s->cur_func->scope_level);
|
||
emit_op (s, OP_define_field);
|
||
emit_prop_key (s, name);
|
||
} else if (s->token.val == '(') {
|
||
JSParseFunctionEnum func_type;
|
||
int op_flags;
|
||
|
||
func_type = JS_PARSE_FUNC_METHOD;
|
||
if (js_parse_function_decl (s, func_type, JS_NULL, start_ptr)) goto fail;
|
||
if (JS_IsNull (name)) {
|
||
emit_op (s, OP_define_method_computed);
|
||
} else {
|
||
emit_op (s, OP_define_method);
|
||
emit_key (s, name);
|
||
}
|
||
|
||
op_flags = OP_DEFINE_METHOD_METHOD;
|
||
emit_u8 (s, op_flags | OP_DEFINE_METHOD_ENUMERABLE);
|
||
} else {
|
||
if (JS_IsNull (name)) {
|
||
/* must be done before evaluating expr */
|
||
emit_op (s, OP_to_propkey);
|
||
}
|
||
if (js_parse_expect (s, ':')) goto fail;
|
||
if (js_parse_assign_expr (s)) goto fail;
|
||
if (JS_IsNull (name)) {
|
||
set_object_name_computed (s);
|
||
emit_op (s, OP_define_array_el);
|
||
emit_op (s, OP_drop);
|
||
} else {
|
||
set_object_name (s, name);
|
||
emit_op (s, OP_define_field);
|
||
emit_prop_key (s, name);
|
||
}
|
||
}
|
||
name = JS_NULL;
|
||
if (s->token.val != ',') break;
|
||
if (next_token (s)) goto fail;
|
||
}
|
||
if (js_parse_expect (s, '}')) goto fail;
|
||
return 0;
|
||
fail:
|
||
return -1;
|
||
}
|
||
|
||
/* allow the 'in' binary operator */
|
||
#define PF_IN_ACCEPTED (1 << 0)
|
||
/* allow function calls parsing in js_parse_postfix_expr() */
|
||
#define PF_POSTFIX_CALL (1 << 1)
|
||
/* allow the exponentiation operator in js_parse_unary() */
|
||
#define PF_POW_ALLOWED (1 << 2)
|
||
/* forbid the exponentiation operator in js_parse_unary() */
|
||
#define PF_POW_FORBIDDEN (1 << 3)
|
||
|
||
static __exception int js_parse_postfix_expr (JSParseState *s,
|
||
int parse_flags);
|
||
|
||
static JSFunctionDef *
|
||
js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache);
|
||
static void emit_return (JSParseState *s, BOOL hasval);
|
||
|
||
static __exception int js_parse_left_hand_side_expr (JSParseState *s) {
|
||
return js_parse_postfix_expr (s, PF_POSTFIX_CALL);
|
||
}
|
||
|
||
#define ARRAY_LITERAL_MAX 1024
|
||
|
||
static __exception int js_parse_array_literal (JSParseState *s) {
|
||
uint32_t idx = 0;
|
||
|
||
if (next_token (s)) return -1;
|
||
|
||
while (s->token.val != ']' && idx < ARRAY_LITERAL_MAX) {
|
||
if (s->token.val == ',')
|
||
return js_parse_error (s, "array holes not allowed");
|
||
|
||
if (js_parse_assign_expr (s)) return -1;
|
||
idx++;
|
||
|
||
if (s->token.val == ',') {
|
||
if (next_token (s)) return -1;
|
||
} else if (s->token.val != ']') {
|
||
return js_parse_error (s, "expected ',' or ']'");
|
||
}
|
||
}
|
||
|
||
if (s->token.val != ']') return js_parse_error (s, "array literal too long");
|
||
|
||
emit_op (s, OP_array_from);
|
||
emit_u16 (s, idx);
|
||
return js_parse_expect (s, ']');
|
||
}
|
||
|
||
static __exception int get_lvalue (JSParseState *s, int *popcode, int *pscope, JSValue *pname, int *plabel, int *pdepth, BOOL keep, int tok) {
|
||
JSFunctionDef *fd;
|
||
int opcode, scope, label, depth;
|
||
JSValue name;
|
||
|
||
/* we check the last opcode to get the lvalue type */
|
||
fd = s->cur_func;
|
||
scope = 0;
|
||
name = JS_NULL;
|
||
label = -1;
|
||
depth = 0;
|
||
switch (opcode = get_prev_opcode (fd)) {
|
||
case OP_scope_get_var: {
|
||
/* Read cpool index and get key from cpool */
|
||
uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
|
||
scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5);
|
||
name = fd->cpool[idx];
|
||
if (js_key_equal_str (name, "eval")) {
|
||
return js_parse_error (s, "invalid lvalue in strict mode");
|
||
}
|
||
if (js_key_equal_str (name, "this")
|
||
|| js_key_equal_str (name, "new.target")) {
|
||
goto invalid_lvalue;
|
||
}
|
||
/* Variable lvalue: depth=0, no reference objects on stack */
|
||
depth = 0;
|
||
break;
|
||
}
|
||
case OP_get_field: {
|
||
/* Read cpool index and get property name from cpool */
|
||
uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
|
||
name = fd->cpool[idx];
|
||
depth = 1;
|
||
break;
|
||
}
|
||
case OP_get_array_el:
|
||
depth = 2;
|
||
break;
|
||
default:
|
||
invalid_lvalue:
|
||
if (tok == TOK_FOR) {
|
||
return js_parse_error (s, "invalid for in/of left hand-side");
|
||
} else if (tok == TOK_INC || tok == TOK_DEC) {
|
||
return js_parse_error (s, "invalid increment/decrement operand");
|
||
} else if (tok == '[' || tok == '{') {
|
||
return js_parse_error (s, "invalid destructuring target");
|
||
} else {
|
||
return js_parse_error (s, "invalid assignment left-hand side");
|
||
}
|
||
}
|
||
/* remove the last opcode */
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
fd->last_opcode_pos = -1;
|
||
|
||
if (keep) {
|
||
/* get the value but keep the object/fields on the stack */
|
||
switch (opcode) {
|
||
case OP_scope_get_var:
|
||
/* For variable lvalues, just re-emit the get to have the value on stack.
|
||
No reference objects needed - put_lvalue will emit scope_put_var directly. */
|
||
emit_op (s, OP_scope_get_var);
|
||
emit_key (s, name);
|
||
emit_u16 (s, scope);
|
||
break;
|
||
case OP_get_field:
|
||
emit_op (s, OP_get_field2);
|
||
emit_prop_key (s, name);
|
||
break;
|
||
case OP_get_array_el:
|
||
emit_op (s, OP_get_array_el3);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
/* For variable lvalues without keep, nothing needs to be emitted.
|
||
put_lvalue will handle the store with just the value on stack. */
|
||
|
||
*popcode = opcode;
|
||
*pscope = scope;
|
||
/* name is set for OP_get_field, OP_scope_get_var, and JS_NULL for array_el */
|
||
*pname = name;
|
||
*plabel = label;
|
||
if (pdepth) *pdepth = depth;
|
||
return 0;
|
||
}
|
||
|
||
typedef enum {
|
||
PUT_LVALUE_NOKEEP, /* [depth] v -> */
|
||
PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently
|
||
just disable optimizations) */
|
||
PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */
|
||
PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */
|
||
PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */
|
||
} PutLValueEnum;
|
||
|
||
/* name has a live reference. 'is_let' is only used with opcode =
|
||
OP_scope_get_var for variable lvalues (no reference objects on stack). */
|
||
static void put_lvalue (JSParseState *s, int opcode, int scope, JSValue name, int label, PutLValueEnum special, BOOL is_let) {
|
||
switch (opcode) {
|
||
case OP_scope_get_var:
|
||
/* depth = 0 for variable lvalues - no reference objects on stack */
|
||
switch (special) {
|
||
case PUT_LVALUE_NOKEEP:
|
||
case PUT_LVALUE_NOKEEP_DEPTH:
|
||
/* val -> (store, nothing left) */
|
||
break;
|
||
case PUT_LVALUE_KEEP_TOP:
|
||
/* val -> val (dup before store) */
|
||
emit_op (s, OP_dup);
|
||
break;
|
||
case PUT_LVALUE_KEEP_SECOND:
|
||
/* v0 v -> v0 (store v, keep v0) - for postfix ++/-- */
|
||
/* stack: old_val new_val, store new_val, result is old_val */
|
||
break;
|
||
case PUT_LVALUE_NOKEEP_BOTTOM:
|
||
/* val -> (same as NOKEEP for depth=0) */
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
case OP_get_field:
|
||
/* depth = 1 */
|
||
switch (special) {
|
||
case PUT_LVALUE_NOKEEP:
|
||
case PUT_LVALUE_NOKEEP_DEPTH:
|
||
break;
|
||
case PUT_LVALUE_KEEP_TOP:
|
||
emit_op (s, OP_insert2); /* obj v -> v obj v */
|
||
break;
|
||
case PUT_LVALUE_KEEP_SECOND:
|
||
emit_op (s, OP_perm3); /* obj v0 v -> v0 obj v */
|
||
break;
|
||
case PUT_LVALUE_NOKEEP_BOTTOM:
|
||
emit_op (s, OP_swap);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
case OP_get_array_el:
|
||
/* depth = 2 */
|
||
switch (special) {
|
||
case PUT_LVALUE_NOKEEP:
|
||
emit_op (s, OP_nop); /* will trigger optimization */
|
||
break;
|
||
case PUT_LVALUE_NOKEEP_DEPTH:
|
||
break;
|
||
case PUT_LVALUE_KEEP_TOP:
|
||
emit_op (s, OP_insert3); /* obj prop v -> v obj prop v */
|
||
break;
|
||
case PUT_LVALUE_KEEP_SECOND:
|
||
emit_op (s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */
|
||
break;
|
||
case PUT_LVALUE_NOKEEP_BOTTOM:
|
||
emit_op (s, OP_rot3l);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
switch (opcode) {
|
||
case OP_scope_get_var: /* val -> */
|
||
emit_op (s, is_let ? OP_scope_put_var_init : OP_scope_put_var);
|
||
emit_key (s, name); /* emit cpool index */
|
||
emit_u16 (s, scope);
|
||
break;
|
||
case OP_get_field:
|
||
emit_op (s, OP_put_field);
|
||
emit_prop_key (s, name); /* name has refcount */
|
||
break;
|
||
case OP_get_array_el:
|
||
emit_op (s, OP_put_array_el);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
static __exception int js_parse_expr_paren (JSParseState *s) {
|
||
if (js_parse_expect (s, '(')) return -1;
|
||
if (js_parse_expr (s)) return -1;
|
||
if (js_parse_expect (s, ')')) return -1;
|
||
return 0;
|
||
}
|
||
|
||
static int js_unsupported_keyword (JSParseState *s, JSValue key) {
|
||
char buf[KEY_GET_STR_BUF_SIZE];
|
||
return js_parse_error (s, "unsupported keyword: %s", JS_KeyGetStr (s->ctx, buf, sizeof (buf), key));
|
||
}
|
||
|
||
static __exception int js_define_var (JSParseState *s, JSValue name, int tok) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
JSVarDefEnum var_def_type;
|
||
|
||
if (js_key_equal (name, JS_KEY_eval)) {
|
||
return js_parse_error (s, "invalid variable name in strict mode");
|
||
}
|
||
if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) {
|
||
return js_parse_error (s, "invalid lexical variable name");
|
||
}
|
||
switch (tok) {
|
||
case TOK_DEF:
|
||
var_def_type = JS_VAR_DEF_CONST;
|
||
break;
|
||
case TOK_VAR:
|
||
var_def_type = JS_VAR_DEF_LET;
|
||
break;
|
||
case TOK_DISRUPTION:
|
||
var_def_type = JS_VAR_DEF_CATCH;
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
if (define_var (s, fd, name, var_def_type) < 0) return -1;
|
||
return 0;
|
||
}
|
||
|
||
static int js_parse_check_duplicate_parameter (JSParseState *s, JSValue name) {
|
||
/* Check for duplicate parameter names */
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int i;
|
||
for (i = 0; i < fd->arg_count; i++) {
|
||
if (js_key_equal (fd->args[i].var_name, name)) goto duplicate;
|
||
}
|
||
for (i = 0; i < fd->var_count; i++) {
|
||
if (js_key_equal (fd->vars[i].var_name, name)) goto duplicate;
|
||
}
|
||
return 0;
|
||
|
||
duplicate:
|
||
return js_parse_error (
|
||
s, "duplicate parameter names not allowed in this context");
|
||
}
|
||
|
||
static JSValue js_parse_destructuring_var (JSParseState *s, int tok, int is_arg) {
|
||
JSValue name;
|
||
|
||
if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)
|
||
|| js_key_equal (s->token.u.ident.str, JS_KEY_eval)) {
|
||
js_parse_error (s, "invalid destructuring target");
|
||
return JS_NULL;
|
||
}
|
||
name = s->token.u.ident.str;
|
||
if (is_arg && js_parse_check_duplicate_parameter (s, name)) goto fail;
|
||
if (next_token (s)) goto fail;
|
||
|
||
return name;
|
||
fail:
|
||
return JS_NULL;
|
||
}
|
||
|
||
/* Return -1 if error, 0 if no initializer, 1 if an initializer is
|
||
present at the top level. */
|
||
static int js_parse_destructuring_element (JSParseState *s, int tok, int is_arg, int hasval, BOOL allow_initializer, BOOL export_flag) {
|
||
int label_parse, label_assign, label_done, label_lvalue, depth_lvalue;
|
||
int start_addr, assign_addr;
|
||
JSValue prop_name, var_name;
|
||
int opcode, scope, tok1, skip_bits;
|
||
BOOL has_initializer;
|
||
|
||
label_parse = new_label (s);
|
||
label_assign = new_label (s);
|
||
|
||
start_addr = s->cur_func->byte_code.size;
|
||
if (hasval) {
|
||
/* consume value from the stack */
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_null);
|
||
emit_op (s, OP_strict_eq);
|
||
emit_goto (s, OP_if_true, label_parse);
|
||
emit_label (s, label_assign);
|
||
} else {
|
||
emit_goto (s, OP_goto, label_parse);
|
||
emit_label (s, label_assign);
|
||
/* leave value on the stack */
|
||
emit_op (s, OP_dup);
|
||
}
|
||
assign_addr = s->cur_func->byte_code.size;
|
||
if (s->token.val == '{') {
|
||
if (next_token (s)) return -1;
|
||
/* XXX: throw an exception if the value cannot be converted to an object */
|
||
while (s->token.val != '}') {
|
||
int prop_type;
|
||
prop_type = js_parse_property_name (s, &prop_name, FALSE, TRUE);
|
||
if (prop_type < 0) return -1;
|
||
var_name = JS_NULL;
|
||
if (prop_type == PROP_TYPE_IDENT) {
|
||
if (next_token (s)) goto prop_error;
|
||
if ((s->token.val == '[' || s->token.val == '{')
|
||
&& ((tok1 = js_parse_skip_parens_token (s, &skip_bits, FALSE))
|
||
== ','
|
||
|| tok1 == '=' || tok1 == '}')) {
|
||
if (JS_IsNull (prop_name)) {
|
||
/* computed property name on stack */
|
||
/* get the computed property from the source object */
|
||
emit_op (s, OP_get_array_el2);
|
||
} else {
|
||
/* named property */
|
||
/* get the named property from the source object */
|
||
emit_op (s, OP_get_field2);
|
||
emit_prop_key (s, prop_name);
|
||
}
|
||
if (js_parse_destructuring_element (s, tok, is_arg, TRUE, TRUE, export_flag)
|
||
< 0)
|
||
return -1;
|
||
if (s->token.val == '}') break;
|
||
/* accept a trailing comma before the '}' */
|
||
if (js_parse_expect (s, ',')) return -1;
|
||
continue;
|
||
}
|
||
if (JS_IsNull (prop_name)) {
|
||
emit_op (s, OP_to_propkey);
|
||
/* source prop -- source source prop */
|
||
emit_op (s, OP_dup1);
|
||
} else {
|
||
/* source -- source source */
|
||
emit_op (s, OP_dup);
|
||
}
|
||
if (tok) {
|
||
var_name = js_parse_destructuring_var (s, tok, is_arg);
|
||
if (JS_IsNull (var_name)) goto prop_error;
|
||
/* no need to make a reference for let/const */
|
||
opcode = OP_scope_get_var;
|
||
scope = s->cur_func->scope_level;
|
||
label_lvalue = -1;
|
||
depth_lvalue = 0;
|
||
} else {
|
||
if (js_parse_left_hand_side_expr (s)) goto prop_error;
|
||
lvalue1:
|
||
if (get_lvalue (s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{'))
|
||
goto prop_error;
|
||
/* swap ref and lvalue object if any */
|
||
if (JS_IsNull (prop_name)) {
|
||
switch (depth_lvalue) {
|
||
case 1:
|
||
/* source prop x -> x source prop */
|
||
emit_op (s, OP_rot3r);
|
||
break;
|
||
case 2:
|
||
/* source prop x y -> x y source prop */
|
||
emit_op (s, OP_swap2); /* t p2 s p1 */
|
||
break;
|
||
case 3:
|
||
/* source prop x y z -> x y z source prop */
|
||
emit_op (s, OP_rot5l);
|
||
emit_op (s, OP_rot5l);
|
||
break;
|
||
}
|
||
} else {
|
||
switch (depth_lvalue) {
|
||
case 1:
|
||
/* source x -> x source */
|
||
emit_op (s, OP_swap);
|
||
break;
|
||
case 2:
|
||
/* source x y -> x y source */
|
||
emit_op (s, OP_rot3l);
|
||
break;
|
||
case 3:
|
||
/* source x y z -> x y z source */
|
||
emit_op (s, OP_rot4l);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (JS_IsNull (prop_name)) {
|
||
/* computed property name on stack */
|
||
/* XXX: should have OP_get_array_el2x with depth */
|
||
/* source prop -- val */
|
||
emit_op (s, OP_get_array_el);
|
||
} else {
|
||
/* named property */
|
||
/* XXX: should have OP_get_field2x with depth */
|
||
/* source -- val */
|
||
emit_op (s, OP_get_field);
|
||
emit_prop_key (s, prop_name);
|
||
}
|
||
} else {
|
||
/* prop_type = PROP_TYPE_VAR, cannot be a computed property */
|
||
if (is_arg && js_parse_check_duplicate_parameter (s, prop_name))
|
||
goto prop_error;
|
||
if (js_key_equal_str (prop_name, "eval")) {
|
||
js_parse_error (s, "invalid destructuring target");
|
||
goto prop_error;
|
||
}
|
||
if (!tok) {
|
||
/* generate reference */
|
||
/* source -- source source */
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_scope_get_var);
|
||
emit_key (s, prop_name);
|
||
emit_u16 (s, s->cur_func->scope_level);
|
||
goto lvalue1;
|
||
} else {
|
||
/* no need to make a reference for let/const */
|
||
var_name = prop_name;
|
||
opcode = OP_scope_get_var;
|
||
scope = s->cur_func->scope_level;
|
||
label_lvalue = -1;
|
||
depth_lvalue = 0;
|
||
|
||
/* source -- source val */
|
||
emit_op (s, OP_get_field2);
|
||
emit_prop_key (s, prop_name);
|
||
}
|
||
}
|
||
if (tok) {
|
||
if (js_define_var (s, var_name, tok)) goto var_error;
|
||
scope = s->cur_func->scope_level; /* XXX: check */
|
||
}
|
||
if (s->token.val == '=') { /* handle optional default value */
|
||
int label_hasval;
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_null);
|
||
emit_op (s, OP_strict_eq);
|
||
label_hasval = emit_goto (s, OP_if_false, -1);
|
||
if (next_token (s)) goto var_error;
|
||
emit_op (s, OP_drop);
|
||
if (js_parse_assign_expr (s)) goto var_error;
|
||
if (opcode == OP_scope_get_var)
|
||
set_object_name (s, var_name);
|
||
emit_label (s, label_hasval);
|
||
}
|
||
/* store value into lvalue object */
|
||
put_lvalue (s, opcode, scope, var_name, label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_DEF || tok == TOK_VAR));
|
||
if (s->token.val == '}') break;
|
||
/* accept a trailing comma before the '}' */
|
||
if (js_parse_expect (s, ',')) return -1;
|
||
}
|
||
/* drop the source object */
|
||
emit_op (s, OP_drop);
|
||
if (next_token (s)) return -1;
|
||
} else if (s->token.val == '[') {
|
||
return js_parse_error (s, "array destructuring is not supported");
|
||
} else {
|
||
return js_parse_error (s, "invalid assignment syntax");
|
||
}
|
||
if (s->token.val == '=' && allow_initializer) {
|
||
label_done = emit_goto (s, OP_goto, -1);
|
||
if (next_token (s)) return -1;
|
||
emit_label (s, label_parse);
|
||
if (hasval) emit_op (s, OP_drop);
|
||
if (js_parse_assign_expr (s)) return -1;
|
||
emit_goto (s, OP_goto, label_assign);
|
||
emit_label (s, label_done);
|
||
has_initializer = TRUE;
|
||
} else {
|
||
/* normally hasval is true except if
|
||
js_parse_skip_parens_token() was wrong in the parsing */
|
||
// assert(hasval);
|
||
if (!hasval) {
|
||
js_parse_error (s, "too complicated destructuring expression");
|
||
return -1;
|
||
}
|
||
/* remove test and decrement label ref count */
|
||
memset (s->cur_func->byte_code.buf + start_addr, OP_nop, assign_addr - start_addr);
|
||
s->cur_func->label_slots[label_parse].ref_count--;
|
||
has_initializer = FALSE;
|
||
}
|
||
return has_initializer;
|
||
|
||
prop_error:
|
||
var_error:
|
||
return -1;
|
||
}
|
||
|
||
static void optional_chain_test (JSParseState *s,
|
||
int *poptional_chaining_label,
|
||
int drop_count) {
|
||
int label_next, i;
|
||
if (*poptional_chaining_label < 0) *poptional_chaining_label = new_label (s);
|
||
/* XXX: could be more efficient with a specific opcode */
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_is_null);
|
||
label_next = emit_goto (s, OP_if_false, -1);
|
||
for (i = 0; i < drop_count; i++)
|
||
emit_op (s, OP_drop);
|
||
emit_op (s, OP_null);
|
||
emit_goto (s, OP_goto, *poptional_chaining_label);
|
||
emit_label (s, label_next);
|
||
}
|
||
|
||
/* allowed parse_flags: PF_POSTFIX_CALL */
|
||
static __exception int js_parse_postfix_expr (JSParseState *s,
|
||
int parse_flags) {
|
||
int optional_chaining_label;
|
||
BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0;
|
||
const uint8_t *op_token_ptr;
|
||
|
||
switch (s->token.val) {
|
||
case TOK_NUMBER: {
|
||
JSValue val;
|
||
val = s->token.u.num.val;
|
||
|
||
if (JS_VALUE_GET_TAG (val) == JS_TAG_INT) {
|
||
emit_op (s, OP_push_i32);
|
||
emit_u32 (s, JS_VALUE_GET_INT (val));
|
||
} else {
|
||
if (emit_push_const (s, val) < 0) return -1;
|
||
}
|
||
}
|
||
if (next_token (s)) return -1;
|
||
break;
|
||
case TOK_TEMPLATE:
|
||
if (js_parse_template (s)) return -1;
|
||
break;
|
||
case TOK_STRING:
|
||
if (emit_push_const (s, s->token.u.str.str)) return -1;
|
||
if (next_token (s)) return -1;
|
||
break;
|
||
|
||
case TOK_DIV_ASSIGN:
|
||
s->buf_ptr -= 2;
|
||
goto parse_regexp;
|
||
case '/':
|
||
s->buf_ptr--;
|
||
parse_regexp: {
|
||
JSValue str;
|
||
int ret;
|
||
if (!s->ctx->compile_regexp)
|
||
return js_parse_error (s, "RegExp are not supported");
|
||
/* the previous token is '/' or '/=', so no need to free */
|
||
if (js_parse_regexp (s)) return -1;
|
||
ret = emit_push_const (s, s->token.u.regexp.body);
|
||
str = s->ctx->compile_regexp (s->ctx, s->token.u.regexp.body, s->token.u.regexp.flags);
|
||
if (JS_IsException (str)) {
|
||
/* add the line number info */
|
||
int line_num, col_num;
|
||
line_num
|
||
= get_line_col (&col_num, s->buf_start, s->token.ptr - s->buf_start);
|
||
build_backtrace (s->ctx, s->ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0);
|
||
return -1;
|
||
}
|
||
ret = emit_push_const (s, str);
|
||
if (ret) return -1;
|
||
/* we use a specific opcode to be sure the correct
|
||
function is called (otherwise the bytecode would have
|
||
to be verified by the RegExp constructor) */
|
||
emit_op (s, OP_regexp);
|
||
if (next_token (s)) return -1;
|
||
} break;
|
||
case '(':
|
||
if (js_parse_expr_paren (s)) return -1;
|
||
break;
|
||
case TOK_FUNCTION:
|
||
if (js_parse_function_decl (s, JS_PARSE_FUNC_EXPR, JS_NULL, s->token.ptr))
|
||
return -1;
|
||
break;
|
||
case TOK_NULL:
|
||
if (next_token (s)) return -1;
|
||
emit_op (s, OP_null);
|
||
break;
|
||
case TOK_THIS:
|
||
if (next_token (s)) return -1;
|
||
emit_op (s, OP_scope_get_var);
|
||
emit_key (s, JS_KEY_this);
|
||
emit_u16 (s, 0);
|
||
break;
|
||
case TOK_FALSE:
|
||
if (next_token (s)) return -1;
|
||
emit_op (s, OP_push_false);
|
||
break;
|
||
case TOK_TRUE:
|
||
if (next_token (s)) return -1;
|
||
emit_op (s, OP_push_true);
|
||
break;
|
||
case TOK_IDENT: {
|
||
JSValue name;
|
||
const uint8_t *source_ptr;
|
||
if (s->token.u.ident.is_reserved) {
|
||
return js_parse_error_reserved_identifier (s);
|
||
}
|
||
source_ptr = s->token.ptr;
|
||
name = s->token.u.ident.str;
|
||
if (next_token (s)) {
|
||
return -1;
|
||
}
|
||
emit_source_pos (s, source_ptr);
|
||
emit_op (s, OP_scope_get_var);
|
||
emit_key (s, name);
|
||
emit_u16 (s, s->cur_func->scope_level);
|
||
} break;
|
||
case '{':
|
||
case '[':
|
||
if (s->token.val == '{') {
|
||
if (js_parse_object_literal (s)) return -1;
|
||
} else {
|
||
if (js_parse_array_literal (s)) return -1;
|
||
}
|
||
break;
|
||
default:
|
||
return js_parse_error (s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr);
|
||
}
|
||
|
||
optional_chaining_label = -1;
|
||
for (;;) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
BOOL has_optional_chain = FALSE;
|
||
|
||
if (s->token.val == '(' && accept_lparen) {
|
||
int opcode, arg_count, drop_count;
|
||
|
||
/* function call */
|
||
parse_func_call:
|
||
op_token_ptr = s->token.ptr;
|
||
if (next_token (s)) return -1;
|
||
|
||
switch (opcode = get_prev_opcode (fd)) {
|
||
case OP_get_field:
|
||
/* keep the object on the stack */
|
||
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
|
||
drop_count = 2;
|
||
break;
|
||
case OP_get_field_opt_chain: {
|
||
int opt_chain_label, next_label;
|
||
opt_chain_label
|
||
= get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1);
|
||
/* keep the object on the stack */
|
||
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
|
||
fd->byte_code.size = fd->last_opcode_pos + 1 + 4;
|
||
next_label = emit_goto (s, OP_goto, -1);
|
||
emit_label (s, opt_chain_label);
|
||
/* need an additional undefined value for the
|
||
case where the optional field does not
|
||
exists */
|
||
emit_op (s, OP_null);
|
||
emit_label (s, next_label);
|
||
drop_count = 2;
|
||
opcode = OP_get_field;
|
||
} break;
|
||
case OP_get_array_el:
|
||
/* keep the object on the stack */
|
||
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
|
||
drop_count = 2;
|
||
break;
|
||
case OP_get_array_el_opt_chain: {
|
||
int opt_chain_label, next_label;
|
||
opt_chain_label
|
||
= get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1);
|
||
/* keep the object on the stack */
|
||
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
|
||
fd->byte_code.size = fd->last_opcode_pos + 1;
|
||
next_label = emit_goto (s, OP_goto, -1);
|
||
emit_label (s, opt_chain_label);
|
||
/* need an additional undefined value for the
|
||
case where the optional field does not
|
||
exists */
|
||
emit_op (s, OP_null);
|
||
emit_label (s, next_label);
|
||
drop_count = 2;
|
||
opcode = OP_get_array_el;
|
||
} break;
|
||
case OP_scope_get_var: {
|
||
/* 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:
|
||
opcode = OP_invalid;
|
||
drop_count = 1;
|
||
break;
|
||
}
|
||
if (has_optional_chain) {
|
||
optional_chain_test (s, &optional_chaining_label, drop_count);
|
||
}
|
||
|
||
/* parse arguments */
|
||
arg_count = 0;
|
||
while (s->token.val != ')') {
|
||
if (arg_count >= 65535) {
|
||
return js_parse_error (s, "Too many call arguments");
|
||
}
|
||
if (js_parse_assign_expr (s)) return -1;
|
||
arg_count++;
|
||
if (s->token.val == ')') break;
|
||
/* accept a trailing comma before the ')' */
|
||
if (js_parse_expect (s, ',')) return -1;
|
||
}
|
||
if (next_token (s)) return -1;
|
||
emit_func_call: {
|
||
emit_source_pos (s, op_token_ptr);
|
||
switch (opcode) {
|
||
case OP_get_field:
|
||
case OP_get_array_el:
|
||
emit_op (s, OP_call_method);
|
||
emit_u16 (s, arg_count);
|
||
break;
|
||
default:
|
||
emit_op (s, OP_call);
|
||
emit_u16 (s, arg_count);
|
||
break;
|
||
}
|
||
}
|
||
} else if (s->token.val == '.') {
|
||
op_token_ptr = s->token.ptr;
|
||
if (next_token (s)) return -1;
|
||
parse_property:
|
||
emit_source_pos (s, op_token_ptr);
|
||
if (!token_is_ident (s->token.val)) {
|
||
return js_parse_error (s, "expecting field name");
|
||
}
|
||
if (has_optional_chain) {
|
||
optional_chain_test (s, &optional_chaining_label, 1);
|
||
}
|
||
emit_op (s, OP_get_field);
|
||
emit_prop_key (s, s->token.u.ident.str);
|
||
if (next_token (s)) return -1;
|
||
} else if (s->token.val == '[') {
|
||
op_token_ptr = s->token.ptr;
|
||
parse_array_access:
|
||
if (has_optional_chain) {
|
||
optional_chain_test (s, &optional_chaining_label, 1);
|
||
}
|
||
if (next_token (s)) return -1;
|
||
if (js_parse_expr (s)) return -1;
|
||
if (js_parse_expect (s, ']')) return -1;
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_get_array_el);
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
if (optional_chaining_label >= 0) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int opcode;
|
||
emit_label_raw (s, optional_chaining_label);
|
||
/* modify the last opcode so that it is an indicator of an
|
||
optional chain */
|
||
opcode = get_prev_opcode (fd);
|
||
if (opcode == OP_get_field || opcode == OP_get_array_el) {
|
||
if (opcode == OP_get_field)
|
||
opcode = OP_get_field_opt_chain;
|
||
else
|
||
opcode = OP_get_array_el_opt_chain;
|
||
fd->byte_code.buf[fd->last_opcode_pos] = opcode;
|
||
} else {
|
||
fd->last_opcode_pos = -1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static __exception int js_parse_delete (JSParseState *s) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
int opcode;
|
||
|
||
if (next_token (s)) return -1;
|
||
if (js_parse_unary (s, PF_POW_FORBIDDEN)) return -1;
|
||
switch (opcode = get_prev_opcode (fd)) {
|
||
case OP_get_field:
|
||
case OP_get_field_opt_chain: {
|
||
JSValue val;
|
||
int ret, opt_chain_label, next_label;
|
||
if (opcode == OP_get_field_opt_chain) {
|
||
opt_chain_label
|
||
= get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1);
|
||
} else {
|
||
opt_chain_label = -1;
|
||
}
|
||
{
|
||
/* Read cpool index and get property name from cpool */
|
||
uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
|
||
val = fd->cpool[idx];
|
||
}
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
ret = emit_push_const (s, val);
|
||
if (ret) return ret;
|
||
emit_op (s, OP_delete);
|
||
if (opt_chain_label >= 0) {
|
||
next_label = emit_goto (s, OP_goto, -1);
|
||
emit_label (s, opt_chain_label);
|
||
/* if the optional chain is not taken, return 'true' */
|
||
emit_op (s, OP_drop);
|
||
emit_op (s, OP_push_true);
|
||
emit_label (s, next_label);
|
||
}
|
||
fd->last_opcode_pos = -1;
|
||
} break;
|
||
case OP_get_array_el:
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
fd->last_opcode_pos = -1;
|
||
emit_op (s, OP_delete);
|
||
break;
|
||
case OP_get_array_el_opt_chain: {
|
||
int opt_chain_label, next_label;
|
||
opt_chain_label
|
||
= get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1);
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
emit_op (s, OP_delete);
|
||
next_label = emit_goto (s, OP_goto, -1);
|
||
emit_label (s, opt_chain_label);
|
||
/* if the optional chain is not taken, return 'true' */
|
||
emit_op (s, OP_drop);
|
||
emit_op (s, OP_push_true);
|
||
emit_label (s, next_label);
|
||
fd->last_opcode_pos = -1;
|
||
} break;
|
||
case OP_scope_get_var: {
|
||
/* 'delete this': this is not a reference */
|
||
uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1);
|
||
JSValue name = fd->cpool[idx];
|
||
if (js_key_equal (name, JS_KEY_this)
|
||
|| js_key_equal_str (name, "new.target"))
|
||
goto ret_true;
|
||
|
||
return js_parse_error (s,
|
||
"cannot delete a direct reference in strict mode");
|
||
} break;
|
||
default:
|
||
ret_true:
|
||
emit_op (s, OP_drop);
|
||
emit_op (s, OP_push_true);
|
||
break;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */
|
||
static __exception int js_parse_unary (JSParseState *s, int parse_flags) {
|
||
int op;
|
||
const uint8_t *op_token_ptr;
|
||
|
||
switch (s->token.val) {
|
||
case '+':
|
||
case '-':
|
||
case '!':
|
||
case '~':
|
||
op_token_ptr = s->token.ptr;
|
||
op = s->token.val;
|
||
if (next_token (s)) return -1;
|
||
if (js_parse_unary (s, PF_POW_FORBIDDEN)) return -1;
|
||
switch (op) {
|
||
case '-':
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_neg);
|
||
break;
|
||
case '+':
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_plus);
|
||
break;
|
||
case '!':
|
||
emit_op (s, OP_lnot);
|
||
break;
|
||
case '~':
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_not);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
parse_flags = 0;
|
||
break;
|
||
case TOK_DEC:
|
||
case TOK_INC: {
|
||
int opcode, op, scope, label;
|
||
JSValue name;
|
||
op = s->token.val;
|
||
op_token_ptr = s->token.ptr;
|
||
if (next_token (s)) return -1;
|
||
if (js_parse_unary (s, 0)) return -1;
|
||
if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, TRUE, op))
|
||
return -1;
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_dec + op - TOK_DEC);
|
||
put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE);
|
||
} break;
|
||
case TOK_DELETE:
|
||
if (js_parse_delete (s)) return -1;
|
||
parse_flags = 0;
|
||
break;
|
||
default:
|
||
if (js_parse_postfix_expr (s, PF_POSTFIX_CALL)) return -1;
|
||
if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
|
||
int opcode, op, scope, label;
|
||
JSValue name;
|
||
op = s->token.val;
|
||
op_token_ptr = s->token.ptr;
|
||
if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, TRUE, op))
|
||
return -1;
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_post_dec + op - TOK_DEC);
|
||
put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, FALSE);
|
||
if (next_token (s)) return -1;
|
||
}
|
||
break;
|
||
}
|
||
if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) {
|
||
if (s->token.val == TOK_POW) {
|
||
/* Strict ES7 exponentiation syntax rules: To solve
|
||
conficting semantics between different implementations
|
||
regarding the precedence of prefix operators and the
|
||
postifx exponential, ES7 specifies that -2**2 is a
|
||
syntax error. */
|
||
if (parse_flags & PF_POW_FORBIDDEN) {
|
||
JS_ThrowSyntaxError (s->ctx, "unparenthesized unary expression can't "
|
||
"appear on the left-hand side of '**'");
|
||
return -1;
|
||
}
|
||
op_token_ptr = s->token.ptr;
|
||
if (next_token (s)) return -1;
|
||
if (js_parse_unary (s, PF_POW_ALLOWED)) return -1;
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, OP_pow);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* allowed parse_flags: PF_IN_ACCEPTED */
|
||
static __exception int js_parse_expr_binary (JSParseState *s, int level, int parse_flags) {
|
||
int op, opcode;
|
||
const uint8_t *op_token_ptr;
|
||
|
||
if (level == 0) {
|
||
return js_parse_unary (s, PF_POW_ALLOWED);
|
||
} else {
|
||
if (js_parse_expr_binary (s, level - 1, parse_flags)) return -1;
|
||
}
|
||
for (;;) {
|
||
op = s->token.val;
|
||
op_token_ptr = s->token.ptr;
|
||
switch (level) {
|
||
case 1:
|
||
switch (op) {
|
||
case '*':
|
||
opcode = OP_mul;
|
||
break;
|
||
case '/':
|
||
opcode = OP_div;
|
||
break;
|
||
case '%':
|
||
opcode = OP_mod;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 2:
|
||
switch (op) {
|
||
case '+':
|
||
opcode = OP_add;
|
||
break;
|
||
case '-':
|
||
opcode = OP_sub;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 3:
|
||
switch (op) {
|
||
case TOK_SHL:
|
||
opcode = OP_shl;
|
||
break;
|
||
case TOK_SAR:
|
||
opcode = OP_sar;
|
||
break;
|
||
case TOK_SHR:
|
||
opcode = OP_shr;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 4:
|
||
switch (op) {
|
||
case '<':
|
||
opcode = OP_lt;
|
||
break;
|
||
case '>':
|
||
opcode = OP_gt;
|
||
break;
|
||
case TOK_LTE:
|
||
opcode = OP_lte;
|
||
break;
|
||
case TOK_GTE:
|
||
opcode = OP_gte;
|
||
break;
|
||
case TOK_IN:
|
||
if (parse_flags & PF_IN_ACCEPTED) {
|
||
opcode = OP_in;
|
||
} else {
|
||
return 0;
|
||
}
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 5:
|
||
switch (op) {
|
||
case TOK_STRICT_EQ:
|
||
opcode = OP_strict_eq;
|
||
break;
|
||
case TOK_STRICT_NEQ:
|
||
opcode = OP_strict_neq;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 6:
|
||
switch (op) {
|
||
case '&':
|
||
opcode = OP_and;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 7:
|
||
switch (op) {
|
||
case '^':
|
||
opcode = OP_xor;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
case 8:
|
||
switch (op) {
|
||
case '|':
|
||
opcode = OP_or;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
if (next_token (s)) return -1;
|
||
if (js_parse_expr_binary (s, level - 1, parse_flags)) return -1;
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, opcode);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* allowed parse_flags: PF_IN_ACCEPTED */
|
||
static __exception int js_parse_logical_and_or (JSParseState *s, int op, int parse_flags) {
|
||
int label1;
|
||
|
||
if (op == TOK_LAND) {
|
||
if (js_parse_expr_binary (s, 8, parse_flags)) return -1;
|
||
} else {
|
||
if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1;
|
||
}
|
||
if (s->token.val == op) {
|
||
label1 = new_label (s);
|
||
|
||
for (;;) {
|
||
if (next_token (s)) return -1;
|
||
emit_op (s, OP_dup);
|
||
emit_goto (s, op == TOK_LAND ? OP_if_false : OP_if_true, label1);
|
||
emit_op (s, OP_drop);
|
||
|
||
if (op == TOK_LAND) {
|
||
if (js_parse_expr_binary (s, 8, parse_flags)) return -1;
|
||
} else {
|
||
if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1;
|
||
}
|
||
if (s->token.val != op) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
emit_label (s, label1);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static __exception int js_parse_coalesce_expr (JSParseState *s,
|
||
int parse_flags) {
|
||
return js_parse_logical_and_or (s, TOK_LOR, parse_flags);
|
||
}
|
||
|
||
/* allowed parse_flags: PF_IN_ACCEPTED */
|
||
static __exception int js_parse_cond_expr (JSParseState *s, int parse_flags) {
|
||
int label1, label2;
|
||
|
||
if (js_parse_coalesce_expr (s, parse_flags)) return -1;
|
||
if (s->token.val == '?') {
|
||
if (next_token (s)) return -1;
|
||
label1 = emit_goto (s, OP_if_false, -1);
|
||
|
||
if (js_parse_assign_expr (s)) return -1;
|
||
if (js_parse_expect (s, ':')) return -1;
|
||
|
||
label2 = emit_goto (s, OP_goto, -1);
|
||
|
||
emit_label (s, label1);
|
||
|
||
if (js_parse_assign_expr2 (s, parse_flags & PF_IN_ACCEPTED)) return -1;
|
||
|
||
emit_label (s, label2);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* allowed parse_flags: PF_IN_ACCEPTED */
|
||
static __exception int js_parse_assign_expr2 (JSParseState *s,
|
||
int parse_flags) {
|
||
int opcode, op, scope, skip_bits;
|
||
JSValue name0 = JS_NULL;
|
||
JSValue name;
|
||
|
||
if (s->token.val == '('
|
||
&& js_parse_skip_parens_token (s, NULL, TRUE) == TOK_ARROW) {
|
||
return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr);
|
||
} else if (s->token.val == TOK_IDENT && peek_token (s, TRUE) == TOK_ARROW) {
|
||
return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr);
|
||
} else if ((s->token.val == '{' || s->token.val == '[')
|
||
&& js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') {
|
||
if (js_parse_destructuring_element (s, 0, 0, FALSE, TRUE, FALSE) < 0)
|
||
return -1;
|
||
return 0;
|
||
}
|
||
if (s->token.val == TOK_IDENT) {
|
||
/* name0 is used to check for OP_set_name pattern, not duplicated */
|
||
name0 = s->token.u.ident.str;
|
||
}
|
||
if (js_parse_cond_expr (s, parse_flags)) return -1;
|
||
|
||
op = s->token.val;
|
||
if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) {
|
||
int label;
|
||
const uint8_t *op_token_ptr;
|
||
op_token_ptr = s->token.ptr;
|
||
if (next_token (s)) return -1;
|
||
if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, (op != '='), op)
|
||
< 0)
|
||
return -1;
|
||
|
||
if (js_parse_assign_expr2 (s, parse_flags)) {
|
||
return -1;
|
||
}
|
||
|
||
if (op == '=') {
|
||
if (opcode == OP_scope_get_var && js_key_equal (name, name0)) {
|
||
set_object_name (s, name);
|
||
}
|
||
} else {
|
||
static const uint8_t assign_opcodes[] = {
|
||
OP_mul,
|
||
OP_div,
|
||
OP_mod,
|
||
OP_add,
|
||
OP_sub,
|
||
OP_shl,
|
||
OP_sar,
|
||
OP_shr,
|
||
OP_and,
|
||
OP_xor,
|
||
OP_or,
|
||
OP_pow,
|
||
};
|
||
op = assign_opcodes[op - TOK_MUL_ASSIGN];
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_op (s, op);
|
||
}
|
||
put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE);
|
||
} else if (op >= TOK_LAND_ASSIGN && op <= TOK_LOR_ASSIGN) {
|
||
int label, label1, depth_lvalue, label2;
|
||
|
||
if (next_token (s)) return -1;
|
||
if (get_lvalue (s, &opcode, &scope, &name, &label, &depth_lvalue, TRUE, op)
|
||
< 0)
|
||
return -1;
|
||
|
||
emit_op (s, OP_dup);
|
||
label1
|
||
= emit_goto (s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, -1);
|
||
emit_op (s, OP_drop);
|
||
|
||
if (js_parse_assign_expr2 (s, parse_flags)) {
|
||
return -1;
|
||
}
|
||
|
||
if (opcode == OP_scope_get_var && js_key_equal (name, name0)) {
|
||
set_object_name (s, name);
|
||
}
|
||
|
||
/* For depth=0 (variable lvalues), dup to keep result on stack after put.
|
||
For depth>=1, insert to put value below reference objects. */
|
||
switch (depth_lvalue) {
|
||
case 0:
|
||
emit_op (s, OP_dup);
|
||
break;
|
||
case 1:
|
||
emit_op (s, OP_insert2);
|
||
break;
|
||
case 2:
|
||
emit_op (s, OP_insert3);
|
||
break;
|
||
case 3:
|
||
emit_op (s, OP_insert4);
|
||
break;
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, FALSE);
|
||
label2 = emit_goto (s, OP_goto, -1);
|
||
|
||
emit_label (s, label1);
|
||
|
||
/* remove the lvalue stack entries (none for depth=0 variables) */
|
||
while (depth_lvalue != 0) {
|
||
emit_op (s, OP_nip);
|
||
depth_lvalue--;
|
||
}
|
||
|
||
emit_label (s, label2);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static __exception int js_parse_assign_expr (JSParseState *s) {
|
||
return js_parse_assign_expr2 (s, PF_IN_ACCEPTED);
|
||
}
|
||
|
||
/* allowed parse_flags: PF_IN_ACCEPTED */
|
||
static __exception int js_parse_expr2 (JSParseState *s, int parse_flags) {
|
||
BOOL comma = FALSE;
|
||
for (;;) {
|
||
if (js_parse_assign_expr2 (s, parse_flags)) return -1;
|
||
if (comma) {
|
||
/* prevent get_lvalue from using the last expression
|
||
as an lvalue. This also prevents the conversion of
|
||
of get_var to get_ref for method lookup in function
|
||
call inside `with` statement.
|
||
*/
|
||
s->cur_func->last_opcode_pos = -1;
|
||
}
|
||
if (s->token.val != ',') break;
|
||
comma = TRUE;
|
||
if (next_token (s)) return -1;
|
||
emit_op (s, OP_drop);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static __exception int js_parse_expr (JSParseState *s) {
|
||
return js_parse_expr2 (s, PF_IN_ACCEPTED);
|
||
}
|
||
|
||
static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count) {
|
||
be->prev = fd->top_break;
|
||
fd->top_break = be;
|
||
be->label_name = label_name;
|
||
be->label_break = label_break;
|
||
be->label_cont = label_cont;
|
||
be->drop_count = drop_count;
|
||
be->label_finally = -1;
|
||
be->scope_level = fd->scope_level;
|
||
be->is_regular_stmt = FALSE;
|
||
}
|
||
|
||
static void pop_break_entry (JSFunctionDef *fd) {
|
||
BlockEnv *be;
|
||
be = fd->top_break;
|
||
fd->top_break = be->prev;
|
||
}
|
||
|
||
static __exception int emit_break (JSParseState *s, JSValue name, int is_cont) {
|
||
BlockEnv *top;
|
||
int i, scope_level;
|
||
|
||
scope_level = s->cur_func->scope_level;
|
||
top = s->cur_func->top_break;
|
||
while (top != NULL) {
|
||
close_scopes (s, scope_level, top->scope_level);
|
||
scope_level = top->scope_level;
|
||
if (is_cont && top->label_cont != -1
|
||
&& (JS_IsNull (name) || js_key_equal (top->label_name, name))) {
|
||
/* continue stays inside the same block */
|
||
emit_goto (s, OP_goto, top->label_cont);
|
||
return 0;
|
||
}
|
||
if (!is_cont && top->label_break != -1
|
||
&& ((JS_IsNull (name) && !top->is_regular_stmt)
|
||
|| js_key_equal (top->label_name, name))) {
|
||
emit_goto (s, OP_goto, top->label_break);
|
||
return 0;
|
||
}
|
||
for (i = 0; i < top->drop_count; i++)
|
||
emit_op (s, OP_drop);
|
||
if (top->label_finally != -1) {
|
||
/* must push dummy value to keep same stack depth */
|
||
emit_op (s, OP_null);
|
||
emit_goto (s, OP_gosub, top->label_finally);
|
||
emit_op (s, OP_drop);
|
||
}
|
||
top = top->prev;
|
||
}
|
||
if (JS_IsNull (name)) {
|
||
if (is_cont)
|
||
return js_parse_error (s, "continue must be inside loop");
|
||
else
|
||
return js_parse_error (s, "break must be inside loop or switch");
|
||
} else {
|
||
return js_parse_error (s, "break/continue label not found");
|
||
}
|
||
}
|
||
|
||
/* execute the finally blocks before return */
|
||
static void emit_return (JSParseState *s, BOOL hasval) {
|
||
BlockEnv *top;
|
||
|
||
top = s->cur_func->top_break;
|
||
while (top != NULL) {
|
||
if (top->label_finally != -1) {
|
||
if (!hasval) {
|
||
emit_op (s, OP_null);
|
||
hasval = TRUE;
|
||
}
|
||
/* Remove the stack elements up to and including the catch
|
||
offset. When 'yield' is used in an expression we have
|
||
no easy way to count them, so we use this specific
|
||
instruction instead. */
|
||
emit_op (s, OP_nip_catch);
|
||
/* execute the "finally" block */
|
||
emit_goto (s, OP_gosub, top->label_finally);
|
||
}
|
||
top = top->prev;
|
||
}
|
||
|
||
emit_op (s, hasval ? OP_return : OP_return_undef);
|
||
}
|
||
|
||
#define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */
|
||
/* ored with DECL_MASK_FUNC if function declarations are allowed with a label
|
||
*/
|
||
#define DECL_MASK_FUNC_WITH_LABEL (1 << 1)
|
||
#define DECL_MASK_OTHER (1 << 2) /* all other declarations */
|
||
#define DECL_MASK_ALL \
|
||
(DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER)
|
||
|
||
static __exception int js_parse_statement_or_decl (JSParseState *s,
|
||
int decl_mask);
|
||
|
||
static __exception int js_parse_statement (JSParseState *s) {
|
||
return js_parse_statement_or_decl (s, 0);
|
||
}
|
||
|
||
static __exception int js_parse_block (JSParseState *s) {
|
||
if (js_parse_expect (s, '{')) return -1;
|
||
if (s->token.val != '}') {
|
||
push_scope (s);
|
||
for (;;) {
|
||
if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) return -1;
|
||
if (s->token.val == '}') break;
|
||
}
|
||
pop_scope (s);
|
||
}
|
||
if (next_token (s)) return -1;
|
||
return 0;
|
||
}
|
||
|
||
/* allowed parse_flags: PF_IN_ACCEPTED */
|
||
static __exception int js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
JSValue name = JS_NULL;
|
||
|
||
for (;;) {
|
||
if (s->token.val == TOK_IDENT) {
|
||
if (s->token.u.ident.is_reserved) {
|
||
return js_parse_error_reserved_identifier (s);
|
||
}
|
||
name = s->token.u.ident.str;
|
||
if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) {
|
||
js_parse_error (s, "'let' is not a valid lexical identifier");
|
||
goto var_error;
|
||
}
|
||
if (next_token (s)) goto var_error;
|
||
if (js_define_var (s, name, tok)) goto var_error;
|
||
|
||
if (s->token.val == '=') {
|
||
if (next_token (s)) goto var_error;
|
||
|
||
if (js_parse_assign_expr2 (s, parse_flags)) goto var_error;
|
||
set_object_name (s, name);
|
||
emit_op (s, (tok == TOK_DEF || tok == TOK_VAR) ? OP_scope_put_var_init : OP_scope_put_var);
|
||
emit_key (s, name);
|
||
emit_u16 (s, fd->scope_level);
|
||
} else {
|
||
if (tok == TOK_DEF) {
|
||
js_parse_error (s, "missing initializer for const variable");
|
||
goto var_error;
|
||
}
|
||
if (tok == TOK_VAR) {
|
||
/* initialize lexical variable upon entering its scope */
|
||
emit_op (s, OP_null);
|
||
emit_op (s, OP_scope_put_var_init);
|
||
emit_key (s, name);
|
||
emit_u16 (s, fd->scope_level);
|
||
}
|
||
}
|
||
} else {
|
||
int skip_bits;
|
||
if ((s->token.val == '[' || s->token.val == '{')
|
||
&& js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') {
|
||
emit_op (s, OP_null);
|
||
if (js_parse_destructuring_element (s, tok, 0, TRUE, TRUE, export_flag)
|
||
< 0)
|
||
return -1;
|
||
} else {
|
||
return js_parse_error (s, "variable name expected");
|
||
}
|
||
}
|
||
if (s->token.val != ',') break;
|
||
if (next_token (s)) return -1;
|
||
}
|
||
return 0;
|
||
|
||
var_error:
|
||
return -1;
|
||
}
|
||
|
||
/* test if the current token is a label. Use simplistic look-ahead scanner */
|
||
static BOOL is_label (JSParseState *s) {
|
||
return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved
|
||
&& peek_token (s, FALSE) == ':');
|
||
}
|
||
|
||
/* for-in and for-of loops are not supported */
|
||
static __exception int js_parse_for_in_of (JSParseState *s, int label_name) {
|
||
return js_parse_error (s, "'for in' and 'for of' loops are not supported");
|
||
}
|
||
|
||
static __exception int js_parse_statement_or_decl (JSParseState *s,
|
||
int decl_mask) {
|
||
JSValue label_name;
|
||
int tok;
|
||
|
||
/* specific label handling */
|
||
/* XXX: support multiple labels on loop statements */
|
||
label_name = JS_NULL;
|
||
if (is_label (s)) {
|
||
BlockEnv *be;
|
||
|
||
label_name = s->token.u.ident.str;
|
||
|
||
for (be = s->cur_func->top_break; be; be = be->prev) {
|
||
if (js_key_equal (be->label_name, label_name)) {
|
||
js_parse_error (s, "duplicate label name");
|
||
goto fail;
|
||
}
|
||
}
|
||
|
||
if (next_token (s)) goto fail;
|
||
if (js_parse_expect (s, ':')) goto fail;
|
||
if (s->token.val != TOK_FOR && s->token.val != TOK_DO
|
||
&& s->token.val != TOK_WHILE) {
|
||
/* labelled regular statement */
|
||
int label_break, mask;
|
||
BlockEnv break_entry;
|
||
|
||
label_break = new_label (s);
|
||
push_break_entry (s->cur_func, &break_entry, label_name, label_break, -1, 0);
|
||
break_entry.is_regular_stmt = TRUE;
|
||
mask = 0;
|
||
if (js_parse_statement_or_decl (s, mask)) goto fail;
|
||
emit_label (s, label_break);
|
||
pop_break_entry (s->cur_func);
|
||
goto done;
|
||
}
|
||
}
|
||
|
||
switch (tok = s->token.val) {
|
||
case '{':
|
||
if (js_parse_block (s)) goto fail;
|
||
break;
|
||
case TOK_RETURN: {
|
||
const uint8_t *op_token_ptr;
|
||
op_token_ptr = s->token.ptr;
|
||
if (next_token (s)) goto fail;
|
||
if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
|
||
if (js_parse_expr (s)) goto fail;
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_return (s, TRUE);
|
||
} else {
|
||
emit_source_pos (s, op_token_ptr);
|
||
emit_return (s, FALSE);
|
||
}
|
||
if (js_parse_expect_semi (s)) goto fail;
|
||
} break;
|
||
case TOK_DISRUPT: {
|
||
if (next_token (s)) goto fail;
|
||
emit_op (s, OP_null);
|
||
emit_op (s, OP_throw);
|
||
if (js_parse_expect_semi (s)) goto fail;
|
||
} break;
|
||
case TOK_DEF:
|
||
if (!(decl_mask & DECL_MASK_OTHER)) {
|
||
js_parse_error (
|
||
s, "lexical declarations can't appear in single-statement context");
|
||
goto fail;
|
||
}
|
||
/* fall thru */
|
||
case TOK_VAR:
|
||
if (!(decl_mask & DECL_MASK_OTHER)) {
|
||
js_parse_error (
|
||
s, "lexical declarations can't appear in single-statement context");
|
||
goto fail;
|
||
}
|
||
if (next_token (s)) goto fail;
|
||
if (js_parse_var (s, TRUE, tok, FALSE)) goto fail;
|
||
if (js_parse_expect_semi (s)) goto fail;
|
||
break;
|
||
case TOK_IF: {
|
||
int label1, label2, mask;
|
||
if (next_token (s)) goto fail;
|
||
/* create a new scope for `let f;if(1) function f(){}` */
|
||
push_scope (s);
|
||
if (js_parse_expr_paren (s)) goto fail;
|
||
label1 = emit_goto (s, OP_if_false, -1);
|
||
mask = 0;
|
||
|
||
if (js_parse_statement_or_decl (s, mask)) goto fail;
|
||
|
||
if (s->token.val == TOK_ELSE) {
|
||
label2 = emit_goto (s, OP_goto, -1);
|
||
if (next_token (s)) goto fail;
|
||
|
||
emit_label (s, label1);
|
||
if (js_parse_statement_or_decl (s, mask)) goto fail;
|
||
|
||
label1 = label2;
|
||
}
|
||
emit_label (s, label1);
|
||
pop_scope (s);
|
||
} break;
|
||
case TOK_WHILE: {
|
||
int label_cont, label_break;
|
||
BlockEnv break_entry;
|
||
|
||
label_cont = new_label (s);
|
||
label_break = new_label (s);
|
||
|
||
push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0);
|
||
|
||
if (next_token (s)) goto fail;
|
||
|
||
emit_label (s, label_cont);
|
||
if (js_parse_expr_paren (s)) goto fail;
|
||
emit_goto (s, OP_if_false, label_break);
|
||
|
||
if (js_parse_statement (s)) goto fail;
|
||
emit_goto (s, OP_goto, label_cont);
|
||
|
||
emit_label (s, label_break);
|
||
|
||
pop_break_entry (s->cur_func);
|
||
} break;
|
||
case TOK_DO: {
|
||
int label_cont, label_break, label1;
|
||
BlockEnv break_entry;
|
||
|
||
label_cont = new_label (s);
|
||
label_break = new_label (s);
|
||
label1 = new_label (s);
|
||
|
||
push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0);
|
||
|
||
if (next_token (s)) goto fail;
|
||
|
||
emit_label (s, label1);
|
||
|
||
if (js_parse_statement (s)) goto fail;
|
||
|
||
emit_label (s, label_cont);
|
||
if (js_parse_expect (s, TOK_WHILE)) goto fail;
|
||
if (js_parse_expr_paren (s)) goto fail;
|
||
/* Insert semicolon if missing */
|
||
if (s->token.val == ';') {
|
||
if (next_token (s)) goto fail;
|
||
}
|
||
emit_goto (s, OP_if_true, label1);
|
||
|
||
emit_label (s, label_break);
|
||
|
||
pop_break_entry (s->cur_func);
|
||
} break;
|
||
case TOK_FOR: {
|
||
int label_cont, label_break, label_body, label_test;
|
||
int pos_cont, pos_body, block_scope_level;
|
||
BlockEnv break_entry;
|
||
int tok, bits;
|
||
|
||
if (next_token (s)) goto fail;
|
||
|
||
bits = 0;
|
||
if (s->token.val == '(') { js_parse_skip_parens_token (s, &bits, FALSE); }
|
||
if (js_parse_expect (s, '(')) goto fail;
|
||
|
||
if (!(bits & SKIP_HAS_SEMI)) {
|
||
/* parse for/in or for/of */
|
||
if (js_parse_for_in_of (s, label_name)) goto fail;
|
||
break;
|
||
}
|
||
block_scope_level = s->cur_func->scope_level;
|
||
|
||
/* create scope for the lexical variables declared in the initial,
|
||
test and increment expressions */
|
||
push_scope (s);
|
||
/* initial expression */
|
||
tok = s->token.val;
|
||
if (tok != ';') {
|
||
if (tok == TOK_VAR || tok == TOK_DEF) {
|
||
if (next_token (s)) goto fail;
|
||
if (js_parse_var (s, FALSE, tok, FALSE)) goto fail;
|
||
} else {
|
||
if (js_parse_expr2 (s, FALSE)) goto fail;
|
||
emit_op (s, OP_drop);
|
||
}
|
||
|
||
/* close the closures before the first iteration */
|
||
close_scopes (s, s->cur_func->scope_level, block_scope_level);
|
||
}
|
||
if (js_parse_expect (s, ';')) goto fail;
|
||
|
||
label_test = new_label (s);
|
||
label_cont = new_label (s);
|
||
label_body = new_label (s);
|
||
label_break = new_label (s);
|
||
|
||
push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0);
|
||
|
||
/* test expression */
|
||
if (s->token.val == ';') {
|
||
/* no test expression */
|
||
label_test = label_body;
|
||
} else {
|
||
emit_label (s, label_test);
|
||
if (js_parse_expr (s)) goto fail;
|
||
emit_goto (s, OP_if_false, label_break);
|
||
}
|
||
if (js_parse_expect (s, ';')) goto fail;
|
||
|
||
if (s->token.val == ')') {
|
||
/* no end expression */
|
||
break_entry.label_cont = label_cont = label_test;
|
||
pos_cont = 0; /* avoid warning */
|
||
} else {
|
||
/* skip the end expression */
|
||
emit_goto (s, OP_goto, label_body);
|
||
|
||
pos_cont = s->cur_func->byte_code.size;
|
||
emit_label (s, label_cont);
|
||
if (js_parse_expr (s)) goto fail;
|
||
emit_op (s, OP_drop);
|
||
if (label_test != label_body) emit_goto (s, OP_goto, label_test);
|
||
}
|
||
if (js_parse_expect (s, ')')) goto fail;
|
||
|
||
pos_body = s->cur_func->byte_code.size;
|
||
emit_label (s, label_body);
|
||
if (js_parse_statement (s)) goto fail;
|
||
|
||
/* close the closures before the next iteration */
|
||
/* XXX: check continue case */
|
||
close_scopes (s, s->cur_func->scope_level, block_scope_level);
|
||
|
||
if (OPTIMIZE && label_test != label_body && label_cont != label_test) {
|
||
/* move the increment code here */
|
||
DynBuf *bc = &s->cur_func->byte_code;
|
||
int chunk_size = pos_body - pos_cont;
|
||
int offset = bc->size - pos_cont;
|
||
int i;
|
||
dbuf_realloc (bc, bc->size + chunk_size);
|
||
dbuf_put (bc, bc->buf + pos_cont, chunk_size);
|
||
memset (bc->buf + pos_cont, OP_nop, chunk_size);
|
||
/* increment part ends with a goto */
|
||
s->cur_func->last_opcode_pos = bc->size - 5;
|
||
/* relocate labels */
|
||
for (i = label_cont; i < s->cur_func->label_count; i++) {
|
||
LabelSlot *ls = &s->cur_func->label_slots[i];
|
||
if (ls->pos >= pos_cont && ls->pos < pos_body) ls->pos += offset;
|
||
}
|
||
} else {
|
||
emit_goto (s, OP_goto, label_cont);
|
||
}
|
||
|
||
emit_label (s, label_break);
|
||
|
||
pop_break_entry (s->cur_func);
|
||
pop_scope (s);
|
||
} break;
|
||
case TOK_BREAK:
|
||
case TOK_CONTINUE: {
|
||
int is_cont = s->token.val - TOK_BREAK;
|
||
JSValue label;
|
||
|
||
if (next_token (s)) goto fail;
|
||
if (!s->got_lf && s->token.val == TOK_IDENT
|
||
&& !s->token.u.ident.is_reserved)
|
||
label = s->token.u.ident.str;
|
||
else
|
||
label = JS_NULL;
|
||
if (emit_break (s, label, is_cont)) goto fail;
|
||
if (!JS_IsNull (label)) {
|
||
if (next_token (s)) goto fail;
|
||
}
|
||
if (js_parse_expect_semi (s)) goto fail;
|
||
} break;
|
||
case ';':
|
||
/* empty statement */
|
||
if (next_token (s)) goto fail;
|
||
break;
|
||
case TOK_FUNCTION:
|
||
/* ES6 Annex B.3.2 and B.3.3 semantics */
|
||
if (!(decl_mask & DECL_MASK_FUNC)) goto func_decl_error;
|
||
if (!(decl_mask & DECL_MASK_OTHER) && peek_token (s, FALSE) == '*')
|
||
goto func_decl_error;
|
||
goto parse_func_var;
|
||
case TOK_IDENT:
|
||
if (s->token.u.ident.is_reserved) {
|
||
js_parse_error_reserved_identifier (s);
|
||
goto fail;
|
||
}
|
||
/* let keyword is no longer supported */
|
||
if (token_is_pseudo_keyword (s, JS_KEY_async)
|
||
&& peek_token (s, TRUE) == TOK_FUNCTION) {
|
||
if (!(decl_mask & DECL_MASK_OTHER)) {
|
||
func_decl_error:
|
||
js_parse_error (
|
||
s, "function declarations can't appear in single-statement context");
|
||
goto fail;
|
||
}
|
||
parse_func_var:
|
||
if (js_parse_function_decl (s, JS_PARSE_FUNC_VAR, JS_NULL, s->token.ptr))
|
||
goto fail;
|
||
break;
|
||
}
|
||
goto hasexpr;
|
||
|
||
case TOK_DEBUGGER:
|
||
/* currently no debugger, so just skip the keyword */
|
||
if (next_token (s)) goto fail;
|
||
if (js_parse_expect_semi (s)) goto fail;
|
||
break;
|
||
|
||
case TOK_ENUM:
|
||
case TOK_EXPORT:
|
||
js_unsupported_keyword (s, s->token.u.ident.str);
|
||
goto fail;
|
||
|
||
default:
|
||
hasexpr:
|
||
emit_source_pos (s, s->token.ptr);
|
||
if (js_parse_expr (s)) goto fail;
|
||
emit_op (s, OP_drop); /* drop the result */
|
||
if (js_parse_expect_semi (s)) goto fail;
|
||
break;
|
||
}
|
||
done:
|
||
return 0;
|
||
fail:
|
||
return -1;
|
||
}
|
||
|
||
static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind);
|
||
|
||
static __exception int js_parse_source_element (JSParseState *s) {
|
||
if (s->token.val == TOK_FUNCTION
|
||
|| (token_is_pseudo_keyword (s, JS_KEY_async)
|
||
&& peek_token (s, TRUE) == TOK_FUNCTION)) {
|
||
if (js_parse_function_decl (s, JS_PARSE_FUNC_STATEMENT, JS_NULL, s->token.ptr))
|
||
return -1;
|
||
} else {
|
||
if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static JSFunctionDef *
|
||
js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache) {
|
||
JSFunctionDef *fd;
|
||
|
||
fd = pjs_mallocz (sizeof (*fd));
|
||
if (!fd) return NULL;
|
||
|
||
fd->ctx = ctx;
|
||
init_list_head (&fd->child_list);
|
||
|
||
/* insert in parent list */
|
||
fd->parent = parent;
|
||
fd->parent_cpool_idx = -1;
|
||
if (parent) {
|
||
list_add_tail (&fd->link, &parent->child_list);
|
||
fd->js_mode = parent->js_mode;
|
||
fd->parent_scope_level = parent->scope_level;
|
||
}
|
||
fd->strip_debug = ((ctx->rt->strip_flags & JS_STRIP_DEBUG) != 0);
|
||
fd->strip_source
|
||
= ((ctx->rt->strip_flags & (JS_STRIP_DEBUG | JS_STRIP_SOURCE)) != 0);
|
||
|
||
fd->is_func_expr = is_func_expr;
|
||
js_dbuf_init (ctx, &fd->byte_code);
|
||
fd->last_opcode_pos = -1;
|
||
fd->func_name = JS_NULL;
|
||
fd->var_object_idx = -1;
|
||
fd->arg_var_object_idx = -1;
|
||
fd->func_var_idx = -1;
|
||
fd->this_var_idx = -1;
|
||
fd->this_active_func_var_idx = -1;
|
||
|
||
/* XXX: should distinguish arg, var and var object and body scopes */
|
||
fd->scopes = fd->def_scope_array;
|
||
fd->scope_size = countof (fd->def_scope_array);
|
||
fd->scope_count = 1;
|
||
fd->scopes[0].first = -1;
|
||
fd->scopes[0].parent = -1;
|
||
fd->scope_level = 0; /* 0: var/arg scope */
|
||
fd->scope_first = -1;
|
||
fd->body_scope = -1;
|
||
|
||
fd->filename = js_key_new (ctx, filename);
|
||
fd->source_pos = source_ptr - get_line_col_cache->buf_start;
|
||
fd->get_line_col_cache = get_line_col_cache;
|
||
|
||
js_dbuf_init (ctx, &fd->pc2line);
|
||
// fd->pc2line_last_line_num = line_num;
|
||
// fd->pc2line_last_pc = 0;
|
||
fd->last_opcode_source_ptr = source_ptr;
|
||
return fd;
|
||
}
|
||
|
||
static void free_bytecode_atoms (JSRuntime *rt, const uint8_t *bc_buf, int bc_len, BOOL use_short_opcodes) {
|
||
int pos, len, op;
|
||
const JSOpCode *oi;
|
||
|
||
pos = 0;
|
||
while (pos < bc_len) {
|
||
op = bc_buf[pos];
|
||
if (use_short_opcodes)
|
||
oi = &short_opcode_info (op);
|
||
else
|
||
oi = &opcode_info[op];
|
||
|
||
len = oi->size;
|
||
switch (oi->fmt) {
|
||
case OP_FMT_key:
|
||
case OP_FMT_key_u8:
|
||
case OP_FMT_key_u16:
|
||
case OP_FMT_key_label_u16:
|
||
/* Key operand is now a cpool index; cpool values freed separately */
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
pos += len;
|
||
}
|
||
}
|
||
|
||
static void js_free_function_def (JSContext *ctx, JSFunctionDef *fd) {
|
||
int i;
|
||
struct list_head *el, *el1;
|
||
|
||
/* free the child functions */
|
||
list_for_each_safe (el, el1, &fd->child_list) {
|
||
JSFunctionDef *fd1;
|
||
fd1 = list_entry (el, JSFunctionDef, link);
|
||
js_free_function_def (ctx, fd1);
|
||
}
|
||
|
||
free_bytecode_atoms (ctx->rt, fd->byte_code.buf, fd->byte_code.size, fd->use_short_opcodes);
|
||
dbuf_free (&fd->byte_code);
|
||
pjs_free (fd->jump_slots);
|
||
pjs_free (fd->label_slots);
|
||
pjs_free (fd->line_number_slots);
|
||
|
||
for (i = 0; i < fd->cpool_count; i++) {
|
||
}
|
||
pjs_free (fd->cpool);
|
||
|
||
|
||
for (i = 0; i < fd->var_count; i++) {
|
||
}
|
||
pjs_free (fd->vars);
|
||
for (i = 0; i < fd->arg_count; i++) {
|
||
}
|
||
pjs_free (fd->args);
|
||
|
||
for (i = 0; i < fd->global_var_count; i++) {
|
||
}
|
||
pjs_free (fd->global_vars);
|
||
|
||
for (i = 0; i < fd->closure_var_count; i++) {
|
||
JSClosureVar *cv = &fd->closure_var[i];
|
||
(void)cv;
|
||
}
|
||
pjs_free (fd->closure_var);
|
||
|
||
if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes);
|
||
|
||
dbuf_free (&fd->pc2line);
|
||
|
||
pjs_free (fd->source);
|
||
|
||
if (fd->parent) {
|
||
/* remove in parent list */
|
||
list_del (&fd->link);
|
||
}
|
||
pjs_free (fd);
|
||
}
|
||
|
||
#ifdef DUMP_BYTECODE
|
||
static const char *skip_lines (const char *p, int n) {
|
||
while (n-- > 0 && *p) {
|
||
while (*p && *p++ != '\n')
|
||
continue;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
static void print_lines (const char *source, int line, int line1) {
|
||
const char *s = source;
|
||
const char *p = skip_lines (s, line);
|
||
if (*p) {
|
||
while (line++ < line1) {
|
||
p = skip_lines (s = p, 1);
|
||
printf (";; %.*s", (int)(p - s), s);
|
||
if (!*p) {
|
||
if (p[-1] != '\n') printf ("\n");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void dump_byte_code (JSContext *ctx, int pass, const uint8_t *tab, int len, const JSVarDef *args, int arg_count, const JSVarDef *vars, int var_count, const JSClosureVar *closure_var, int closure_var_count, const JSValue *cpool, uint32_t cpool_count, const char *source, const LabelSlot *label_slots, JSFunctionBytecode *b) {
|
||
const JSOpCode *oi;
|
||
int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num;
|
||
uint8_t *bits = js_mallocz (ctx, len * sizeof (*bits));
|
||
BOOL use_short_opcodes = (b != NULL);
|
||
|
||
if (b) {
|
||
int col_num;
|
||
line_num = find_line_num (ctx, b, -1, &col_num);
|
||
}
|
||
|
||
/* scan for jump targets */
|
||
for (pos = 0; pos < len; pos = pos_next) {
|
||
op = tab[pos];
|
||
if (use_short_opcodes)
|
||
oi = &short_opcode_info (op);
|
||
else
|
||
oi = &opcode_info[op];
|
||
pos_next = pos + oi->size;
|
||
if (op < OP_COUNT) {
|
||
switch (oi->fmt) {
|
||
#if SHORT_OPCODES
|
||
case OP_FMT_label8:
|
||
pos++;
|
||
addr = (int8_t)tab[pos];
|
||
goto has_addr;
|
||
case OP_FMT_label16:
|
||
pos++;
|
||
addr = (int16_t)get_u16 (tab + pos);
|
||
goto has_addr;
|
||
#endif
|
||
case OP_FMT_key_label_u16:
|
||
pos += 4;
|
||
/* fall thru */
|
||
case OP_FMT_label:
|
||
case OP_FMT_label_u16:
|
||
pos++;
|
||
addr = get_u32 (tab + pos);
|
||
goto has_addr;
|
||
has_addr:
|
||
if (pass == 1) addr = label_slots[addr].pos;
|
||
if (pass == 2) addr = label_slots[addr].pos2;
|
||
if (pass == 3) addr += pos;
|
||
if (addr >= 0 && addr < len) bits[addr] |= 1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
in_source = 0;
|
||
if (source) {
|
||
/* Always print first line: needed if single line */
|
||
print_lines (source, 0, 1);
|
||
in_source = 1;
|
||
}
|
||
line1 = line = 1;
|
||
pos = 0;
|
||
while (pos < len) {
|
||
op = tab[pos];
|
||
if (source && b) {
|
||
int col_num;
|
||
if (b) {
|
||
line1 = find_line_num (ctx, b, pos, &col_num) - line_num + 1;
|
||
} else if (op == OP_line_num) {
|
||
/* XXX: no longer works */
|
||
line1 = get_u32 (tab + pos + 1) - line_num + 1;
|
||
}
|
||
if (line1 > line) {
|
||
if (!in_source) printf ("\n");
|
||
in_source = 1;
|
||
print_lines (source, line, line1);
|
||
line = line1;
|
||
// bits[pos] |= 2;
|
||
}
|
||
}
|
||
if (in_source) printf ("\n");
|
||
in_source = 0;
|
||
if (op >= OP_COUNT) {
|
||
printf ("invalid opcode (0x%02x)\n", op);
|
||
pos++;
|
||
continue;
|
||
}
|
||
if (use_short_opcodes)
|
||
oi = &short_opcode_info (op);
|
||
else
|
||
oi = &opcode_info[op];
|
||
size = oi->size;
|
||
if (pos + size > len) {
|
||
printf ("truncated opcode (0x%02x)\n", op);
|
||
break;
|
||
}
|
||
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 16)
|
||
{
|
||
int i, x, x0;
|
||
x = x0 = printf ("%5d ", pos);
|
||
for (i = 0; i < size; i++) {
|
||
if (i == 6) { printf ("\n%*s", x = x0, ""); }
|
||
x += printf (" %02X", tab[pos + i]);
|
||
}
|
||
printf ("%*s", x0 + 20 - x, "");
|
||
}
|
||
#endif
|
||
if (bits[pos]) {
|
||
printf ("%5d: ", pos);
|
||
} else {
|
||
printf (" ");
|
||
}
|
||
printf ("%s", oi->name);
|
||
pos++;
|
||
switch (oi->fmt) {
|
||
case OP_FMT_none_int:
|
||
printf (" %d", op - OP_push_0);
|
||
break;
|
||
case OP_FMT_npopx:
|
||
printf (" %d", op - OP_call0);
|
||
break;
|
||
case OP_FMT_u8:
|
||
printf (" %u", get_u8 (tab + pos));
|
||
break;
|
||
case OP_FMT_i8:
|
||
printf (" %d", get_i8 (tab + pos));
|
||
break;
|
||
case OP_FMT_u16:
|
||
case OP_FMT_npop:
|
||
printf (" %u", get_u16 (tab + pos));
|
||
break;
|
||
case OP_FMT_npop_u16:
|
||
printf (" %u,%u", get_u16 (tab + pos), get_u16 (tab + pos + 2));
|
||
break;
|
||
case OP_FMT_i16:
|
||
printf (" %d", get_i16 (tab + pos));
|
||
break;
|
||
case OP_FMT_i32:
|
||
printf (" %d", get_i32 (tab + pos));
|
||
break;
|
||
case OP_FMT_u32:
|
||
printf (" %u", get_u32 (tab + pos));
|
||
break;
|
||
#if SHORT_OPCODES
|
||
case OP_FMT_label8:
|
||
addr = get_i8 (tab + pos);
|
||
goto has_addr1;
|
||
case OP_FMT_label16:
|
||
addr = get_i16 (tab + pos);
|
||
goto has_addr1;
|
||
#endif
|
||
case OP_FMT_label:
|
||
addr = get_u32 (tab + pos);
|
||
goto has_addr1;
|
||
has_addr1:
|
||
if (pass == 1) printf (" %u:%u", addr, label_slots[addr].pos);
|
||
if (pass == 2) printf (" %u:%u", addr, label_slots[addr].pos2);
|
||
if (pass == 3) printf (" %u", addr + pos);
|
||
break;
|
||
case OP_FMT_label_u16:
|
||
addr = get_u32 (tab + pos);
|
||
if (pass == 1) printf (" %u:%u", addr, label_slots[addr].pos);
|
||
if (pass == 2) printf (" %u:%u", addr, label_slots[addr].pos2);
|
||
if (pass == 3) printf (" %u", addr + pos);
|
||
printf (",%u", get_u16 (tab + pos + 4));
|
||
break;
|
||
#if SHORT_OPCODES
|
||
case OP_FMT_const8:
|
||
idx = get_u8 (tab + pos);
|
||
goto has_pool_idx;
|
||
#endif
|
||
case OP_FMT_const:
|
||
idx = get_u32 (tab + pos);
|
||
goto has_pool_idx;
|
||
has_pool_idx:
|
||
printf (" %u: ", idx);
|
||
if (idx < cpool_count) {
|
||
JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL);
|
||
}
|
||
break;
|
||
case OP_FMT_key:
|
||
printf (" ");
|
||
print_atom (ctx, get_u32 (tab + pos));
|
||
break;
|
||
case OP_FMT_key: {
|
||
/* Key operand is a cpool index; print the cpool value */
|
||
uint32_t key_idx = get_u32 (tab + pos);
|
||
printf (" %u: ", key_idx);
|
||
if (key_idx < cpool_count) {
|
||
JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[key_idx], NULL);
|
||
}
|
||
} break;
|
||
case OP_FMT_key_u8:
|
||
printf (" ");
|
||
print_atom (ctx, get_u32 (tab + pos));
|
||
printf (",%d", get_u8 (tab + pos + 4));
|
||
break;
|
||
case OP_FMT_key_u16:
|
||
printf (" ");
|
||
print_atom (ctx, get_u32 (tab + pos));
|
||
printf (",%d", get_u16 (tab + pos + 4));
|
||
break;
|
||
case OP_FMT_key_label_u16:
|
||
printf (" ");
|
||
print_atom (ctx, get_u32 (tab + pos));
|
||
addr = get_u32 (tab + pos + 4);
|
||
if (pass == 1) printf (",%u:%u", addr, label_slots[addr].pos);
|
||
if (pass == 2) printf (",%u:%u", addr, label_slots[addr].pos2);
|
||
if (pass == 3) printf (",%u", addr + pos + 4);
|
||
printf (",%u", get_u16 (tab + pos + 8));
|
||
break;
|
||
case OP_FMT_none_loc:
|
||
idx = (op - OP_get_loc0) % 4;
|
||
goto has_loc;
|
||
case OP_FMT_loc8:
|
||
idx = get_u8 (tab + pos);
|
||
goto has_loc;
|
||
case OP_FMT_loc:
|
||
idx = get_u16 (tab + pos);
|
||
has_loc:
|
||
printf (" %d: ", idx);
|
||
if (idx < var_count) { print_atom (ctx, vars[idx].var_name); }
|
||
break;
|
||
case OP_FMT_none_arg:
|
||
idx = (op - OP_get_arg0) % 4;
|
||
goto has_arg;
|
||
case OP_FMT_arg:
|
||
idx = get_u16 (tab + pos);
|
||
has_arg:
|
||
printf (" %d: ", idx);
|
||
if (idx < arg_count) { print_atom (ctx, args[idx].var_name); }
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
printf ("\n");
|
||
pos += oi->size - 1;
|
||
}
|
||
if (source) {
|
||
if (!in_source) printf ("\n");
|
||
print_lines (source, line, INT32_MAX);
|
||
}
|
||
js_free (ctx, bits);
|
||
}
|
||
|
||
static __maybe_unused void dump_pc2line (JSContext *ctx, const uint8_t *buf, int len) {
|
||
const uint8_t *p_end, *p;
|
||
int pc, v, line_num, col_num, ret;
|
||
unsigned int op;
|
||
uint32_t val;
|
||
|
||
if (len <= 0) return;
|
||
|
||
printf ("%5s %5s %5s\n", "PC", "LINE", "COL");
|
||
|
||
p = buf;
|
||
p_end = buf + len;
|
||
|
||
/* get the function line and column numbers */
|
||
ret = get_leb128 (&val, p, p_end);
|
||
if (ret < 0) goto fail;
|
||
p += ret;
|
||
line_num = val + 1;
|
||
|
||
ret = get_leb128 (&val, p, p_end);
|
||
if (ret < 0) goto fail;
|
||
p += ret;
|
||
col_num = val + 1;
|
||
|
||
printf ("%5s %5d %5d\n", "-", line_num, col_num);
|
||
|
||
pc = 0;
|
||
while (p < p_end) {
|
||
op = *p++;
|
||
if (op == 0) {
|
||
ret = get_leb128 (&val, p, p_end);
|
||
if (ret < 0) goto fail;
|
||
pc += val;
|
||
p += ret;
|
||
ret = get_sleb128 (&v, p, p_end);
|
||
if (ret < 0) goto fail;
|
||
p += ret;
|
||
line_num += v;
|
||
} else {
|
||
op -= PC2LINE_OP_FIRST;
|
||
pc += (op / PC2LINE_RANGE);
|
||
line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE;
|
||
}
|
||
ret = get_sleb128 (&v, p, p_end);
|
||
if (ret < 0) goto fail;
|
||
p += ret;
|
||
col_num += v;
|
||
|
||
printf ("%5d %5d %5d\n", pc, line_num, col_num);
|
||
}
|
||
fail:;
|
||
}
|
||
|
||
static __maybe_unused void js_dump_function_bytecode (JSContext *ctx,
|
||
JSFunctionBytecode *b) {
|
||
int i;
|
||
char atom_buf[KEY_GET_STR_BUF_SIZE];
|
||
const char *str;
|
||
|
||
if (b->has_debug && !JS_IsNull (b->debug.filename)) {
|
||
int line_num, col_num;
|
||
str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->debug.filename);
|
||
line_num = find_line_num (ctx, b, -1, &col_num);
|
||
printf ("%s:%d:%d: ", str, line_num, col_num);
|
||
}
|
||
|
||
str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->func_name);
|
||
printf ("function: %s%s\n", "", str);
|
||
if (b->js_mode) {
|
||
printf (" mode:");
|
||
printf (" strict");
|
||
printf ("\n");
|
||
}
|
||
if (b->arg_count && b->vardefs) {
|
||
printf (" args:");
|
||
for (i = 0; i < b->arg_count; i++) {
|
||
printf (" %s", JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->vardefs[i].var_name));
|
||
}
|
||
printf ("\n");
|
||
}
|
||
if (b->var_count && b->vardefs) {
|
||
printf (" locals:\n");
|
||
for (i = 0; i < b->var_count; i++) {
|
||
JSVarDef *vd = &b->vardefs[b->arg_count + i];
|
||
printf ("%5d: %s %s", i, vd->var_kind == JS_VAR_CATCH ? "catch" : (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function"
|
||
: vd->is_const ? "const"
|
||
: vd->is_lexical ? "let"
|
||
: "var",
|
||
JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), vd->var_name));
|
||
if (vd->scope_level)
|
||
printf (" [level:%d next:%d]", vd->scope_level, vd->scope_next);
|
||
printf ("\n");
|
||
}
|
||
}
|
||
if (b->closure_var_count) {
|
||
printf (" closure vars:\n");
|
||
for (i = 0; i < b->closure_var_count; i++) {
|
||
JSClosureVar *cv = &b->closure_var[i];
|
||
printf ("%5d: %s %s:%s%d %s\n", i, JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), cv->var_name), cv->is_local ? "local" : "parent", cv->is_arg ? "arg" : "loc", cv->var_idx, cv->is_const ? "const" : cv->is_lexical ? "let"
|
||
: "var");
|
||
}
|
||
}
|
||
printf (" stack_size: %d\n", b->stack_size);
|
||
printf (" opcodes:\n");
|
||
dump_byte_code (ctx, 3, b->byte_code_buf, b->byte_code_len, b->vardefs, b->arg_count, b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count, b->closure_var, b->closure_var_count, b->cpool, b->cpool_count, b->has_debug ? b->debug.source : NULL, NULL, b);
|
||
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32)
|
||
if (b->has_debug)
|
||
dump_pc2line (ctx, b->debug.pc2line_buf, b->debug.pc2line_len);
|
||
#endif
|
||
printf ("\n");
|
||
}
|
||
#endif
|
||
|
||
static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) {
|
||
JSClosureVar *cv;
|
||
|
||
/* the closure variable indexes are currently stored on 16 bits */
|
||
if (s->closure_var_count >= JS_MAX_LOCAL_VARS) {
|
||
JS_ThrowInternalError (ctx, "too many closure variables");
|
||
return -1;
|
||
}
|
||
|
||
if (pjs_resize_array ((void **)&s->closure_var, sizeof (s->closure_var[0]), &s->closure_var_size, s->closure_var_count + 1))
|
||
return -1;
|
||
cv = &s->closure_var[s->closure_var_count++];
|
||
cv->is_local = is_local;
|
||
cv->is_arg = is_arg;
|
||
cv->is_const = is_const;
|
||
cv->is_lexical = is_lexical;
|
||
cv->var_kind = var_kind;
|
||
cv->var_idx = var_idx;
|
||
cv->var_name = var_name;
|
||
return s->closure_var_count - 1;
|
||
}
|
||
|
||
/* 'fd' must be a parent of 's'. Create in 's' a closure referencing a
|
||
local variable (is_local = TRUE) or a closure (is_local = FALSE) in
|
||
'fd' */
|
||
static int get_closure_var2 (JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) {
|
||
int i;
|
||
|
||
if (fd != s->parent) {
|
||
var_idx = get_closure_var2 (ctx, s->parent, fd, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind);
|
||
if (var_idx < 0) return -1;
|
||
is_local = FALSE;
|
||
}
|
||
for (i = 0; i < s->closure_var_count; i++) {
|
||
JSClosureVar *cv = &s->closure_var[i];
|
||
if (cv->var_idx == var_idx && cv->is_arg == is_arg
|
||
&& cv->is_local == is_local)
|
||
return i;
|
||
}
|
||
return add_closure_var (ctx, s, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind);
|
||
}
|
||
|
||
static int get_closure_var (JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) {
|
||
return get_closure_var2 (ctx, s, fd, TRUE, is_arg, var_idx, var_name, is_const, is_lexical, var_kind);
|
||
}
|
||
|
||
/* Compute closure depth and slot for OP_get_up/OP_set_up opcodes.
|
||
Follows the closure_var chain to find the actual variable location.
|
||
Returns depth (1 = immediate parent) and slot (index in JSFrame.slots).
|
||
Returns -1 on error. */
|
||
static int compute_closure_depth_slot(JSFunctionDef *s, int closure_idx, int *out_slot) {
|
||
int depth = 1;
|
||
JSClosureVar *cv = &s->closure_var[closure_idx];
|
||
JSFunctionDef *fd = s->parent;
|
||
|
||
/* Follow chain until we find the actual local variable */
|
||
while (!cv->is_local) {
|
||
if (!fd || !fd->parent) return -1; /* Invalid chain */
|
||
cv = &fd->closure_var[cv->var_idx];
|
||
fd = fd->parent;
|
||
depth++;
|
||
}
|
||
|
||
/* Now cv->var_idx is the index in fd's variables/arguments.
|
||
Compute slot in JSFrame.slots layout: [args..., vars...] */
|
||
if (cv->is_arg) {
|
||
*out_slot = cv->var_idx;
|
||
} else {
|
||
*out_slot = fd->arg_count + cv->var_idx;
|
||
}
|
||
return depth;
|
||
}
|
||
|
||
static int add_var_this (JSContext *ctx, JSFunctionDef *fd) {
|
||
return add_var (ctx, fd, JS_KEY_this);
|
||
}
|
||
|
||
static int resolve_pseudo_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name) {
|
||
int var_idx;
|
||
|
||
if (!s->has_this_binding) return -1;
|
||
|
||
if (js_key_equal (var_name, JS_KEY_this)) {
|
||
/* 'this' pseudo variable */
|
||
if (s->this_var_idx < 0) s->this_var_idx = add_var_this (ctx, s);
|
||
var_idx = s->this_var_idx;
|
||
} else if (js_key_equal (var_name, js_key_new (ctx, "this_active_func"))) {
|
||
/* 'this.active_func' pseudo variable */
|
||
if (s->this_active_func_var_idx < 0)
|
||
s->this_active_func_var_idx = add_var (ctx, s, var_name);
|
||
var_idx = s->this_active_func_var_idx;
|
||
} else if (js_key_equal (var_name, js_key_new (ctx, "new_target"))) {
|
||
/* 'new.target' not supported - constructors removed */
|
||
var_idx = -1;
|
||
} else {
|
||
var_idx = -1;
|
||
}
|
||
return var_idx;
|
||
}
|
||
|
||
/* test if 'var_name' is in the variable object on the stack. If is it
|
||
the case, handle it and jump to 'label_done' */
|
||
static void var_object_test (JSContext *ctx, JSFunctionDef *s, JSValue var_name, int op, DynBuf *bc, int *plabel_done, BOOL is_with) {
|
||
int cpool_idx = fd_cpool_add (ctx, s, var_name);
|
||
dbuf_putc (bc, op);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
}
|
||
|
||
/* return the position of the next opcode */
|
||
static int resolve_scope_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name, int scope_level, int op, DynBuf *bc, uint8_t *bc_buf, LabelSlot *ls, int pos_next) {
|
||
int idx, var_idx, is_put;
|
||
int label_done;
|
||
JSFunctionDef *fd;
|
||
JSVarDef *vd;
|
||
BOOL is_pseudo_var, is_arg_scope;
|
||
|
||
label_done = -1;
|
||
|
||
/* XXX: could be simpler to use a specific function to
|
||
resolve the pseudo variables */
|
||
is_pseudo_var = (js_key_equal_str (var_name, "home_object")
|
||
|| js_key_equal_str (var_name, "this_active_func")
|
||
|| js_key_equal_str (var_name, "new_target")
|
||
|| js_key_equal (var_name, JS_KEY_this));
|
||
|
||
/* resolve local scoped variables */
|
||
var_idx = -1;
|
||
for (idx = s->scopes[scope_level].first; idx >= 0;) {
|
||
vd = &s->vars[idx];
|
||
if (js_key_equal (vd->var_name, var_name)) {
|
||
if (op == OP_scope_put_var) {
|
||
if (vd->is_const) {
|
||
int cpool_idx = fd_cpool_add (ctx, s, var_name);
|
||
dbuf_putc (bc, OP_throw_error);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
dbuf_putc (bc, JS_THROW_VAR_RO);
|
||
goto done;
|
||
}
|
||
}
|
||
var_idx = idx;
|
||
break;
|
||
}
|
||
idx = vd->scope_next;
|
||
}
|
||
is_arg_scope = (idx == ARG_SCOPE_END);
|
||
if (var_idx < 0) {
|
||
/* argument scope: variables are not visible but pseudo
|
||
variables are visible */
|
||
if (!is_arg_scope) { var_idx = find_var (ctx, s, var_name); }
|
||
|
||
if (var_idx < 0 && is_pseudo_var)
|
||
var_idx = resolve_pseudo_var (ctx, s, var_name);
|
||
|
||
if (var_idx < 0 && s->is_func_expr
|
||
&& js_key_equal (var_name, s->func_name)) {
|
||
/* add a new variable with the function name */
|
||
var_idx = add_func_var (ctx, s, var_name);
|
||
}
|
||
}
|
||
if (var_idx >= 0) {
|
||
if (op == OP_scope_put_var
|
||
&& !(var_idx & ARGUMENT_VAR_OFFSET) && s->vars[var_idx].is_const) {
|
||
/* only happens when assigning a function expression name
|
||
in strict mode */
|
||
int cpool_idx = fd_cpool_add (ctx, s, var_name);
|
||
dbuf_putc (bc, OP_throw_error);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
dbuf_putc (bc, JS_THROW_VAR_RO);
|
||
goto done;
|
||
}
|
||
/* OP_scope_put_var_init is only used to initialize a
|
||
lexical variable, so it is never used in a with or var object. It
|
||
can be used with a closure (module global variable case). */
|
||
switch (op) {
|
||
case OP_scope_get_var_checkthis:
|
||
case OP_scope_get_var_undef:
|
||
case OP_scope_get_var:
|
||
case OP_scope_put_var:
|
||
case OP_scope_put_var_init:
|
||
is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init);
|
||
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
||
dbuf_putc (bc, OP_get_arg + is_put);
|
||
dbuf_put_u16 (bc, var_idx - ARGUMENT_VAR_OFFSET);
|
||
} else {
|
||
if (is_put) {
|
||
if (s->vars[var_idx].is_lexical) {
|
||
if (op == OP_scope_put_var_init) {
|
||
/* 'this' can only be initialized once */
|
||
if (js_key_equal (var_name, JS_KEY_this))
|
||
dbuf_putc (bc, OP_put_loc_check_init);
|
||
else
|
||
dbuf_putc (bc, OP_put_loc);
|
||
} else {
|
||
dbuf_putc (bc, OP_put_loc_check);
|
||
}
|
||
} else {
|
||
dbuf_putc (bc, OP_put_loc);
|
||
}
|
||
} else {
|
||
if (s->vars[var_idx].is_lexical) {
|
||
if (op == OP_scope_get_var_checkthis) {
|
||
/* only used for 'this' return in derived class constructors */
|
||
dbuf_putc (bc, OP_get_loc_checkthis);
|
||
} else {
|
||
dbuf_putc (bc, OP_get_loc_check);
|
||
}
|
||
} else {
|
||
dbuf_putc (bc, OP_get_loc);
|
||
}
|
||
}
|
||
dbuf_put_u16 (bc, var_idx);
|
||
}
|
||
break;
|
||
case OP_scope_delete_var:
|
||
dbuf_putc (bc, OP_push_false);
|
||
break;
|
||
}
|
||
goto done;
|
||
}
|
||
/* check eval object */
|
||
if (!is_arg_scope && s->var_object_idx >= 0 && !is_pseudo_var) {
|
||
dbuf_putc (bc, OP_get_loc);
|
||
dbuf_put_u16 (bc, s->var_object_idx);
|
||
var_object_test (ctx, s, var_name, op, bc, &label_done, 0);
|
||
}
|
||
/* check eval object in argument scope */
|
||
if (s->arg_var_object_idx >= 0 && !is_pseudo_var) {
|
||
dbuf_putc (bc, OP_get_loc);
|
||
dbuf_put_u16 (bc, s->arg_var_object_idx);
|
||
var_object_test (ctx, s, var_name, op, bc, &label_done, 0);
|
||
}
|
||
|
||
/* check parent scopes */
|
||
for (fd = s; fd->parent;) {
|
||
scope_level = fd->parent_scope_level;
|
||
fd = fd->parent;
|
||
for (idx = fd->scopes[scope_level].first; idx >= 0;) {
|
||
vd = &fd->vars[idx];
|
||
if (js_key_equal (vd->var_name, var_name)) {
|
||
if (op == OP_scope_put_var) {
|
||
if (vd->is_const) {
|
||
int cpool_idx = fd_cpool_add (ctx, s, var_name);
|
||
dbuf_putc (bc, OP_throw_error);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
dbuf_putc (bc, JS_THROW_VAR_RO);
|
||
goto done;
|
||
}
|
||
}
|
||
var_idx = idx;
|
||
break;
|
||
}
|
||
idx = vd->scope_next;
|
||
}
|
||
is_arg_scope = (idx == ARG_SCOPE_END);
|
||
if (var_idx >= 0) break;
|
||
|
||
if (!is_arg_scope) {
|
||
var_idx = find_var (ctx, fd, var_name);
|
||
if (var_idx >= 0) break;
|
||
}
|
||
if (is_pseudo_var) {
|
||
var_idx = resolve_pseudo_var (ctx, fd, var_name);
|
||
if (var_idx >= 0) break;
|
||
}
|
||
if (fd->is_func_expr && js_key_equal (fd->func_name, var_name)) {
|
||
/* add a new variable with the function name */
|
||
var_idx = add_func_var (ctx, fd, var_name);
|
||
break;
|
||
}
|
||
|
||
/* check eval object */
|
||
if (!is_arg_scope && fd->var_object_idx >= 0 && !is_pseudo_var) {
|
||
int slot, depth;
|
||
vd = &fd->vars[fd->var_object_idx];
|
||
vd->is_captured = 1;
|
||
idx = get_closure_var (ctx, s, fd, FALSE, fd->var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL);
|
||
depth = compute_closure_depth_slot(s, idx, &slot);
|
||
dbuf_putc (bc, OP_get_up);
|
||
dbuf_putc (bc, (uint8_t)depth);
|
||
dbuf_put_u16 (bc, (uint16_t)slot);
|
||
var_object_test (ctx, s, var_name, op, bc, &label_done, 0);
|
||
}
|
||
|
||
/* check eval object in argument scope */
|
||
if (fd->arg_var_object_idx >= 0 && !is_pseudo_var) {
|
||
int slot, depth;
|
||
vd = &fd->vars[fd->arg_var_object_idx];
|
||
vd->is_captured = 1;
|
||
idx = get_closure_var (ctx, s, fd, FALSE, fd->arg_var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL);
|
||
depth = compute_closure_depth_slot(s, idx, &slot);
|
||
dbuf_putc (bc, OP_get_up);
|
||
dbuf_putc (bc, (uint8_t)depth);
|
||
dbuf_put_u16 (bc, (uint16_t)slot);
|
||
var_object_test (ctx, s, var_name, op, bc, &label_done, 0);
|
||
}
|
||
}
|
||
|
||
if (var_idx >= 0) {
|
||
/* find the corresponding closure variable */
|
||
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
||
fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1;
|
||
idx = get_closure_var (ctx, s, fd, TRUE, var_idx - ARGUMENT_VAR_OFFSET, var_name, FALSE, FALSE, JS_VAR_NORMAL);
|
||
} else {
|
||
fd->vars[var_idx].is_captured = 1;
|
||
idx = get_closure_var (
|
||
ctx, s, fd, FALSE, var_idx, var_name, fd->vars[var_idx].is_const, fd->vars[var_idx].is_lexical, fd->vars[var_idx].var_kind);
|
||
}
|
||
if (idx >= 0) {
|
||
has_idx:
|
||
if (op == OP_scope_put_var && s->closure_var[idx].is_const) {
|
||
int cpool_idx = fd_cpool_add (ctx, s, var_name);
|
||
dbuf_putc (bc, OP_throw_error);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
dbuf_putc (bc, JS_THROW_VAR_RO);
|
||
goto done;
|
||
}
|
||
switch (op) {
|
||
case OP_scope_get_var_undef:
|
||
case OP_scope_get_var:
|
||
case OP_scope_put_var:
|
||
case OP_scope_put_var_init:
|
||
is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init);
|
||
{
|
||
/* Use OP_get_up/OP_set_up with (depth, slot) encoding */
|
||
int slot;
|
||
int depth = compute_closure_depth_slot(s, idx, &slot);
|
||
assert(depth > 0 && depth <= 255 && slot <= 65535);
|
||
if (is_put) {
|
||
dbuf_putc(bc, OP_set_up);
|
||
} else {
|
||
dbuf_putc(bc, OP_get_up);
|
||
}
|
||
dbuf_putc(bc, (uint8_t)depth);
|
||
dbuf_put_u16(bc, (uint16_t)slot);
|
||
}
|
||
break;
|
||
case OP_scope_delete_var:
|
||
dbuf_putc (bc, OP_push_false);
|
||
break;
|
||
}
|
||
goto done;
|
||
}
|
||
}
|
||
|
||
/* global variable access */
|
||
{
|
||
int cpool_idx = fd_cpool_add (ctx, s, var_name);
|
||
|
||
switch (op) {
|
||
case OP_scope_get_var_undef:
|
||
case OP_scope_get_var:
|
||
case OP_scope_put_var:
|
||
dbuf_putc (bc, OP_get_var_undef + (op - OP_scope_get_var_undef));
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
break;
|
||
case OP_scope_put_var_init:
|
||
dbuf_putc (bc, OP_put_var_init);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
break;
|
||
case OP_scope_delete_var:
|
||
dbuf_putc (bc, OP_delete_var);
|
||
dbuf_put_u32 (bc, cpool_idx);
|
||
break;
|
||
}
|
||
}
|
||
done:
|
||
if (label_done >= 0) {
|
||
dbuf_putc (bc, OP_label);
|
||
dbuf_put_u32 (bc, label_done);
|
||
s->label_slots[label_done].pos2 = bc->size;
|
||
}
|
||
return pos_next;
|
||
}
|
||
|
||
typedef struct CodeContext {
|
||
const uint8_t *bc_buf; /* code buffer */
|
||
int bc_len; /* length of the code buffer */
|
||
int pos; /* position past the matched code pattern */
|
||
int line_num; /* last visited OP_line_num parameter or -1 */
|
||
int op;
|
||
int idx;
|
||
int label;
|
||
int val;
|
||
} CodeContext;
|
||
|
||
#define M2(op1, op2) ((op1) | ((op2) << 8))
|
||
#define M3(op1, op2, op3) ((op1) | ((op2) << 8) | ((op3) << 16))
|
||
#define M4(op1, op2, op3, op4) \
|
||
((op1) | ((op2) << 8) | ((op3) << 16) | ((op4) << 24))
|
||
|
||
static BOOL code_match (CodeContext *s, int pos, ...) {
|
||
const uint8_t *tab = s->bc_buf;
|
||
int op, len, op1, line_num, pos_next;
|
||
va_list ap;
|
||
BOOL ret = FALSE;
|
||
|
||
line_num = -1;
|
||
va_start (ap, pos);
|
||
|
||
for (;;) {
|
||
op1 = va_arg (ap, int);
|
||
if (op1 == -1) {
|
||
s->pos = pos;
|
||
s->line_num = line_num;
|
||
ret = TRUE;
|
||
break;
|
||
}
|
||
for (;;) {
|
||
if (pos >= s->bc_len) goto done;
|
||
op = tab[pos];
|
||
len = opcode_info[op].size;
|
||
pos_next = pos + len;
|
||
if (pos_next > s->bc_len) goto done;
|
||
if (op == OP_line_num) {
|
||
line_num = get_u32 (tab + pos + 1);
|
||
pos = pos_next;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
if (op != op1) {
|
||
if (op1 == (uint8_t)op1 || !op) break;
|
||
if (op != (uint8_t)op1 && op != (uint8_t)(op1 >> 8)
|
||
&& op != (uint8_t)(op1 >> 16) && op != (uint8_t)(op1 >> 24)) {
|
||
break;
|
||
}
|
||
s->op = op;
|
||
}
|
||
|
||
pos++;
|
||
switch (opcode_info[op].fmt) {
|
||
case OP_FMT_loc8:
|
||
case OP_FMT_u8: {
|
||
int idx = tab[pos];
|
||
int arg = va_arg (ap, int);
|
||
if (arg == -1) {
|
||
s->idx = idx;
|
||
} else {
|
||
if (arg != idx) goto done;
|
||
}
|
||
break;
|
||
}
|
||
case OP_FMT_u16:
|
||
case OP_FMT_npop:
|
||
case OP_FMT_loc:
|
||
case OP_FMT_arg: {
|
||
int idx = get_u16 (tab + pos);
|
||
int arg = va_arg (ap, int);
|
||
if (arg == -1) {
|
||
s->idx = idx;
|
||
} else {
|
||
if (arg != idx) goto done;
|
||
}
|
||
break;
|
||
}
|
||
case OP_FMT_i32:
|
||
case OP_FMT_u32:
|
||
case OP_FMT_label:
|
||
case OP_FMT_const: {
|
||
s->label = get_u32 (tab + pos);
|
||
break;
|
||
}
|
||
case OP_FMT_label_u16: {
|
||
s->label = get_u32 (tab + pos);
|
||
s->val = get_u16 (tab + pos + 4);
|
||
break;
|
||
}
|
||
case OP_FMT_key: {
|
||
/* Store cpool index in the label field */
|
||
s->label = get_u32 (tab + pos);
|
||
break;
|
||
}
|
||
case OP_FMT_key_u8: {
|
||
s->label = get_u32 (tab + pos);
|
||
s->val = get_u8 (tab + pos + 4);
|
||
break;
|
||
}
|
||
case OP_FMT_key_u16: {
|
||
s->label = get_u32 (tab + pos);
|
||
s->val = get_u16 (tab + pos + 4);
|
||
break;
|
||
}
|
||
case OP_FMT_key_label_u16: {
|
||
s->label = get_u32 (tab + pos);
|
||
s->label = get_u32 (tab + pos + 4);
|
||
s->val = get_u16 (tab + pos + 8);
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
pos = pos_next;
|
||
}
|
||
done:
|
||
va_end (ap);
|
||
return ret;
|
||
}
|
||
|
||
static void instantiate_hoisted_definitions (JSContext *ctx, JSFunctionDef *s, DynBuf *bc) {
|
||
int i, idx;
|
||
|
||
/* add the hoisted functions in arguments and local variables */
|
||
for (i = 0; i < s->arg_count; i++) {
|
||
JSVarDef *vd = &s->args[i];
|
||
if (vd->func_pool_idx >= 0) {
|
||
dbuf_putc (bc, OP_fclosure);
|
||
dbuf_put_u32 (bc, vd->func_pool_idx);
|
||
dbuf_putc (bc, OP_put_arg);
|
||
dbuf_put_u16 (bc, i);
|
||
}
|
||
}
|
||
for (i = 0; i < s->var_count; i++) {
|
||
JSVarDef *vd = &s->vars[i];
|
||
if (vd->scope_level == 0 && vd->func_pool_idx >= 0) {
|
||
dbuf_putc (bc, OP_fclosure);
|
||
dbuf_put_u32 (bc, vd->func_pool_idx);
|
||
dbuf_putc (bc, OP_put_loc);
|
||
dbuf_put_u16 (bc, i);
|
||
}
|
||
}
|
||
|
||
/* add the global variables (only happens if s->is_global_var is
|
||
true) */
|
||
for (i = 0; i < s->global_var_count; i++) {
|
||
JSGlobalVar *hf = &s->global_vars[i];
|
||
int has_closure = 0;
|
||
BOOL force_init = hf->force_init;
|
||
/* we are in an eval, so the closure contains all the
|
||
enclosing variables */
|
||
/* If the outer function has a variable environment,
|
||
create a property for the variable there */
|
||
for (idx = 0; idx < s->closure_var_count; idx++) {
|
||
JSClosureVar *cv = &s->closure_var[idx];
|
||
if (js_key_equal (cv->var_name, hf->var_name)) {
|
||
has_closure = 2;
|
||
force_init = FALSE;
|
||
break;
|
||
}
|
||
if (js_key_equal_str (cv->var_name, "_var_")
|
||
|| js_key_equal_str (cv->var_name, "_arg_var_")) {
|
||
int slot, depth = compute_closure_depth_slot(s, idx, &slot);
|
||
dbuf_putc (bc, OP_get_up);
|
||
dbuf_putc (bc, (uint8_t)depth);
|
||
dbuf_put_u16 (bc, (uint16_t)slot);
|
||
has_closure = 1;
|
||
force_init = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
if (!has_closure) {
|
||
int flags;
|
||
|
||
flags = 0;
|
||
if (hf->cpool_idx >= 0 && !hf->is_lexical) {
|
||
/* global function definitions need a specific handling */
|
||
dbuf_putc (bc, OP_fclosure);
|
||
dbuf_put_u32 (bc, hf->cpool_idx);
|
||
|
||
{
|
||
int key_idx = fd_cpool_add (ctx, s, hf->var_name);
|
||
dbuf_putc (bc, OP_define_func);
|
||
dbuf_put_u32 (bc, key_idx);
|
||
dbuf_putc (bc, flags);
|
||
}
|
||
|
||
goto done_global_var;
|
||
} else {
|
||
if (hf->is_lexical) { flags |= DEFINE_GLOBAL_LEX_VAR; }
|
||
int key_idx = fd_cpool_add (ctx, s, hf->var_name);
|
||
dbuf_putc (bc, OP_define_var);
|
||
dbuf_put_u32 (bc, key_idx);
|
||
dbuf_putc (bc, flags);
|
||
}
|
||
}
|
||
if (hf->cpool_idx >= 0 || force_init) {
|
||
if (hf->cpool_idx >= 0) {
|
||
dbuf_putc (bc, OP_fclosure);
|
||
dbuf_put_u32 (bc, hf->cpool_idx);
|
||
if (js_key_equal_str (hf->var_name, "_default_")) {
|
||
/* set default export function name */
|
||
int name_idx = fd_cpool_add (ctx, s, JS_KEY_default);
|
||
dbuf_putc (bc, OP_set_name);
|
||
dbuf_put_u32 (bc, name_idx);
|
||
}
|
||
} else {
|
||
dbuf_putc (bc, OP_null);
|
||
}
|
||
if (has_closure == 2) {
|
||
int slot, depth = compute_closure_depth_slot(s, idx, &slot);
|
||
dbuf_putc (bc, OP_set_up);
|
||
dbuf_putc (bc, (uint8_t)depth);
|
||
dbuf_put_u16 (bc, (uint16_t)slot);
|
||
} else if (has_closure == 1) {
|
||
int key_idx = fd_cpool_add (ctx, s, hf->var_name);
|
||
dbuf_putc (bc, OP_define_field);
|
||
dbuf_put_u32 (bc, key_idx);
|
||
dbuf_putc (bc, OP_drop);
|
||
} else {
|
||
/* XXX: Check if variable is writable and enumerable */
|
||
int key_idx = fd_cpool_add (ctx, s, hf->var_name);
|
||
dbuf_putc (bc, OP_put_var);
|
||
dbuf_put_u32 (bc, key_idx);
|
||
}
|
||
}
|
||
done_global_var:;
|
||
}
|
||
|
||
js_free (ctx, s->global_vars);
|
||
s->global_vars = NULL;
|
||
s->global_var_count = 0;
|
||
s->global_var_size = 0;
|
||
}
|
||
|
||
static int skip_dead_code (JSFunctionDef *s, const uint8_t *bc_buf, int bc_len, int pos, int *linep) {
|
||
int op, len, label;
|
||
|
||
for (; pos < bc_len; pos += len) {
|
||
op = bc_buf[pos];
|
||
len = opcode_info[op].size;
|
||
if (op == OP_line_num) {
|
||
*linep = get_u32 (bc_buf + pos + 1);
|
||
} else if (op == OP_label) {
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
if (update_label (s, label, 0) > 0) break;
|
||
assert (s->label_slots[label].first_reloc == NULL);
|
||
} else {
|
||
/* XXX: output a warning for unreachable code? */
|
||
switch (opcode_info[op].fmt) {
|
||
case OP_FMT_label:
|
||
case OP_FMT_label_u16:
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
update_label (s, label, -1);
|
||
break;
|
||
case OP_FMT_key_label_u16:
|
||
label = get_u32 (bc_buf + pos + 5);
|
||
update_label (s, label, -1);
|
||
/* fall thru */
|
||
case OP_FMT_key:
|
||
case OP_FMT_key_u8:
|
||
case OP_FMT_key_u16:
|
||
/* Key operand is a cpool index; cpool values freed separately */
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return pos;
|
||
}
|
||
|
||
static int get_label_pos (JSFunctionDef *s, int label) {
|
||
int i, pos;
|
||
for (i = 0; i < 20; i++) {
|
||
pos = s->label_slots[label].pos;
|
||
for (;;) {
|
||
switch (s->byte_code.buf[pos]) {
|
||
case OP_line_num:
|
||
case OP_label:
|
||
pos += 5;
|
||
continue;
|
||
case OP_goto:
|
||
label = get_u32 (s->byte_code.buf + pos + 1);
|
||
break;
|
||
default:
|
||
return pos;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
return pos;
|
||
}
|
||
|
||
/* convert global variable accesses to local variables or closure
|
||
variables when necessary */
|
||
static __exception int resolve_variables (JSContext *ctx, JSFunctionDef *s) {
|
||
int pos, pos_next, bc_len, op, len, i, idx, line_num;
|
||
uint8_t *bc_buf;
|
||
JSValue var_name;
|
||
int cpool_idx;
|
||
DynBuf bc_out;
|
||
CodeContext cc;
|
||
int scope;
|
||
|
||
cc.bc_buf = bc_buf = s->byte_code.buf;
|
||
cc.bc_len = bc_len = s->byte_code.size;
|
||
js_dbuf_init (ctx, &bc_out);
|
||
|
||
/* first pass for runtime checks (must be done before the
|
||
variables are created) */
|
||
for (i = 0; i < s->global_var_count; i++) {
|
||
JSGlobalVar *hf = &s->global_vars[i];
|
||
int flags;
|
||
|
||
/* check if global variable (XXX: simplify) */
|
||
for (idx = 0; idx < s->closure_var_count; idx++) {
|
||
JSClosureVar *cv = &s->closure_var[idx];
|
||
if (js_key_equal (cv->var_name, hf->var_name)) {
|
||
goto next;
|
||
}
|
||
if (js_key_equal_str (cv->var_name, "<var>")
|
||
|| js_key_equal_str (cv->var_name, "<arg_var>"))
|
||
goto next;
|
||
}
|
||
|
||
{
|
||
int key_idx = fd_cpool_add (ctx, s, hf->var_name);
|
||
dbuf_putc (&bc_out, OP_check_define_var);
|
||
dbuf_put_u32 (&bc_out, key_idx);
|
||
}
|
||
flags = 0;
|
||
if (hf->is_lexical) flags |= DEFINE_GLOBAL_LEX_VAR;
|
||
if (hf->cpool_idx >= 0) flags |= DEFINE_GLOBAL_FUNC_VAR;
|
||
dbuf_putc (&bc_out, flags);
|
||
next:;
|
||
}
|
||
|
||
line_num = 0; /* avoid warning */
|
||
for (pos = 0; pos < bc_len; pos = pos_next) {
|
||
op = bc_buf[pos];
|
||
len = opcode_info[op].size;
|
||
pos_next = pos + len;
|
||
switch (op) {
|
||
case OP_line_num:
|
||
line_num = get_u32 (bc_buf + pos + 1);
|
||
s->line_number_size++;
|
||
goto no_change;
|
||
|
||
case OP_scope_get_var_checkthis:
|
||
case OP_scope_get_var_undef:
|
||
case OP_scope_get_var:
|
||
case OP_scope_put_var:
|
||
case OP_scope_delete_var:
|
||
case OP_scope_put_var_init:
|
||
cpool_idx = get_u32 (bc_buf + pos + 1);
|
||
var_name = s->cpool[cpool_idx];
|
||
scope = get_u16 (bc_buf + pos + 5);
|
||
pos_next = resolve_scope_var (ctx, s, var_name, scope, op, &bc_out, NULL, NULL, pos_next);
|
||
break;
|
||
case OP_gosub:
|
||
s->jump_size++;
|
||
if (OPTIMIZE) {
|
||
/* remove calls to empty finalizers */
|
||
int label;
|
||
LabelSlot *ls;
|
||
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
assert (label >= 0 && label < s->label_count);
|
||
ls = &s->label_slots[label];
|
||
if (code_match (&cc, ls->pos, OP_ret, -1)) {
|
||
ls->ref_count--;
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
case OP_drop:
|
||
if (0) {
|
||
/* remove drops before return_undef */
|
||
/* do not perform this optimization in pass2 because
|
||
it breaks patterns recognised in resolve_labels */
|
||
int pos1 = pos_next;
|
||
int line1 = line_num;
|
||
while (code_match (&cc, pos1, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line1 = cc.line_num;
|
||
pos1 = cc.pos;
|
||
}
|
||
if (code_match (&cc, pos1, OP_return_undef, -1)) {
|
||
pos_next = pos1;
|
||
if (line1 != -1 && line1 != line_num) {
|
||
line_num = line1;
|
||
s->line_number_size++;
|
||
dbuf_putc (&bc_out, OP_line_num);
|
||
dbuf_put_u32 (&bc_out, line_num);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
case OP_insert3:
|
||
if (OPTIMIZE) {
|
||
/* Transformation: insert3 put_array_el drop -> put_array_el */
|
||
if (code_match (&cc, pos_next, OP_put_array_el, OP_drop, -1)) {
|
||
dbuf_putc (&bc_out, cc.op);
|
||
pos_next = cc.pos;
|
||
if (cc.line_num != -1 && cc.line_num != line_num) {
|
||
line_num = cc.line_num;
|
||
s->line_number_size++;
|
||
dbuf_putc (&bc_out, OP_line_num);
|
||
dbuf_put_u32 (&bc_out, line_num);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_goto:
|
||
s->jump_size++;
|
||
/* fall thru */
|
||
case OP_tail_call:
|
||
case OP_tail_call_method:
|
||
case OP_return:
|
||
case OP_return_undef:
|
||
case OP_throw:
|
||
case OP_throw_error:
|
||
case OP_ret:
|
||
if (OPTIMIZE) {
|
||
/* remove dead code */
|
||
int line = -1;
|
||
dbuf_put (&bc_out, bc_buf + pos, len);
|
||
pos = skip_dead_code (s, bc_buf, bc_len, pos + len, &line);
|
||
pos_next = pos;
|
||
if (pos < bc_len && line >= 0 && line_num != line) {
|
||
line_num = line;
|
||
s->line_number_size++;
|
||
dbuf_putc (&bc_out, OP_line_num);
|
||
dbuf_put_u32 (&bc_out, line_num);
|
||
}
|
||
break;
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_label: {
|
||
int label;
|
||
LabelSlot *ls;
|
||
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
assert (label >= 0 && label < s->label_count);
|
||
ls = &s->label_slots[label];
|
||
ls->pos2 = bc_out.size + opcode_info[op].size;
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_enter_scope: {
|
||
int scope_idx, scope = get_u16 (bc_buf + pos + 1);
|
||
|
||
if (scope == s->body_scope) {
|
||
instantiate_hoisted_definitions (ctx, s, &bc_out);
|
||
}
|
||
|
||
for (scope_idx = s->scopes[scope].first; scope_idx >= 0;) {
|
||
JSVarDef *vd = &s->vars[scope_idx];
|
||
if (vd->scope_level != scope) break;
|
||
|
||
if (vd->var_kind == JS_VAR_FUNCTION_DECL
|
||
|| vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) {
|
||
/* Initialize lexical variable upon entering scope */
|
||
dbuf_putc (&bc_out, OP_fclosure);
|
||
dbuf_put_u32 (&bc_out, vd->func_pool_idx);
|
||
dbuf_putc (&bc_out, OP_put_loc);
|
||
dbuf_put_u16 (&bc_out, scope_idx);
|
||
} else {
|
||
/* XXX: should check if variable can be used
|
||
before initialization */
|
||
dbuf_putc (&bc_out, OP_set_loc_uninitialized);
|
||
dbuf_put_u16 (&bc_out, scope_idx);
|
||
}
|
||
|
||
scope_idx = vd->scope_next;
|
||
}
|
||
} break;
|
||
|
||
case OP_leave_scope:
|
||
/* With outer_frame model, captured variables don't need closing */
|
||
break;
|
||
|
||
case OP_set_name: {
|
||
/* remove dummy set_name opcodes */
|
||
int name_idx = get_u32 (bc_buf + pos + 1);
|
||
if (JS_IsNull (s->cpool[name_idx])) break;
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_if_false:
|
||
case OP_if_true:
|
||
case OP_catch:
|
||
s->jump_size++;
|
||
goto no_change;
|
||
|
||
case OP_dup:
|
||
if (OPTIMIZE) {
|
||
/* Transformation: dup if_false(l1) drop, l1: if_false(l2) ->
|
||
* if_false(l2) */
|
||
/* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2)
|
||
*/
|
||
if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), OP_drop, -1)) {
|
||
int lab0, lab1, op1, pos1, line1, pos2;
|
||
lab0 = lab1 = cc.label;
|
||
assert (lab1 >= 0 && lab1 < s->label_count);
|
||
op1 = cc.op;
|
||
pos1 = cc.pos;
|
||
line1 = cc.line_num;
|
||
while (code_match (&cc, (pos2 = get_label_pos (s, lab1)), OP_dup, op1, OP_drop, -1)) {
|
||
lab1 = cc.label;
|
||
}
|
||
if (code_match (&cc, pos2, op1, -1)) {
|
||
s->jump_size++;
|
||
update_label (s, lab0, -1);
|
||
update_label (s, cc.label, +1);
|
||
dbuf_putc (&bc_out, op1);
|
||
dbuf_put_u32 (&bc_out, cc.label);
|
||
pos_next = pos1;
|
||
if (line1 != -1 && line1 != line_num) {
|
||
line_num = line1;
|
||
s->line_number_size++;
|
||
dbuf_putc (&bc_out, OP_line_num);
|
||
dbuf_put_u32 (&bc_out, line_num);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_nop:
|
||
/* remove erased code */
|
||
break;
|
||
case OP_set_class_name:
|
||
/* only used during parsing */
|
||
break;
|
||
|
||
case OP_get_field_opt_chain: /* equivalent to OP_get_field */
|
||
{
|
||
int key_idx = get_u32 (bc_buf + pos + 1);
|
||
dbuf_putc (&bc_out, OP_get_field);
|
||
dbuf_put_u32 (&bc_out, key_idx);
|
||
} break;
|
||
case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */
|
||
dbuf_putc (&bc_out, OP_get_array_el);
|
||
break;
|
||
|
||
default:
|
||
no_change:
|
||
dbuf_put (&bc_out, bc_buf + pos, len);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* set the new byte code */
|
||
dbuf_free (&s->byte_code);
|
||
s->byte_code = bc_out;
|
||
if (dbuf_error (&s->byte_code)) {
|
||
JS_ThrowOutOfMemory (ctx);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* the pc2line table gives a source position for each PC value */
|
||
static void add_pc2line_info (JSFunctionDef *s, uint32_t pc, uint32_t source_pos) {
|
||
if (s->line_number_slots != NULL
|
||
&& s->line_number_count < s->line_number_size
|
||
&& pc >= s->line_number_last_pc && source_pos != s->line_number_last) {
|
||
s->line_number_slots[s->line_number_count].pc = pc;
|
||
s->line_number_slots[s->line_number_count].source_pos = source_pos;
|
||
s->line_number_count++;
|
||
s->line_number_last_pc = pc;
|
||
s->line_number_last = source_pos;
|
||
}
|
||
}
|
||
|
||
/* XXX: could use a more compact storage */
|
||
/* XXX: get_line_col_cached() is slow. For more predictable
|
||
performance, line/cols could be stored every N source
|
||
bytes. Alternatively, get_line_col_cached() could be issued in
|
||
emit_source_pos() so that the deltas are more likely to be
|
||
small. */
|
||
static void compute_pc2line_info (JSFunctionDef *s) {
|
||
if (!s->strip_debug) {
|
||
int last_line_num, last_col_num;
|
||
uint32_t last_pc = 0;
|
||
int i, line_num, col_num;
|
||
const uint8_t *buf_start = s->get_line_col_cache->buf_start;
|
||
js_dbuf_init (s->ctx, &s->pc2line);
|
||
|
||
last_line_num = get_line_col_cached (s->get_line_col_cache, &last_col_num, buf_start + s->source_pos);
|
||
dbuf_put_leb128 (&s->pc2line, last_line_num); /* line number minus 1 */
|
||
dbuf_put_leb128 (&s->pc2line, last_col_num); /* column number minus 1 */
|
||
|
||
for (i = 0; i < s->line_number_count; i++) {
|
||
uint32_t pc = s->line_number_slots[i].pc;
|
||
uint32_t source_pos = s->line_number_slots[i].source_pos;
|
||
int diff_pc, diff_line, diff_col;
|
||
|
||
if (source_pos == -1) continue;
|
||
diff_pc = pc - last_pc;
|
||
if (diff_pc < 0) continue;
|
||
|
||
line_num = get_line_col_cached (s->get_line_col_cache, &col_num, buf_start + source_pos);
|
||
diff_line = line_num - last_line_num;
|
||
diff_col = col_num - last_col_num;
|
||
if (diff_line == 0 && diff_col == 0) continue;
|
||
|
||
if (diff_line >= PC2LINE_BASE && diff_line < PC2LINE_BASE + PC2LINE_RANGE
|
||
&& diff_pc <= PC2LINE_DIFF_PC_MAX) {
|
||
dbuf_putc (&s->pc2line, (diff_line - PC2LINE_BASE) + diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST);
|
||
} else {
|
||
/* longer encoding */
|
||
dbuf_putc (&s->pc2line, 0);
|
||
dbuf_put_leb128 (&s->pc2line, diff_pc);
|
||
dbuf_put_sleb128 (&s->pc2line, diff_line);
|
||
}
|
||
dbuf_put_sleb128 (&s->pc2line, diff_col);
|
||
|
||
last_pc = pc;
|
||
last_line_num = line_num;
|
||
last_col_num = col_num;
|
||
}
|
||
}
|
||
}
|
||
|
||
static RelocEntry *add_reloc (JSContext *ctx, LabelSlot *ls, uint32_t addr, int size) {
|
||
RelocEntry *re;
|
||
(void)ctx;
|
||
re = pjs_malloc (sizeof (*re));
|
||
if (!re) return NULL;
|
||
re->addr = addr;
|
||
re->size = size;
|
||
re->next = ls->first_reloc;
|
||
ls->first_reloc = re;
|
||
return re;
|
||
}
|
||
|
||
static BOOL code_has_label (CodeContext *s, int pos, int label) {
|
||
while (pos < s->bc_len) {
|
||
int op = s->bc_buf[pos];
|
||
if (op == OP_line_num) {
|
||
pos += 5;
|
||
continue;
|
||
}
|
||
if (op == OP_label) {
|
||
int lab = get_u32 (s->bc_buf + pos + 1);
|
||
if (lab == label) return TRUE;
|
||
pos += 5;
|
||
continue;
|
||
}
|
||
if (op == OP_goto) {
|
||
int lab = get_u32 (s->bc_buf + pos + 1);
|
||
if (lab == label) return TRUE;
|
||
}
|
||
break;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
/* return the target label, following the OP_goto jumps
|
||
the first opcode at destination is stored in *pop
|
||
*/
|
||
static int find_jump_target (JSFunctionDef *s, int label0, int *pop, int *pline) {
|
||
int i, pos, op, label;
|
||
|
||
label = label0;
|
||
update_label (s, label, -1);
|
||
for (i = 0; i < 10; i++) {
|
||
assert (label >= 0 && label < s->label_count);
|
||
pos = s->label_slots[label].pos2;
|
||
for (;;) {
|
||
switch (op = s->byte_code.buf[pos]) {
|
||
case OP_line_num:
|
||
if (pline) *pline = get_u32 (s->byte_code.buf + pos + 1);
|
||
/* fall thru */
|
||
case OP_label:
|
||
pos += opcode_info[op].size;
|
||
continue;
|
||
case OP_goto:
|
||
label = get_u32 (s->byte_code.buf + pos + 1);
|
||
break;
|
||
case OP_drop:
|
||
/* ignore drop opcodes if followed by OP_return_undef */
|
||
while (s->byte_code.buf[++pos] == OP_drop)
|
||
continue;
|
||
if (s->byte_code.buf[pos] == OP_return_undef) op = OP_return_undef;
|
||
/* fall thru */
|
||
default:
|
||
goto done;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
/* cycle detected, could issue a warning */
|
||
/* XXX: the combination of find_jump_target() and skip_dead_code()
|
||
seems incorrect with cyclic labels. See for exemple:
|
||
|
||
for (;;) {
|
||
l:break l;
|
||
l:break l;
|
||
l:break l;
|
||
l:break l;
|
||
}
|
||
|
||
Avoiding changing the target is just a workaround and might not
|
||
suffice to completely fix the problem. */
|
||
label = label0;
|
||
done:
|
||
*pop = op;
|
||
update_label (s, label, +1);
|
||
return label;
|
||
}
|
||
|
||
static void push_short_int (DynBuf *bc_out, int val) {
|
||
#if SHORT_OPCODES
|
||
if (val >= -1 && val <= 7) {
|
||
dbuf_putc (bc_out, OP_push_0 + val);
|
||
return;
|
||
}
|
||
if (val == (int8_t)val) {
|
||
dbuf_putc (bc_out, OP_push_i8);
|
||
dbuf_putc (bc_out, val);
|
||
return;
|
||
}
|
||
if (val == (int16_t)val) {
|
||
dbuf_putc (bc_out, OP_push_i16);
|
||
dbuf_put_u16 (bc_out, val);
|
||
return;
|
||
}
|
||
#endif
|
||
dbuf_putc (bc_out, OP_push_i32);
|
||
dbuf_put_u32 (bc_out, val);
|
||
}
|
||
|
||
static void put_short_code (DynBuf *bc_out, int op, int idx) {
|
||
#if SHORT_OPCODES
|
||
if (idx < 4) {
|
||
switch (op) {
|
||
case OP_get_loc:
|
||
dbuf_putc (bc_out, OP_get_loc0 + idx);
|
||
return;
|
||
case OP_put_loc:
|
||
dbuf_putc (bc_out, OP_put_loc0 + idx);
|
||
return;
|
||
case OP_set_loc:
|
||
dbuf_putc (bc_out, OP_set_loc0 + idx);
|
||
return;
|
||
case OP_get_arg:
|
||
dbuf_putc (bc_out, OP_get_arg0 + idx);
|
||
return;
|
||
case OP_put_arg:
|
||
dbuf_putc (bc_out, OP_put_arg0 + idx);
|
||
return;
|
||
case OP_set_arg:
|
||
dbuf_putc (bc_out, OP_set_arg0 + idx);
|
||
return;
|
||
case OP_call:
|
||
dbuf_putc (bc_out, OP_call0 + idx);
|
||
return;
|
||
}
|
||
}
|
||
if (idx < 256) {
|
||
switch (op) {
|
||
case OP_get_loc:
|
||
dbuf_putc (bc_out, OP_get_loc8);
|
||
dbuf_putc (bc_out, idx);
|
||
return;
|
||
case OP_put_loc:
|
||
dbuf_putc (bc_out, OP_put_loc8);
|
||
dbuf_putc (bc_out, idx);
|
||
return;
|
||
case OP_set_loc:
|
||
dbuf_putc (bc_out, OP_set_loc8);
|
||
dbuf_putc (bc_out, idx);
|
||
return;
|
||
}
|
||
}
|
||
#endif
|
||
dbuf_putc (bc_out, op);
|
||
dbuf_put_u16 (bc_out, idx);
|
||
}
|
||
|
||
/* peephole optimizations and resolve goto/labels */
|
||
static __exception int resolve_labels (JSContext *ctx, JSFunctionDef *s) {
|
||
int pos, pos_next, bc_len, op, op1, len, i, line_num;
|
||
const uint8_t *bc_buf;
|
||
DynBuf bc_out;
|
||
LabelSlot *label_slots, *ls;
|
||
RelocEntry *re, *re_next;
|
||
CodeContext cc;
|
||
int label;
|
||
#if SHORT_OPCODES
|
||
JumpSlot *jp;
|
||
#endif
|
||
|
||
label_slots = s->label_slots;
|
||
|
||
line_num = s->source_pos;
|
||
|
||
cc.bc_buf = bc_buf = s->byte_code.buf;
|
||
cc.bc_len = bc_len = s->byte_code.size;
|
||
js_dbuf_init (ctx, &bc_out);
|
||
|
||
#if SHORT_OPCODES
|
||
if (s->jump_size) {
|
||
s->jump_slots
|
||
= pjs_mallocz (sizeof (*s->jump_slots) * s->jump_size);
|
||
if (s->jump_slots == NULL) return -1;
|
||
}
|
||
#endif
|
||
/* XXX: Should skip this phase if not generating SHORT_OPCODES */
|
||
if (s->line_number_size && !s->strip_debug) {
|
||
s->line_number_slots = pjs_mallocz (sizeof (*s->line_number_slots) * s->line_number_size);
|
||
if (s->line_number_slots == NULL) return -1;
|
||
s->line_number_last = s->source_pos;
|
||
s->line_number_last_pc = 0;
|
||
}
|
||
|
||
/* initialize the 'this.active_func' variable if needed */
|
||
if (s->this_active_func_var_idx >= 0) {
|
||
dbuf_putc (&bc_out, OP_special_object);
|
||
dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC);
|
||
put_short_code (&bc_out, OP_put_loc, s->this_active_func_var_idx);
|
||
}
|
||
/* initialize the 'this' variable if needed */
|
||
if (s->this_var_idx >= 0) {
|
||
dbuf_putc (&bc_out, OP_push_this);
|
||
put_short_code (&bc_out, OP_put_loc, s->this_var_idx);
|
||
}
|
||
/* initialize a reference to the current function if needed */
|
||
if (s->func_var_idx >= 0) {
|
||
dbuf_putc (&bc_out, OP_special_object);
|
||
dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC);
|
||
put_short_code (&bc_out, OP_put_loc, s->func_var_idx);
|
||
}
|
||
/* initialize the variable environment object if needed */
|
||
if (s->var_object_idx >= 0) {
|
||
dbuf_putc (&bc_out, OP_special_object);
|
||
dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT);
|
||
put_short_code (&bc_out, OP_put_loc, s->var_object_idx);
|
||
}
|
||
if (s->arg_var_object_idx >= 0) {
|
||
dbuf_putc (&bc_out, OP_special_object);
|
||
dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT);
|
||
put_short_code (&bc_out, OP_put_loc, s->arg_var_object_idx);
|
||
}
|
||
|
||
for (pos = 0; pos < bc_len; pos = pos_next) {
|
||
int val;
|
||
op = bc_buf[pos];
|
||
len = opcode_info[op].size;
|
||
pos_next = pos + len;
|
||
switch (op) {
|
||
case OP_line_num:
|
||
/* line number info (for debug). We put it in a separate
|
||
compressed table to reduce memory usage and get better
|
||
performance */
|
||
line_num = get_u32 (bc_buf + pos + 1);
|
||
break;
|
||
|
||
case OP_label: {
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
assert (label >= 0 && label < s->label_count);
|
||
ls = &label_slots[label];
|
||
assert (ls->addr == -1);
|
||
ls->addr = bc_out.size;
|
||
/* resolve the relocation entries */
|
||
for (re = ls->first_reloc; re != NULL; re = re_next) {
|
||
int diff = ls->addr - re->addr;
|
||
re_next = re->next;
|
||
switch (re->size) {
|
||
case 4:
|
||
put_u32 (bc_out.buf + re->addr, diff);
|
||
break;
|
||
case 2:
|
||
assert (diff == (int16_t)diff);
|
||
put_u16 (bc_out.buf + re->addr, diff);
|
||
break;
|
||
case 1:
|
||
assert (diff == (int8_t)diff);
|
||
put_u8 (bc_out.buf + re->addr, diff);
|
||
break;
|
||
}
|
||
pjs_free (re);
|
||
}
|
||
ls->first_reloc = NULL;
|
||
} break;
|
||
|
||
case OP_call:
|
||
case OP_call_method: {
|
||
/* detect and transform tail calls */
|
||
int argc;
|
||
argc = get_u16 (bc_buf + pos + 1);
|
||
if (code_match (&cc, pos_next, OP_return, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op + 1, argc);
|
||
pos_next = skip_dead_code (s, bc_buf, bc_len, cc.pos, &line_num);
|
||
break;
|
||
}
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op, argc);
|
||
break;
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_return:
|
||
case OP_return_undef:
|
||
case OP_throw:
|
||
case OP_throw_error:
|
||
pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num);
|
||
goto no_change;
|
||
|
||
case OP_goto:
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
has_goto:
|
||
if (OPTIMIZE) {
|
||
int line1 = -1;
|
||
/* Use custom matcher because multiple labels can follow */
|
||
label = find_jump_target (s, label, &op1, &line1);
|
||
if (code_has_label (&cc, pos_next, label)) {
|
||
/* jump to next instruction: remove jump */
|
||
update_label (s, label, -1);
|
||
break;
|
||
}
|
||
if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) {
|
||
/* jump to return/throw: remove jump, append return/throw */
|
||
/* updating the line number obfuscates assembly listing */
|
||
// if (line1 != -1) line_num = line1;
|
||
update_label (s, label, -1);
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, op1);
|
||
pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num);
|
||
break;
|
||
}
|
||
/* XXX: should duplicate single instructions followed by goto or return
|
||
*/
|
||
/* For example, can match one of these followed by return:
|
||
push_i32 / push_const / push_atom_value / get_var /
|
||
undefined / null / push_false / push_true / get_ref_value /
|
||
get_loc / get_arg / get_var_ref
|
||
*/
|
||
}
|
||
goto has_label;
|
||
|
||
case OP_gosub:
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
if (0 && OPTIMIZE) {
|
||
label = find_jump_target (s, label, &op1, NULL);
|
||
if (op1 == OP_ret) {
|
||
update_label (s, label, -1);
|
||
/* empty finally clause: remove gosub */
|
||
break;
|
||
}
|
||
}
|
||
goto has_label;
|
||
|
||
case OP_catch:
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
goto has_label;
|
||
|
||
case OP_if_true:
|
||
case OP_if_false:
|
||
label = get_u32 (bc_buf + pos + 1);
|
||
if (OPTIMIZE) {
|
||
label = find_jump_target (s, label, &op1, NULL);
|
||
/* transform if_false/if_true(l1) label(l1) -> drop label(l1) */
|
||
if (code_has_label (&cc, pos_next, label)) {
|
||
update_label (s, label, -1);
|
||
dbuf_putc (&bc_out, OP_drop);
|
||
break;
|
||
}
|
||
/* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1)
|
||
*/
|
||
if (code_match (&cc, pos_next, OP_goto, -1)) {
|
||
int pos1 = cc.pos;
|
||
int line1 = cc.line_num;
|
||
if (code_has_label (&cc, pos1, label)) {
|
||
if (line1 != -1) line_num = line1;
|
||
pos_next = pos1;
|
||
update_label (s, label, -1);
|
||
label = cc.label;
|
||
op ^= OP_if_true ^ OP_if_false;
|
||
}
|
||
}
|
||
}
|
||
has_label:
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
if (op == OP_goto) {
|
||
pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num);
|
||
}
|
||
assert (label >= 0 && label < s->label_count);
|
||
ls = &label_slots[label];
|
||
#if SHORT_OPCODES
|
||
jp = &s->jump_slots[s->jump_count++];
|
||
jp->op = op;
|
||
jp->size = 4;
|
||
jp->pos = bc_out.size + 1;
|
||
jp->label = label;
|
||
|
||
if (ls->addr == -1) {
|
||
int diff = ls->pos2 - pos - 1;
|
||
if (diff < 128
|
||
&& (op == OP_if_false || op == OP_if_true || op == OP_goto)) {
|
||
jp->size = 1;
|
||
jp->op = OP_if_false8 + (op - OP_if_false);
|
||
dbuf_putc (&bc_out, OP_if_false8 + (op - OP_if_false));
|
||
dbuf_putc (&bc_out, 0);
|
||
if (!add_reloc (ctx, ls, bc_out.size - 1, 1)) goto fail;
|
||
break;
|
||
}
|
||
if (diff < 32768 && op == OP_goto) {
|
||
jp->size = 2;
|
||
jp->op = OP_goto16;
|
||
dbuf_putc (&bc_out, OP_goto16);
|
||
dbuf_put_u16 (&bc_out, 0);
|
||
if (!add_reloc (ctx, ls, bc_out.size - 2, 2)) goto fail;
|
||
break;
|
||
}
|
||
} else {
|
||
int diff = ls->addr - bc_out.size - 1;
|
||
if (diff == (int8_t)diff
|
||
&& (op == OP_if_false || op == OP_if_true || op == OP_goto)) {
|
||
jp->size = 1;
|
||
jp->op = OP_if_false8 + (op - OP_if_false);
|
||
dbuf_putc (&bc_out, OP_if_false8 + (op - OP_if_false));
|
||
dbuf_putc (&bc_out, diff);
|
||
break;
|
||
}
|
||
if (diff == (int16_t)diff && op == OP_goto) {
|
||
jp->size = 2;
|
||
jp->op = OP_goto16;
|
||
dbuf_putc (&bc_out, OP_goto16);
|
||
dbuf_put_u16 (&bc_out, diff);
|
||
break;
|
||
}
|
||
}
|
||
#endif
|
||
dbuf_putc (&bc_out, op);
|
||
dbuf_put_u32 (&bc_out, ls->addr - bc_out.size);
|
||
if (ls->addr == -1) {
|
||
/* unresolved yet: create a new relocation entry */
|
||
if (!add_reloc (ctx, ls, bc_out.size - 4, 4)) goto fail;
|
||
}
|
||
break;
|
||
case OP_drop:
|
||
if (OPTIMIZE) {
|
||
/* remove useless drops before return */
|
||
if (code_match (&cc, pos_next, OP_return_undef, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_null:
|
||
if (OPTIMIZE) {
|
||
/* 1) remove null; drop → (nothing) */
|
||
if (code_match (&cc, pos_next, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* 2) null; return → return_undef */
|
||
if (code_match (&cc, pos_next, OP_return, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_return_undef);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* 3) null; if_false/if_true → goto or nop */
|
||
if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) {
|
||
val = 0;
|
||
goto has_constant_test;
|
||
}
|
||
#if SHORT_OPCODES
|
||
/* 4a) null strict_eq → is_null */
|
||
if (code_match (&cc, pos_next, OP_strict_eq, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_is_null);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* 4b) null strict_neq; if_false/if_true → is_null + flipped branch */
|
||
if (code_match (&cc, pos_next, OP_strict_neq, M2 (OP_if_false, OP_if_true), -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_is_null);
|
||
pos_next = cc.pos;
|
||
label = cc.label;
|
||
op = cc.op ^ OP_if_false ^ OP_if_true;
|
||
goto has_label;
|
||
}
|
||
#endif
|
||
}
|
||
/* didn’t match any of the above? fall straight into the
|
||
OP_push_false / OP_push_true constant‐test code: */
|
||
case OP_push_false:
|
||
case OP_push_true:
|
||
if (OPTIMIZE) {
|
||
val = (op == OP_push_true);
|
||
if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) {
|
||
has_constant_test:
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
if (val == (cc.op - OP_if_false)) {
|
||
/* e.g. null if_false(L) → goto L */
|
||
pos_next = cc.pos;
|
||
op = OP_goto;
|
||
label = cc.label;
|
||
goto has_goto;
|
||
} else {
|
||
/* e.g. null if_true(L) → nop */
|
||
pos_next = cc.pos;
|
||
update_label (s, cc.label, -1);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_push_i32:
|
||
if (OPTIMIZE) {
|
||
/* transform i32(val) neg -> i32(-val) */
|
||
val = get_i32 (bc_buf + pos + 1);
|
||
if ((val != INT32_MIN && val != 0)
|
||
&& code_match (&cc, pos_next, OP_neg, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
if (code_match (&cc, cc.pos, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
} else {
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
push_short_int (&bc_out, -val);
|
||
}
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* remove push/drop pairs generated by the parser */
|
||
if (code_match (&cc, pos_next, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */
|
||
if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) {
|
||
val = (val != 0);
|
||
goto has_constant_test;
|
||
}
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
push_short_int (&bc_out, val);
|
||
break;
|
||
}
|
||
goto no_change;
|
||
|
||
#if SHORT_OPCODES
|
||
case OP_push_const:
|
||
case OP_fclosure:
|
||
if (OPTIMIZE) {
|
||
int idx = get_u32 (bc_buf + pos + 1);
|
||
if (idx < 256) {
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_push_const8 + op - OP_push_const);
|
||
dbuf_putc (&bc_out, idx);
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
#endif
|
||
case OP_to_propkey:
|
||
if (OPTIMIZE) {
|
||
/* remove redundant to_propkey opcodes when storing simple data */
|
||
if (code_match (&cc, pos_next, M2 (OP_get_loc, OP_get_arg), -1, OP_put_array_el, -1)
|
||
|| code_match (&cc, pos_next, M2 (OP_push_i32, OP_push_const), OP_put_array_el, -1)
|
||
|| code_match (&cc, pos_next, M4 (OP_null, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) {
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
case OP_insert2:
|
||
if (OPTIMIZE) {
|
||
/* Transformation:
|
||
insert2 put_field(a) drop -> put_field(a)
|
||
insert2 put_var_strict(a) drop -> put_var_strict(a)
|
||
*/
|
||
if (code_match (&cc, pos_next, M2 (OP_put_field, OP_put_var_strict), OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, cc.op);
|
||
dbuf_put_u32 (&bc_out, cc.label);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_dup:
|
||
if (OPTIMIZE) {
|
||
/* Transformation: dup put_x(n) drop -> put_x(n) */
|
||
int op1, line2 = -1;
|
||
/* Transformation: dup put_x(n) -> set_x(n) */
|
||
if (code_match (&cc, pos_next, M2 (OP_put_loc, OP_put_arg), -1, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
op1 = cc.op + 1; /* put_x -> set_x */
|
||
pos_next = cc.pos;
|
||
if (code_match (&cc, cc.pos, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
op1 -= 1; /* set_x drop -> put_x */
|
||
pos_next = cc.pos;
|
||
if (code_match (&cc, cc.pos, op1 - 1, cc.idx, -1)) {
|
||
line2 = cc.line_num; /* delay line number update */
|
||
op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */
|
||
pos_next = cc.pos;
|
||
}
|
||
}
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op1, cc.idx);
|
||
if (line2 >= 0) line_num = line2;
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_get_loc:
|
||
if (OPTIMIZE) {
|
||
/* transformation:
|
||
get_loc(n) post_dec put_loc(n) drop -> dec_loc(n)
|
||
get_loc(n) post_inc put_loc(n) drop -> inc_loc(n)
|
||
get_loc(n) dec dup put_loc(n) drop -> dec_loc(n)
|
||
get_loc(n) inc dup put_loc(n) drop -> inc_loc(n)
|
||
*/
|
||
int idx;
|
||
idx = get_u16 (bc_buf + pos + 1);
|
||
if (idx >= 256) goto no_change;
|
||
if (code_match (&cc, pos_next, M2 (OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1)
|
||
|| code_match (&cc, pos_next, M2 (OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc);
|
||
dbuf_putc (&bc_out, idx);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* transformation:
|
||
get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x)
|
||
add_loc(n)
|
||
*/
|
||
if (code_match (&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
push_short_int (&bc_out, cc.label);
|
||
dbuf_putc (&bc_out, OP_add_loc);
|
||
dbuf_putc (&bc_out, idx);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
/* transformation:
|
||
get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n)
|
||
get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n)
|
||
*/
|
||
if (code_match (&cc, pos_next, M2 (OP_get_loc, OP_get_arg), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, cc.op, cc.idx);
|
||
dbuf_putc (&bc_out, OP_add_loc);
|
||
dbuf_putc (&bc_out, idx);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op, idx);
|
||
break;
|
||
}
|
||
goto no_change;
|
||
#if SHORT_OPCODES
|
||
case OP_get_arg:
|
||
if (OPTIMIZE) {
|
||
int idx;
|
||
idx = get_u16 (bc_buf + pos + 1);
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op, idx);
|
||
break;
|
||
}
|
||
goto no_change;
|
||
#endif
|
||
case OP_put_loc:
|
||
case OP_put_arg:
|
||
if (OPTIMIZE) {
|
||
/* transformation: put_x(n) get_x(n) -> set_x(n) */
|
||
int idx;
|
||
idx = get_u16 (bc_buf + pos + 1);
|
||
if (code_match (&cc, pos_next, op - 1, idx, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op + 1, idx);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
put_short_code (&bc_out, op, idx);
|
||
break;
|
||
}
|
||
goto no_change;
|
||
|
||
case OP_post_inc:
|
||
case OP_post_dec:
|
||
if (OPTIMIZE) {
|
||
/* transformation:
|
||
post_inc put_x drop -> inc put_x
|
||
post_inc perm3 put_field drop -> inc put_field
|
||
post_inc perm3 put_var_strict drop -> inc put_var_strict
|
||
post_inc perm4 put_array_el drop -> inc put_array_el
|
||
*/
|
||
int op1, idx;
|
||
if (code_match (&cc, pos_next, M2 (OP_put_loc, OP_put_arg), -1, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
op1 = cc.op;
|
||
idx = cc.idx;
|
||
pos_next = cc.pos;
|
||
if (code_match (&cc, cc.pos, op1 - 1, idx, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */
|
||
pos_next = cc.pos;
|
||
}
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec));
|
||
put_short_code (&bc_out, op1, idx);
|
||
break;
|
||
}
|
||
if (code_match (&cc, pos_next, OP_perm3, M2 (OP_put_field, OP_put_var_strict), OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec));
|
||
dbuf_putc (&bc_out, cc.op);
|
||
dbuf_put_u32 (&bc_out, cc.label);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
if (code_match (&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) {
|
||
if (cc.line_num >= 0) line_num = cc.line_num;
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec));
|
||
dbuf_putc (&bc_out, OP_put_array_el);
|
||
pos_next = cc.pos;
|
||
break;
|
||
}
|
||
}
|
||
goto no_change;
|
||
|
||
default:
|
||
no_change:
|
||
add_pc2line_info (s, bc_out.size, line_num);
|
||
dbuf_put (&bc_out, bc_buf + pos, len);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* check that there were no missing labels */
|
||
for (i = 0; i < s->label_count; i++) {
|
||
assert (label_slots[i].first_reloc == NULL);
|
||
}
|
||
#if SHORT_OPCODES
|
||
if (OPTIMIZE) {
|
||
/* more jump optimizations */
|
||
int patch_offsets = 0;
|
||
for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) {
|
||
LabelSlot *ls;
|
||
JumpSlot *jp1;
|
||
int j, pos, diff, delta;
|
||
|
||
delta = 3;
|
||
switch (op = jp->op) {
|
||
case OP_goto16:
|
||
delta = 1;
|
||
/* fall thru */
|
||
case OP_if_false:
|
||
case OP_if_true:
|
||
case OP_goto:
|
||
pos = jp->pos;
|
||
diff = s->label_slots[jp->label].addr - pos;
|
||
if (diff >= -128 && diff <= 127 + delta) {
|
||
// put_u8(bc_out.buf + pos, diff);
|
||
jp->size = 1;
|
||
if (op == OP_goto16) {
|
||
bc_out.buf[pos - 1] = jp->op = OP_goto8;
|
||
} else {
|
||
bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false);
|
||
}
|
||
goto shrink;
|
||
} else if (diff == (int16_t)diff && op == OP_goto) {
|
||
// put_u16(bc_out.buf + pos, diff);
|
||
jp->size = 2;
|
||
delta = 2;
|
||
bc_out.buf[pos - 1] = jp->op = OP_goto16;
|
||
shrink:
|
||
/* XXX: should reduce complexity, using 2 finger copy scheme */
|
||
memmove (bc_out.buf + pos + jp->size,
|
||
bc_out.buf + pos + jp->size + delta,
|
||
bc_out.size - pos - jp->size - delta);
|
||
bc_out.size -= delta;
|
||
patch_offsets++;
|
||
for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) {
|
||
if (ls->addr > pos) ls->addr -= delta;
|
||
}
|
||
for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) {
|
||
if (jp1->pos > pos) jp1->pos -= delta;
|
||
}
|
||
for (j = 0; j < s->line_number_count; j++) {
|
||
if (s->line_number_slots[j].pc > pos)
|
||
s->line_number_slots[j].pc -= delta;
|
||
}
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
if (patch_offsets) {
|
||
JumpSlot *jp1;
|
||
int j;
|
||
for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) {
|
||
int diff1 = s->label_slots[jp1->label].addr - jp1->pos;
|
||
switch (jp1->size) {
|
||
case 1:
|
||
put_u8 (bc_out.buf + jp1->pos, diff1);
|
||
break;
|
||
case 2:
|
||
put_u16 (bc_out.buf + jp1->pos, diff1);
|
||
break;
|
||
case 4:
|
||
put_u32 (bc_out.buf + jp1->pos, diff1);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
pjs_free (s->jump_slots);
|
||
s->jump_slots = NULL;
|
||
#endif
|
||
pjs_free (s->label_slots);
|
||
s->label_slots = NULL;
|
||
/* XXX: should delay until copying to runtime bytecode function */
|
||
compute_pc2line_info (s);
|
||
pjs_free (s->line_number_slots);
|
||
s->line_number_slots = NULL;
|
||
/* set the new byte code */
|
||
dbuf_free (&s->byte_code);
|
||
s->byte_code = bc_out;
|
||
s->use_short_opcodes = TRUE;
|
||
if (dbuf_error (&s->byte_code)) {
|
||
JS_ThrowOutOfMemory (ctx);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
fail:
|
||
/* XXX: not safe */
|
||
dbuf_free (&bc_out);
|
||
return -1;
|
||
}
|
||
|
||
/* compute the maximum stack size needed by the function */
|
||
|
||
typedef struct StackSizeState {
|
||
int bc_len;
|
||
int stack_len_max;
|
||
uint16_t *stack_level_tab;
|
||
int32_t *catch_pos_tab;
|
||
int *pc_stack;
|
||
int pc_stack_len;
|
||
int pc_stack_size;
|
||
} StackSizeState;
|
||
|
||
/* 'op' is only used for error indication */
|
||
static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int op, int stack_len, int catch_pos) {
|
||
if ((unsigned)pos >= s->bc_len) {
|
||
JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
|
||
return -1;
|
||
}
|
||
if (stack_len > s->stack_len_max) {
|
||
s->stack_len_max = stack_len;
|
||
if (s->stack_len_max > JS_STACK_SIZE_MAX) {
|
||
JS_ThrowInternalError (ctx, "stack overflow (op=%d, pc=%d)", op, pos);
|
||
return -1;
|
||
}
|
||
}
|
||
if (s->stack_level_tab[pos] != 0xffff) {
|
||
/* already explored: check that the stack size is consistent */
|
||
if (s->stack_level_tab[pos] != stack_len) {
|
||
JS_ThrowInternalError (ctx, "inconsistent stack size: %d %d (pc=%d)", s->stack_level_tab[pos], stack_len, pos);
|
||
return -1;
|
||
} else if (s->catch_pos_tab[pos] != catch_pos) {
|
||
JS_ThrowInternalError (ctx, "inconsistent catch position: %d %d (pc=%d)", s->catch_pos_tab[pos], catch_pos, pos);
|
||
return -1;
|
||
} else {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/* mark as explored and store the stack size */
|
||
s->stack_level_tab[pos] = stack_len;
|
||
s->catch_pos_tab[pos] = catch_pos;
|
||
|
||
/* queue the new PC to explore */
|
||
if (pjs_resize_array ((void **)&s->pc_stack, sizeof (s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1))
|
||
return -1;
|
||
s->pc_stack[s->pc_stack_len++] = pos;
|
||
return 0;
|
||
}
|
||
|
||
static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, int *pstack_size) {
|
||
StackSizeState s_s, *s = &s_s;
|
||
int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level;
|
||
const JSOpCode *oi;
|
||
const uint8_t *bc_buf;
|
||
|
||
bc_buf = fd->byte_code.buf;
|
||
s->bc_len = fd->byte_code.size;
|
||
/* bc_len > 0 */
|
||
s->stack_level_tab
|
||
= pjs_malloc (sizeof (s->stack_level_tab[0]) * s->bc_len);
|
||
if (!s->stack_level_tab) return -1;
|
||
for (i = 0; i < s->bc_len; i++)
|
||
s->stack_level_tab[i] = 0xffff;
|
||
s->pc_stack = NULL;
|
||
s->catch_pos_tab = pjs_malloc (sizeof (s->catch_pos_tab[0]) * s->bc_len);
|
||
if (!s->catch_pos_tab) goto fail;
|
||
|
||
s->stack_len_max = 0;
|
||
s->pc_stack_len = 0;
|
||
s->pc_stack_size = 0;
|
||
|
||
/* breadth-first graph exploration */
|
||
if (ss_check (ctx, s, 0, OP_invalid, 0, -1)) goto fail;
|
||
|
||
while (s->pc_stack_len > 0) {
|
||
pos = s->pc_stack[--s->pc_stack_len];
|
||
stack_len = s->stack_level_tab[pos];
|
||
catch_pos = s->catch_pos_tab[pos];
|
||
op = bc_buf[pos];
|
||
if (op == 0 || op >= OP_COUNT) {
|
||
JS_ThrowInternalError (ctx, "invalid opcode (op=%d, pc=%d)", op, pos);
|
||
goto fail;
|
||
}
|
||
oi = &short_opcode_info (op);
|
||
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64)
|
||
printf ("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos);
|
||
#endif
|
||
pos_next = pos + oi->size;
|
||
if (pos_next > s->bc_len) {
|
||
JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos);
|
||
goto fail;
|
||
}
|
||
n_pop = oi->n_pop;
|
||
/* call pops a variable number of arguments */
|
||
if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) {
|
||
n_pop += get_u16 (bc_buf + pos + 1);
|
||
} else {
|
||
#if SHORT_OPCODES
|
||
if (oi->fmt == OP_FMT_npopx) { n_pop += op - OP_call0; }
|
||
#endif
|
||
}
|
||
|
||
if (stack_len < n_pop) {
|
||
JS_ThrowInternalError (ctx, "stack underflow (op=%d, pc=%d)", op, pos);
|
||
goto fail;
|
||
}
|
||
stack_len += oi->n_push - n_pop;
|
||
if (stack_len > s->stack_len_max) {
|
||
s->stack_len_max = stack_len;
|
||
if (s->stack_len_max > JS_STACK_SIZE_MAX) {
|
||
JS_ThrowInternalError (ctx, "stack overflow (op=%d, pc=%d)", op, pos);
|
||
goto fail;
|
||
}
|
||
}
|
||
switch (op) {
|
||
case OP_tail_call:
|
||
case OP_tail_call_method:
|
||
case OP_return:
|
||
case OP_return_undef:
|
||
case OP_throw:
|
||
case OP_throw_error:
|
||
case OP_ret:
|
||
goto done_insn;
|
||
case OP_goto:
|
||
diff = get_u32 (bc_buf + pos + 1);
|
||
pos_next = pos + 1 + diff;
|
||
break;
|
||
#if SHORT_OPCODES
|
||
case OP_goto16:
|
||
diff = (int16_t)get_u16 (bc_buf + pos + 1);
|
||
pos_next = pos + 1 + diff;
|
||
break;
|
||
case OP_goto8:
|
||
diff = (int8_t)bc_buf[pos + 1];
|
||
pos_next = pos + 1 + diff;
|
||
break;
|
||
case OP_if_true8:
|
||
case OP_if_false8:
|
||
diff = (int8_t)bc_buf[pos + 1];
|
||
if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
|
||
goto fail;
|
||
break;
|
||
#endif
|
||
case OP_if_true:
|
||
case OP_if_false:
|
||
diff = get_u32 (bc_buf + pos + 1);
|
||
if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
|
||
goto fail;
|
||
break;
|
||
case OP_gosub:
|
||
diff = get_u32 (bc_buf + pos + 1);
|
||
if (ss_check (ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos))
|
||
goto fail;
|
||
break;
|
||
case OP_catch:
|
||
diff = get_u32 (bc_buf + pos + 1);
|
||
if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos))
|
||
goto fail;
|
||
catch_pos = pos;
|
||
break;
|
||
/* we assume the catch offset entry is only removed with
|
||
some op codes */
|
||
case OP_drop:
|
||
catch_level = stack_len;
|
||
goto check_catch;
|
||
case OP_nip:
|
||
catch_level = stack_len - 1;
|
||
goto check_catch;
|
||
case OP_nip1:
|
||
catch_level = stack_len - 1;
|
||
check_catch:
|
||
if (catch_pos >= 0) {
|
||
int level;
|
||
level = s->stack_level_tab[catch_pos];
|
||
/* catch_level = stack_level before op_catch is executed ? */
|
||
if (catch_level == level) { catch_pos = s->catch_pos_tab[catch_pos]; }
|
||
}
|
||
break;
|
||
case OP_nip_catch:
|
||
if (catch_pos < 0) {
|
||
JS_ThrowInternalError (ctx, "nip_catch: no catch op (pc=%d)", pos);
|
||
goto fail;
|
||
}
|
||
stack_len = s->stack_level_tab[catch_pos];
|
||
stack_len++; /* no stack overflow is possible by construction */
|
||
catch_pos = s->catch_pos_tab[catch_pos];
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (ss_check (ctx, s, pos_next, op, stack_len, catch_pos)) goto fail;
|
||
done_insn:;
|
||
}
|
||
pjs_free (s->pc_stack);
|
||
pjs_free (s->catch_pos_tab);
|
||
pjs_free (s->stack_level_tab);
|
||
*pstack_size = s->stack_len_max;
|
||
return 0;
|
||
fail:
|
||
pjs_free (s->pc_stack);
|
||
pjs_free (s->catch_pos_tab);
|
||
pjs_free (s->stack_level_tab);
|
||
*pstack_size = 0;
|
||
return -1;
|
||
}
|
||
|
||
/* create a function object from a function definition. The function
|
||
definition is freed. All the child functions are also created. It
|
||
must be done this way to resolve all the variables. */
|
||
JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) {
|
||
JSValue func_obj;
|
||
JSFunctionBytecode *b;
|
||
struct list_head *el, *el1;
|
||
int stack_size, scope, idx;
|
||
int function_size, byte_code_offset, cpool_offset;
|
||
int closure_var_offset, vardefs_offset;
|
||
|
||
/* recompute scope linkage */
|
||
for (scope = 0; scope < fd->scope_count; scope++) {
|
||
fd->scopes[scope].first = -1;
|
||
}
|
||
if (fd->has_parameter_expressions) {
|
||
/* special end of variable list marker for the argument scope */
|
||
fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END;
|
||
}
|
||
for (idx = 0; idx < fd->var_count; idx++) {
|
||
JSVarDef *vd = &fd->vars[idx];
|
||
vd->scope_next = fd->scopes[vd->scope_level].first;
|
||
fd->scopes[vd->scope_level].first = idx;
|
||
}
|
||
for (scope = 2; scope < fd->scope_count; scope++) {
|
||
JSVarScope *sd = &fd->scopes[scope];
|
||
if (sd->first < 0) sd->first = fd->scopes[sd->parent].first;
|
||
}
|
||
for (idx = 0; idx < fd->var_count; idx++) {
|
||
JSVarDef *vd = &fd->vars[idx];
|
||
if (vd->scope_next < 0 && vd->scope_level > 1) {
|
||
scope = fd->scopes[vd->scope_level].parent;
|
||
vd->scope_next = fd->scopes[scope].first;
|
||
}
|
||
}
|
||
|
||
/* first create all the child functions */
|
||
list_for_each_safe (el, el1, &fd->child_list) {
|
||
JSFunctionDef *fd1;
|
||
int cpool_idx;
|
||
|
||
fd1 = list_entry (el, JSFunctionDef, link);
|
||
cpool_idx = fd1->parent_cpool_idx;
|
||
func_obj = js_create_function (ctx, fd1);
|
||
if (JS_IsException (func_obj)) goto fail;
|
||
/* save it in the constant pool */
|
||
assert (cpool_idx >= 0);
|
||
fd->cpool[cpool_idx] = func_obj;
|
||
}
|
||
|
||
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4)
|
||
if (!fd->strip_debug) {
|
||
printf ("pass 1\n");
|
||
dump_byte_code (ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL);
|
||
printf ("\n");
|
||
}
|
||
#endif
|
||
|
||
if (resolve_variables (ctx, fd)) goto fail;
|
||
|
||
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2)
|
||
if (!fd->strip_debug) {
|
||
printf ("pass 2\n");
|
||
dump_byte_code (ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL);
|
||
printf ("\n");
|
||
}
|
||
#endif
|
||
|
||
if (resolve_labels (ctx, fd)) goto fail;
|
||
|
||
if (compute_stack_size (ctx, fd, &stack_size) < 0) goto fail;
|
||
|
||
if (fd->strip_debug) {
|
||
function_size = offsetof (JSFunctionBytecode, debug);
|
||
} else {
|
||
function_size = sizeof (*b);
|
||
}
|
||
cpool_offset = function_size;
|
||
function_size += fd->cpool_count * sizeof (*fd->cpool);
|
||
vardefs_offset = function_size;
|
||
if (!fd->strip_debug) {
|
||
function_size += (fd->arg_count + fd->var_count) * sizeof (*b->vardefs);
|
||
}
|
||
closure_var_offset = function_size;
|
||
function_size += fd->closure_var_count * sizeof (*fd->closure_var);
|
||
byte_code_offset = function_size;
|
||
function_size += fd->byte_code.size;
|
||
|
||
/* Bytecode is immutable - allocate outside GC heap */
|
||
b = pjs_mallocz (function_size);
|
||
if (!b) goto fail;
|
||
b->header = objhdr_make (0, OBJ_CODE, false, false, false, false);
|
||
|
||
b->byte_code_buf = (void *)((uint8_t *)b + byte_code_offset);
|
||
b->byte_code_len = fd->byte_code.size;
|
||
memcpy (b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size);
|
||
js_free (ctx, fd->byte_code.buf);
|
||
fd->byte_code.buf = NULL;
|
||
|
||
b->func_name = fd->func_name;
|
||
if (fd->arg_count + fd->var_count > 0) {
|
||
if (fd->strip_debug) {
|
||
/* Strip variable definitions not needed at runtime */
|
||
int i;
|
||
for (i = 0; i < fd->var_count; i++) {
|
||
}
|
||
for (i = 0; i < fd->arg_count; i++) {
|
||
}
|
||
for (i = 0; i < fd->closure_var_count; i++) {
|
||
fd->closure_var[i].var_name = JS_NULL;
|
||
}
|
||
} else {
|
||
b->vardefs = (void *)((uint8_t *)b + vardefs_offset);
|
||
memcpy_no_ub (b->vardefs, fd->args, fd->arg_count * sizeof (fd->args[0]));
|
||
memcpy_no_ub (b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof (fd->vars[0]));
|
||
}
|
||
b->var_count = fd->var_count;
|
||
b->arg_count = fd->arg_count;
|
||
b->defined_arg_count = fd->defined_arg_count;
|
||
js_free (ctx, fd->args);
|
||
js_free (ctx, fd->vars);
|
||
}
|
||
b->cpool_count = fd->cpool_count;
|
||
if (b->cpool_count) {
|
||
b->cpool = (void *)((uint8_t *)b + cpool_offset);
|
||
memcpy (b->cpool, fd->cpool, b->cpool_count * sizeof (*b->cpool));
|
||
}
|
||
js_free (ctx, fd->cpool);
|
||
fd->cpool = NULL;
|
||
|
||
b->stack_size = stack_size;
|
||
|
||
if (fd->strip_debug) {
|
||
dbuf_free (&fd->pc2line); // probably useless
|
||
} else {
|
||
/* XXX: source and pc2line info should be packed at the end of the
|
||
JSFunctionBytecode structure, avoiding allocation overhead
|
||
*/
|
||
b->has_debug = 1;
|
||
b->debug.filename = fd->filename;
|
||
|
||
// DynBuf pc2line;
|
||
// compute_pc2line_info(fd, &pc2line);
|
||
// js_free(ctx, fd->line_number_slots)
|
||
/* pc2line.buf is allocated by dbuf (system realloc), so just use it directly */
|
||
b->debug.pc2line_buf = fd->pc2line.buf;
|
||
fd->pc2line.buf = NULL; /* Transfer ownership */
|
||
b->debug.pc2line_len = fd->pc2line.size;
|
||
b->debug.source = fd->source;
|
||
b->debug.source_len = fd->source_len;
|
||
}
|
||
if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes);
|
||
|
||
b->closure_var_count = fd->closure_var_count;
|
||
if (b->closure_var_count) {
|
||
b->closure_var = (void *)((uint8_t *)b + closure_var_offset);
|
||
memcpy (b->closure_var, fd->closure_var, b->closure_var_count * sizeof (*b->closure_var));
|
||
}
|
||
pjs_free (fd->closure_var);
|
||
fd->closure_var = NULL;
|
||
|
||
b->has_prototype = fd->has_prototype;
|
||
b->has_simple_parameter_list = fd->has_simple_parameter_list;
|
||
b->js_mode = fd->js_mode;
|
||
|
||
/* IC removed - shapes no longer used */
|
||
|
||
b->is_direct_or_indirect_eval = FALSE;
|
||
|
||
#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1)
|
||
if (!fd->strip_debug) { js_dump_function_bytecode (ctx, b); }
|
||
#endif
|
||
|
||
if (fd->parent) {
|
||
/* remove from parent list */
|
||
list_del (&fd->link);
|
||
}
|
||
|
||
js_free (ctx, fd);
|
||
return JS_MKPTR (b);
|
||
fail:
|
||
js_free_function_def (ctx, fd);
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
static __exception int js_parse_directives (JSParseState *s) {
|
||
char str[20];
|
||
JSParsePos pos;
|
||
BOOL has_semi;
|
||
|
||
if (s->token.val != TOK_STRING) return 0;
|
||
|
||
js_parse_get_pos (s, &pos);
|
||
|
||
while (s->token.val == TOK_STRING) {
|
||
/* Copy actual source string representation */
|
||
snprintf (str, sizeof str, "%.*s", (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1);
|
||
|
||
if (next_token (s)) return -1;
|
||
|
||
has_semi = FALSE;
|
||
switch (s->token.val) {
|
||
case ';':
|
||
if (next_token (s)) return -1;
|
||
has_semi = TRUE;
|
||
break;
|
||
case '}':
|
||
case TOK_EOF:
|
||
has_semi = TRUE;
|
||
break;
|
||
case TOK_NUMBER:
|
||
case TOK_STRING:
|
||
case TOK_TEMPLATE:
|
||
case TOK_IDENT:
|
||
case TOK_REGEXP:
|
||
case TOK_DEC:
|
||
case TOK_INC:
|
||
case TOK_NULL:
|
||
case TOK_FALSE:
|
||
case TOK_TRUE:
|
||
case TOK_IF:
|
||
case TOK_RETURN:
|
||
case TOK_VAR:
|
||
case TOK_THIS:
|
||
case TOK_DELETE:
|
||
case TOK_DO:
|
||
case TOK_WHILE:
|
||
case TOK_FOR:
|
||
case TOK_DISRUPT:
|
||
case TOK_FUNCTION:
|
||
case TOK_DEBUGGER:
|
||
case TOK_DEF:
|
||
case TOK_ENUM:
|
||
case TOK_EXPORT:
|
||
case TOK_IMPORT:
|
||
case TOK_INTERFACE:
|
||
/* automatic insertion of ';' */
|
||
if (s->got_lf) has_semi = TRUE;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (!has_semi) break;
|
||
}
|
||
return js_parse_seek_token (s, &pos);
|
||
}
|
||
|
||
/* return TRUE if the keyword is forbidden only in strict mode */
|
||
static BOOL is_strict_future_keyword (JSValue name) {
|
||
BOOL is_strict_reserved = FALSE;
|
||
int token = js_keyword_lookup (name, &is_strict_reserved);
|
||
return (token >= 0 && is_strict_reserved);
|
||
}
|
||
|
||
static int js_parse_function_check_names (JSParseState *s, JSFunctionDef *fd, JSValue func_name) {
|
||
JSValue name;
|
||
int i, idx;
|
||
|
||
if (!fd->has_simple_parameter_list && fd->has_use_strict) {
|
||
return js_parse_error (s, "\"use strict\" not allowed in function with "
|
||
"default or destructuring parameter");
|
||
}
|
||
if (js_key_equal (func_name, JS_KEY_eval)
|
||
|| is_strict_future_keyword (func_name)) {
|
||
return js_parse_error (s, "invalid function name in strict code");
|
||
}
|
||
for (idx = 0; idx < fd->arg_count; idx++) {
|
||
name = fd->args[idx].var_name;
|
||
|
||
if (js_key_equal (name, JS_KEY_eval) || is_strict_future_keyword (name)) {
|
||
return js_parse_error (s, "invalid argument name in strict code");
|
||
}
|
||
}
|
||
|
||
for (idx = 0; idx < fd->arg_count; idx++) {
|
||
name = fd->args[idx].var_name;
|
||
if (!JS_IsNull (name)) {
|
||
for (i = 0; i < idx; i++) {
|
||
if (js_key_equal (fd->args[i].var_name, name)) goto duplicate;
|
||
}
|
||
/* Check if argument name duplicates a destructuring parameter */
|
||
/* XXX: should have a flag for such variables */
|
||
for (i = 0; i < fd->var_count; i++) {
|
||
if (js_key_equal (fd->vars[i].var_name, name)
|
||
&& fd->vars[i].scope_level == 0)
|
||
goto duplicate;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
|
||
duplicate:
|
||
return js_parse_error (
|
||
s, "duplicate argument names not allowed in this context");
|
||
}
|
||
|
||
/* func_name must be JS_NULL for JS_PARSE_FUNC_STATEMENT and
|
||
JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */
|
||
static __exception int js_parse_function_decl2 (JSParseState *s,
|
||
JSParseFunctionEnum func_type,
|
||
JSValue func_name,
|
||
const uint8_t *ptr,
|
||
JSFunctionDef **pfd) {
|
||
JSContext *ctx = s->ctx;
|
||
JSFunctionDef *fd = s->cur_func;
|
||
BOOL is_expr;
|
||
int func_idx, lexical_func_idx = -1;
|
||
BOOL create_func_var = FALSE;
|
||
|
||
is_expr
|
||
= (func_type != JS_PARSE_FUNC_STATEMENT && func_type != JS_PARSE_FUNC_VAR);
|
||
|
||
if (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR
|
||
|| func_type == JS_PARSE_FUNC_EXPR) {
|
||
if (next_token (s)) return -1;
|
||
|
||
if (s->token.val == TOK_IDENT) {
|
||
if (s->token.u.ident.is_reserved)
|
||
return js_parse_error_reserved_identifier (s);
|
||
func_name = s->token.u.ident.str;
|
||
if (next_token (s)) {
|
||
return -1;
|
||
}
|
||
} else {
|
||
if (func_type != JS_PARSE_FUNC_EXPR)
|
||
return js_parse_error (s, "function name expected");
|
||
}
|
||
|
||
} else if (func_type != JS_PARSE_FUNC_ARROW) {
|
||
/* func_name is already set, nothing to do */
|
||
}
|
||
|
||
if (func_type == JS_PARSE_FUNC_VAR) {
|
||
/* Create the lexical name here so that the function closure
|
||
contains it */
|
||
/* Always create a lexical name, fail if at the same scope as
|
||
existing name */
|
||
/* Lexical variable will be initialized upon entering scope */
|
||
lexical_func_idx
|
||
= define_var (s, fd, func_name, JS_VAR_DEF_FUNCTION_DECL);
|
||
if (lexical_func_idx < 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
fd = js_new_function_def (ctx, fd, is_expr, s->filename, ptr, &s->get_line_col_cache);
|
||
if (!fd) {
|
||
return -1;
|
||
}
|
||
if (pfd) *pfd = fd;
|
||
s->cur_func = fd;
|
||
fd->func_name = func_name;
|
||
/* XXX: test !fd->is_generator is always false */
|
||
fd->has_prototype
|
||
= (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR
|
||
|| func_type == JS_PARSE_FUNC_EXPR);
|
||
|
||
fd->has_this_binding = (func_type != JS_PARSE_FUNC_ARROW
|
||
&& func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT);
|
||
|
||
/* fd->in_function_body == FALSE prevents yield/await during the parsing
|
||
of the arguments in generator/async functions. They are parsed as
|
||
regular identifiers for other function kinds. */
|
||
fd->func_type = func_type;
|
||
|
||
/* parse arguments */
|
||
fd->has_simple_parameter_list = TRUE;
|
||
fd->has_parameter_expressions = FALSE;
|
||
if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) {
|
||
JSValue name;
|
||
if (s->token.u.ident.is_reserved) {
|
||
js_parse_error_reserved_identifier (s);
|
||
goto fail;
|
||
}
|
||
name = s->token.u.ident.str;
|
||
if (add_arg (ctx, fd, name) < 0) goto fail;
|
||
} else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) {
|
||
if (s->token.val == '(') {
|
||
int skip_bits;
|
||
/* if there is an '=' inside the parameter list, we
|
||
consider there is a parameter expression inside */
|
||
js_parse_skip_parens_token (s, &skip_bits, FALSE);
|
||
if (skip_bits & SKIP_HAS_ASSIGNMENT)
|
||
fd->has_parameter_expressions = TRUE;
|
||
if (next_token (s)) goto fail;
|
||
} else {
|
||
if (js_parse_expect (s, '(')) goto fail;
|
||
}
|
||
|
||
if (fd->has_parameter_expressions) {
|
||
fd->scope_level = -1; /* force no parent scope */
|
||
if (push_scope (s) < 0) return -1;
|
||
}
|
||
|
||
while (s->token.val != ')') {
|
||
JSValue name;
|
||
int idx, has_initializer;
|
||
|
||
if (s->token.val == '[' || s->token.val == '{') {
|
||
fd->has_simple_parameter_list = FALSE;
|
||
/* unnamed arg for destructuring */
|
||
idx = add_arg (ctx, fd, JS_NULL);
|
||
emit_op (s, OP_get_arg);
|
||
emit_u16 (s, idx);
|
||
has_initializer
|
||
= js_parse_destructuring_element (s, TOK_VAR, 1, TRUE, TRUE, FALSE);
|
||
if (has_initializer < 0) goto fail;
|
||
} else if (s->token.val == TOK_IDENT) {
|
||
if (s->token.u.ident.is_reserved) {
|
||
js_parse_error_reserved_identifier (s);
|
||
goto fail;
|
||
}
|
||
name = s->token.u.ident.str;
|
||
if (fd->has_parameter_expressions) {
|
||
if (js_parse_check_duplicate_parameter (s, name)) goto fail;
|
||
if (define_var (s, fd, name, JS_VAR_DEF_LET) < 0) goto fail;
|
||
}
|
||
idx = add_arg (ctx, fd, name);
|
||
if (idx < 0) goto fail;
|
||
if (next_token (s)) goto fail;
|
||
if (s->token.val == '=') {
|
||
int label;
|
||
|
||
fd->has_simple_parameter_list = FALSE;
|
||
|
||
if (next_token (s)) goto fail;
|
||
|
||
label = new_label (s);
|
||
emit_op (s, OP_get_arg);
|
||
emit_u16 (s, idx);
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_null);
|
||
emit_op (s, OP_strict_eq);
|
||
emit_goto (s, OP_if_false, label);
|
||
emit_op (s, OP_drop);
|
||
if (js_parse_assign_expr (s)) goto fail;
|
||
set_object_name (s, name);
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_put_arg);
|
||
emit_u16 (s, idx);
|
||
emit_label (s, label);
|
||
emit_op (s, OP_scope_put_var_init);
|
||
emit_key (s, name);
|
||
emit_u16 (s, fd->scope_level);
|
||
} else {
|
||
if (fd->has_parameter_expressions) {
|
||
/* copy the argument to the argument scope */
|
||
emit_op (s, OP_get_arg);
|
||
emit_u16 (s, idx);
|
||
emit_op (s, OP_scope_put_var_init);
|
||
emit_key (s, name);
|
||
emit_u16 (s, fd->scope_level);
|
||
}
|
||
}
|
||
} else {
|
||
js_parse_error (s, "missing formal parameter");
|
||
goto fail;
|
||
}
|
||
if (s->token.val == ')') break;
|
||
if (js_parse_expect (s, ',')) goto fail;
|
||
}
|
||
if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0)
|
||
|| (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) {
|
||
js_parse_error (s, "invalid number of arguments for getter or setter");
|
||
goto fail;
|
||
}
|
||
if (fd->arg_count > 4) {
|
||
js_parse_error (s, "functions cannot have more than 4 parameters");
|
||
goto fail;
|
||
}
|
||
}
|
||
/* Explicit arity: defined_arg_count == arg_count always */
|
||
fd->defined_arg_count = fd->arg_count;
|
||
|
||
if (fd->has_parameter_expressions) {
|
||
int idx;
|
||
|
||
/* Copy the variables in the argument scope to the variable
|
||
scope (see FunctionDeclarationInstantiation() in spec). The
|
||
normal arguments are already present, so no need to copy
|
||
them. */
|
||
idx = fd->scopes[fd->scope_level].first;
|
||
while (idx >= 0) {
|
||
JSVarDef *vd = &fd->vars[idx];
|
||
if (vd->scope_level != fd->scope_level) break;
|
||
if (find_var (ctx, fd, vd->var_name) < 0) {
|
||
if (add_var (ctx, fd, vd->var_name) < 0) goto fail;
|
||
vd = &fd->vars[idx]; /* fd->vars may have been reallocated */
|
||
emit_op (s, OP_scope_get_var);
|
||
emit_key (s, vd->var_name);
|
||
emit_u16 (s, fd->scope_level);
|
||
emit_op (s, OP_scope_put_var);
|
||
emit_key (s, vd->var_name);
|
||
emit_u16 (s, 0);
|
||
}
|
||
idx = vd->scope_next;
|
||
}
|
||
|
||
/* the argument scope has no parent, hence we don't use pop_scope(s) */
|
||
emit_op (s, OP_leave_scope);
|
||
emit_u16 (s, fd->scope_level);
|
||
|
||
/* set the variable scope as the current scope */
|
||
fd->scope_level = 0;
|
||
fd->scope_first = fd->scopes[fd->scope_level].first;
|
||
}
|
||
|
||
if (next_token (s)) goto fail;
|
||
|
||
/* in generators, yield expression is forbidden during the parsing
|
||
of the arguments */
|
||
fd->in_function_body = TRUE;
|
||
push_scope (s); /* enter body scope */
|
||
fd->body_scope = fd->scope_level;
|
||
|
||
if (s->token.val == TOK_ARROW && func_type == JS_PARSE_FUNC_ARROW) {
|
||
if (next_token (s)) goto fail;
|
||
|
||
if (s->token.val != '{') {
|
||
if (js_parse_function_check_names (s, fd, func_name)) goto fail;
|
||
|
||
if (js_parse_assign_expr (s)) goto fail;
|
||
|
||
emit_op (s, OP_return);
|
||
|
||
if (!fd->strip_source) {
|
||
/* save the function source code */
|
||
/* the end of the function source code is after the last
|
||
token of the function source stored into s->last_ptr */
|
||
fd->source_len = s->last_ptr - ptr;
|
||
fd->source = strndup ((const char *)ptr, fd->source_len);
|
||
if (!fd->source) goto fail;
|
||
}
|
||
goto done;
|
||
}
|
||
}
|
||
|
||
if (js_parse_expect (s, '{')) goto fail;
|
||
|
||
if (js_parse_directives (s)) goto fail;
|
||
|
||
/* in strict_mode, check function and argument names */
|
||
if (js_parse_function_check_names (s, fd, func_name)) goto fail;
|
||
|
||
while (s->token.val != '}') {
|
||
if (js_parse_source_element (s)) goto fail;
|
||
}
|
||
if (!fd->strip_source) {
|
||
/* save the function source code */
|
||
fd->source_len = s->buf_ptr - ptr;
|
||
fd->source = strndup ((const char *)ptr, fd->source_len);
|
||
if (!fd->source) goto fail;
|
||
}
|
||
|
||
if (next_token (s)) {
|
||
/* consume the '}' */
|
||
goto fail;
|
||
}
|
||
|
||
/* in case there is no return, add one */
|
||
if (js_is_live_code (s)) { emit_return (s, FALSE); }
|
||
done:
|
||
s->cur_func = fd->parent;
|
||
|
||
/* Reparse identifiers after the function is terminated so that
|
||
the token is parsed in the englobing function. It could be done
|
||
by just using next_token() here for normal functions, but it is
|
||
necessary for arrow functions with an expression body. */
|
||
reparse_ident_token (s);
|
||
|
||
/* create the function object */
|
||
{
|
||
int idx;
|
||
JSValue func_name_val = fd->func_name;
|
||
|
||
/* the real object will be set at the end of the compilation */
|
||
idx = cpool_add (s, JS_NULL);
|
||
fd->parent_cpool_idx = idx;
|
||
|
||
if (is_expr) {
|
||
/* OP_fclosure creates the function object from the bytecode
|
||
and adds the scope information */
|
||
emit_op (s, OP_fclosure);
|
||
emit_u32 (s, idx);
|
||
if (JS_IsNull (func_name_val)) {
|
||
emit_op (s, OP_set_name);
|
||
emit_key (s, JS_NULL);
|
||
}
|
||
} else if (func_type == JS_PARSE_FUNC_VAR) {
|
||
emit_op (s, OP_fclosure);
|
||
emit_u32 (s, idx);
|
||
if (create_func_var) {
|
||
if (s->cur_func->is_global_var) {
|
||
JSGlobalVar *hf;
|
||
/* the global variable must be defined at the start of the
|
||
function */
|
||
hf = add_global_var (ctx, s->cur_func, func_name_val);
|
||
if (!hf) goto fail;
|
||
/* it is considered as defined at the top level
|
||
(needed for annex B.3.3.4 and B.3.3.5
|
||
checks) */
|
||
hf->scope_level = 0;
|
||
hf->force_init = 1;
|
||
/* store directly into global var, bypass lexical scope */
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_scope_put_var);
|
||
emit_key (s, func_name_val);
|
||
emit_u16 (s, 0);
|
||
} else {
|
||
/* do not call define_var to bypass lexical scope check */
|
||
func_idx = find_var (ctx, s->cur_func, func_name_val);
|
||
if (func_idx < 0) {
|
||
func_idx = add_var (ctx, s->cur_func, func_name_val);
|
||
if (func_idx < 0) goto fail;
|
||
}
|
||
/* store directly into local var, bypass lexical catch scope */
|
||
emit_op (s, OP_dup);
|
||
emit_op (s, OP_scope_put_var);
|
||
emit_key (s, func_name_val);
|
||
emit_u16 (s, 0);
|
||
}
|
||
}
|
||
if (lexical_func_idx >= 0) {
|
||
/* lexical variable will be initialized upon entering scope */
|
||
s->cur_func->vars[lexical_func_idx].func_pool_idx = idx;
|
||
emit_op (s, OP_drop);
|
||
} else {
|
||
/* store function object into its lexical name */
|
||
/* XXX: could use OP_put_loc directly */
|
||
emit_op (s, OP_scope_put_var_init);
|
||
emit_key (s, func_name_val);
|
||
emit_u16 (s, s->cur_func->scope_level);
|
||
}
|
||
} else {
|
||
if (!s->cur_func->is_global_var) {
|
||
int var_idx
|
||
= define_var (s, s->cur_func, func_name_val, JS_VAR_DEF_VAR);
|
||
|
||
if (var_idx < 0) goto fail;
|
||
/* the variable will be assigned at the top of the function */
|
||
if (var_idx & ARGUMENT_VAR_OFFSET) {
|
||
s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx;
|
||
} else {
|
||
s->cur_func->vars[var_idx].func_pool_idx = idx;
|
||
}
|
||
} else {
|
||
JSValue func_var_name;
|
||
JSGlobalVar *hf;
|
||
if (JS_IsNull (func_name_val))
|
||
func_var_name = JS_KEY_STR (ctx, "default"); /* export default */
|
||
else
|
||
func_var_name = func_name_val;
|
||
/* the variable will be assigned at the top of the function */
|
||
hf = add_global_var (ctx, s->cur_func, func_var_name);
|
||
if (!hf) goto fail;
|
||
hf->cpool_idx = idx;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
fail:
|
||
s->cur_func = fd->parent;
|
||
js_free_function_def (ctx, fd);
|
||
if (pfd) *pfd = NULL;
|
||
return -1;
|
||
}
|
||
|
||
static __exception int js_parse_function_decl (JSParseState *s,
|
||
JSParseFunctionEnum func_type,
|
||
JSValue func_name,
|
||
const uint8_t *ptr) {
|
||
return js_parse_function_decl2 (s, func_type, func_name, ptr, NULL);
|
||
}
|
||
|
||
static __exception int js_parse_program (JSParseState *s) {
|
||
JSFunctionDef *fd = s->cur_func;
|
||
if (next_token (s)) return -1;
|
||
|
||
if (js_parse_directives (s)) return -1;
|
||
|
||
/* Global object is immutable - all variables are locals of the eval function */
|
||
fd->is_global_var = FALSE;
|
||
|
||
while (s->token.val != TOK_EOF) {
|
||
if (js_parse_source_element (s)) return -1;
|
||
}
|
||
|
||
/* For eval-like semantics: if the last statement was an expression,
|
||
return its value instead of null. Expression statements emit OP_drop
|
||
to discard the value - remove that and emit OP_return instead. */
|
||
if (get_prev_opcode (fd) == OP_drop) {
|
||
fd->byte_code.size = fd->last_opcode_pos;
|
||
fd->last_opcode_pos = -1;
|
||
emit_return (s, TRUE);
|
||
} else {
|
||
emit_return (s, FALSE);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void js_parse_init (JSContext *ctx, JSParseState *s, const char *input, size_t input_len, const char *filename) {
|
||
memset (s, 0, sizeof (*s));
|
||
s->ctx = ctx;
|
||
s->filename = filename;
|
||
s->buf_start = s->buf_ptr = (const uint8_t *)input;
|
||
s->buf_end = s->buf_ptr + input_len;
|
||
s->token.val = ' ';
|
||
s->token.ptr = s->buf_ptr;
|
||
|
||
s->get_line_col_cache.ptr = s->buf_start;
|
||
s->get_line_col_cache.buf_start = s->buf_start;
|
||
s->get_line_col_cache.line_num = 0;
|
||
s->get_line_col_cache.col_num = 0;
|
||
}
|
||
|
||
/* Forward declaration */
|
||
JSValue __JS_CompileInternal (JSContext *ctx, const char *input, size_t input_len, const char *filename);
|
||
|
||
/* Compile source code to bytecode without executing.
|
||
Returns unlinked bytecode on success, JS_EXCEPTION on error.
|
||
'input' must be zero terminated i.e. input[input_len] = '\0'. */
|
||
JSValue JS_Compile (JSContext *ctx, const char *input, size_t input_len,
|
||
const char *filename) {
|
||
return __JS_CompileInternal (ctx, input, input_len, filename);
|
||
}
|
||
|
||
/* Link compiled bytecode with environment and execute.
|
||
The env should be a stoned record (or null for no env).
|
||
Variables resolve: env first, then global intrinsics. */
|
||
JSValue JS_Integrate (JSContext *ctx, JSValue fun_obj, JSValue env) {
|
||
JSValue ret_val;
|
||
uint32_t tag;
|
||
JSGCRef env_ref, fun_ref;
|
||
JSFunctionBytecode *tpl, *linked_bc;
|
||
|
||
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 bytecode with environment */
|
||
tpl = (JSFunctionBytecode *)JS_VALUE_GET_PTR (fun_ref.val);
|
||
linked_bc = js_link_bytecode (ctx, tpl, env_ref.val);
|
||
if (!linked_bc) {
|
||
JS_DeleteGCRef (ctx, &fun_ref);
|
||
JS_DeleteGCRef (ctx, &env_ref);
|
||
return JS_EXCEPTION;
|
||
}
|
||
JSValue linked = JS_MKPTR (linked_bc);
|
||
|
||
/* Update env from GC ref (may have moved) */
|
||
env = env_ref.val;
|
||
JS_DeleteGCRef (ctx, &fun_ref);
|
||
JS_DeleteGCRef (ctx, &env_ref);
|
||
|
||
/* Create closure and set env_record on the function object */
|
||
linked = js_closure (ctx, linked, NULL);
|
||
if (JS_IsException (linked)) {
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
/* Store env_record on the function for OP_get_env_slot access */
|
||
JSFunction *f = JS_VALUE_GET_FUNCTION (linked);
|
||
f->u.func.env_record = env;
|
||
|
||
ret_val = JS_Call (ctx, linked, ctx->global_obj, 0, NULL);
|
||
|
||
return ret_val;
|
||
}
|
||
|
||
/* Compile source to bytecode.
|
||
'input' must be zero terminated i.e. input[input_len] = '\0'. */
|
||
JSValue __JS_CompileInternal (JSContext *ctx, const char *input, size_t input_len, const char *filename) {
|
||
JSParseState s1, *s = &s1;
|
||
int err;
|
||
JSValue fun_obj;
|
||
JSFunctionDef *fd;
|
||
|
||
js_parse_init (ctx, s, input, input_len, filename);
|
||
|
||
fd = js_new_function_def (ctx, NULL, 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->has_this_binding = TRUE;
|
||
fd->js_mode = 0;
|
||
fd->func_name = JS_KEY__eval_;
|
||
|
||
push_scope (s); /* body scope */
|
||
fd->body_scope = fd->scope_level;
|
||
|
||
err = js_parse_program (s);
|
||
if (err) {
|
||
free_token (s, &s->token);
|
||
ctx->current_parse_fd = NULL; /* Clear GC root before freeing */
|
||
js_free_function_def (ctx, fd);
|
||
goto fail1;
|
||
}
|
||
|
||
/* create the function object and all the enclosed functions */
|
||
ctx->current_parse_fd = NULL; /* Clear GC root - fd ownership transfers to js_create_function */
|
||
fun_obj = js_create_function (ctx, fd);
|
||
if (JS_IsException (fun_obj)) goto fail1;
|
||
return fun_obj;
|
||
fail1:
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
/*******************************************************************/
|
||
/* object list */
|
||
|
||
typedef struct {
|
||
JSRecord *obj;
|
||
uint32_t hash_next; /* -1 if no next entry */
|
||
} JSRecordListEntry;
|
||
|
||
/* XXX: reuse it to optimize weak references */
|
||
typedef struct {
|
||
JSRecordListEntry *object_tab;
|
||
int object_count;
|
||
int object_size;
|
||
uint32_t *hash_table;
|
||
uint32_t hash_size;
|
||
} JSRecordList;
|
||
|
||
static void js_object_list_init (JSRecordList *s) {
|
||
memset (s, 0, sizeof (*s));
|
||
}
|
||
|
||
static uint32_t js_object_list_get_hash (JSRecord *p, uint32_t hash_size) {
|
||
return ((uintptr_t)p * 3163) & (hash_size - 1);
|
||
}
|
||
|
||
static int js_object_list_resize_hash (JSContext *ctx, JSRecordList *s, uint32_t new_hash_size) {
|
||
JSRecordListEntry *e;
|
||
uint32_t i, h, *new_hash_table;
|
||
|
||
new_hash_table = js_malloc (ctx, sizeof (new_hash_table[0]) * new_hash_size);
|
||
if (!new_hash_table) return -1;
|
||
js_free (ctx, s->hash_table);
|
||
s->hash_table = new_hash_table;
|
||
s->hash_size = new_hash_size;
|
||
|
||
for (i = 0; i < s->hash_size; i++) {
|
||
s->hash_table[i] = -1;
|
||
}
|
||
for (i = 0; i < s->object_count; i++) {
|
||
e = &s->object_tab[i];
|
||
h = js_object_list_get_hash (e->obj, s->hash_size);
|
||
e->hash_next = s->hash_table[h];
|
||
s->hash_table[h] = i;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* the reference count of 'obj' is not modified. Return 0 if OK, -1 if
|
||
memory error */
|
||
static int js_object_list_add (JSContext *ctx, JSRecordList *s, JSRecord *obj) {
|
||
JSRecordListEntry *e;
|
||
uint32_t h, new_hash_size;
|
||
|
||
if (js_resize_array (ctx, (void *)&s->object_tab, sizeof (s->object_tab[0]), &s->object_size, s->object_count + 1))
|
||
return -1;
|
||
if (unlikely ((s->object_count + 1) >= s->hash_size)) {
|
||
new_hash_size = max_uint32 (s->hash_size, 4);
|
||
while (new_hash_size <= s->object_count)
|
||
new_hash_size *= 2;
|
||
if (js_object_list_resize_hash (ctx, s, new_hash_size)) return -1;
|
||
}
|
||
e = &s->object_tab[s->object_count++];
|
||
h = js_object_list_get_hash (obj, s->hash_size);
|
||
e->obj = obj;
|
||
e->hash_next = s->hash_table[h];
|
||
s->hash_table[h] = s->object_count - 1;
|
||
return 0;
|
||
}
|
||
|
||
/* return -1 if not present or the object index */
|
||
static int js_object_list_find (JSContext *ctx, JSRecordList *s, JSRecord *obj) {
|
||
JSRecordListEntry *e;
|
||
uint32_t h, p;
|
||
|
||
/* must test empty size because there is no hash table */
|
||
if (s->object_count == 0) return -1;
|
||
h = js_object_list_get_hash (obj, s->hash_size);
|
||
p = s->hash_table[h];
|
||
while (p != -1) {
|
||
e = &s->object_tab[p];
|
||
if (e->obj == obj) return p;
|
||
p = e->hash_next;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static void js_object_list_end (JSContext *ctx, JSRecordList *s) {
|
||
js_free (ctx, s->object_tab);
|
||
js_free (ctx, s->hash_table);
|
||
}
|
||
|
||
/*******************************************************************/
|
||
/* binary object writer & reader */
|
||
|
||
typedef enum BCTagEnum {
|
||
BC_TAG_NULL = 1,
|
||
BC_TAG_BOOL_FALSE,
|
||
BC_TAG_BOOL_TRUE,
|
||
BC_TAG_INT32,
|
||
BC_TAG_FLOAT64,
|
||
BC_TAG_STRING,
|
||
BC_TAG_OBJECT,
|
||
BC_TAG_ARRAY,
|
||
BC_TAG_TEMPLATE_OBJECT,
|
||
BC_TAG_FUNCTION_BYTECODE,
|
||
BC_TAG_MODULE,
|
||
BC_TAG_OBJECT_VALUE,
|
||
BC_TAG_OBJECT_REFERENCE,
|
||
} BCTagEnum;
|
||
|
||
#define BC_VERSION 6
|
||
|
||
typedef struct BCWriterState {
|
||
JSContext *ctx;
|
||
DynBuf dbuf;
|
||
BOOL allow_bytecode : 8;
|
||
BOOL allow_sab : 8;
|
||
BOOL allow_reference : 8;
|
||
uint8_t **sab_tab;
|
||
int sab_tab_len;
|
||
int sab_tab_size;
|
||
/* list of referenced objects (used if allow_reference = TRUE) */
|
||
JSRecordList object_list;
|
||
} BCWriterState;
|
||
|
||
#ifdef DUMP_READ_OBJECT
|
||
static const char *const bc_tag_str[] = {
|
||
"invalid",
|
||
"null",
|
||
"false",
|
||
"true",
|
||
"int32",
|
||
"float64",
|
||
"string",
|
||
"object",
|
||
"array",
|
||
"template",
|
||
"function",
|
||
"module",
|
||
"ObjectReference",
|
||
};
|
||
#endif
|
||
|
||
static inline BOOL is_be (void) {
|
||
union {
|
||
uint16_t a;
|
||
uint8_t b;
|
||
} u = { 0x100 };
|
||
return u.b;
|
||
}
|
||
|
||
static void bc_put_u8 (BCWriterState *s, uint8_t v) {
|
||
dbuf_putc (&s->dbuf, v);
|
||
}
|
||
|
||
static void bc_put_u16 (BCWriterState *s, uint16_t v) {
|
||
if (is_be ()) v = bswap16 (v);
|
||
dbuf_put_u16 (&s->dbuf, v);
|
||
}
|
||
|
||
static __maybe_unused void bc_put_u32 (BCWriterState *s, uint32_t v) {
|
||
if (is_be ()) v = bswap32 (v);
|
||
dbuf_put_u32 (&s->dbuf, v);
|
||
}
|
||
|
||
static void bc_put_u64 (BCWriterState *s, uint64_t v) {
|
||
if (is_be ()) v = bswap64 (v);
|
||
dbuf_put (&s->dbuf, (uint8_t *)&v, sizeof (v));
|
||
}
|
||
|
||
static void bc_put_leb128 (BCWriterState *s, uint32_t v) {
|
||
dbuf_put_leb128 (&s->dbuf, v);
|
||
}
|
||
|
||
static void bc_put_sleb128 (BCWriterState *s, int32_t v) {
|
||
dbuf_put_sleb128 (&s->dbuf, v);
|
||
}
|
||
|
||
static void bc_set_flags (uint32_t *pflags, int *pidx, uint32_t val, int n) {
|
||
*pflags = *pflags | (val << *pidx);
|
||
*pidx += n;
|
||
}
|
||
|
||
/* Write a JSValue key (text) to bytecode stream */
|
||
static int bc_put_key (BCWriterState *s, JSValue key) {
|
||
/* Handle immediate ASCII strings */
|
||
if (MIST_IsImmediateASCII (key)) {
|
||
int len = MIST_GetImmediateASCIILen (key);
|
||
bc_put_leb128 (s, (uint32_t)len);
|
||
for (int i = 0; i < len; i++) {
|
||
bc_put_u8 (s, (uint8_t)MIST_GetImmediateASCIIChar (key, i));
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Handle heap strings */
|
||
if (!JS_IsText (key)) {
|
||
/* Not a string - write empty */
|
||
bc_put_leb128 (s, 0);
|
||
return 0;
|
||
}
|
||
|
||
JSText *p = JS_VALUE_GET_STRING (key);
|
||
/* Write as UTF-8 */
|
||
uint32_t len = (uint32_t)JSText_len (p);
|
||
/* Calculate UTF-8 size */
|
||
size_t utf8_size = 0;
|
||
for (uint32_t i = 0; i < len; i++) {
|
||
uint32_t c = string_get (p, i);
|
||
if (c < 0x80)
|
||
utf8_size += 1;
|
||
else if (c < 0x800)
|
||
utf8_size += 2;
|
||
else if (c < 0x10000)
|
||
utf8_size += 3;
|
||
else
|
||
utf8_size += 4;
|
||
}
|
||
bc_put_leb128 (s, (uint32_t)utf8_size);
|
||
for (uint32_t i = 0; i < len; i++) {
|
||
uint32_t c = string_get (p, i);
|
||
if (c < 0x80) {
|
||
bc_put_u8 (s, c);
|
||
} else if (c < 0x800) {
|
||
bc_put_u8 (s, 0xC0 | (c >> 6));
|
||
bc_put_u8 (s, 0x80 | (c & 0x3F));
|
||
} else if (c < 0x10000) {
|
||
bc_put_u8 (s, 0xE0 | (c >> 12));
|
||
bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F));
|
||
bc_put_u8 (s, 0x80 | (c & 0x3F));
|
||
} else {
|
||
bc_put_u8 (s, 0xF0 | (c >> 18));
|
||
bc_put_u8 (s, 0x80 | ((c >> 12) & 0x3F));
|
||
bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F));
|
||
bc_put_u8 (s, 0x80 | (c & 0x3F));
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void bc_byte_swap (uint8_t *bc_buf, int bc_len) {
|
||
int pos, len, op, fmt;
|
||
|
||
pos = 0;
|
||
while (pos < bc_len) {
|
||
op = bc_buf[pos];
|
||
len = short_opcode_info (op).size;
|
||
fmt = short_opcode_info (op).fmt;
|
||
switch (fmt) {
|
||
case OP_FMT_u16:
|
||
case OP_FMT_i16:
|
||
case OP_FMT_label16:
|
||
case OP_FMT_npop:
|
||
case OP_FMT_loc:
|
||
case OP_FMT_arg:
|
||
put_u16 (bc_buf + pos + 1, bswap16 (get_u16 (bc_buf + pos + 1)));
|
||
break;
|
||
case OP_FMT_i32:
|
||
case OP_FMT_u32:
|
||
case OP_FMT_const:
|
||
case OP_FMT_label:
|
||
case OP_FMT_key:
|
||
case OP_FMT_key_u8:
|
||
put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1)));
|
||
break;
|
||
case OP_FMT_key_u16:
|
||
case OP_FMT_label_u16:
|
||
put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1)));
|
||
put_u16 (bc_buf + pos + 1 + 4, bswap16 (get_u16 (bc_buf + pos + 1 + 4)));
|
||
break;
|
||
case OP_FMT_key_label_u16:
|
||
put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1)));
|
||
put_u32 (bc_buf + pos + 1 + 4, bswap32 (get_u32 (bc_buf + pos + 1 + 4)));
|
||
put_u16 (bc_buf + pos + 1 + 4 + 4,
|
||
bswap16 (get_u16 (bc_buf + pos + 1 + 4 + 4)));
|
||
break;
|
||
case OP_FMT_npop_u16:
|
||
put_u16 (bc_buf + pos + 1, bswap16 (get_u16 (bc_buf + pos + 1)));
|
||
put_u16 (bc_buf + pos + 1 + 2, bswap16 (get_u16 (bc_buf + pos + 1 + 2)));
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
pos += len;
|
||
}
|
||
}
|
||
|
||
static int JS_WriteFunctionBytecode (BCWriterState *s, const uint8_t *bc_buf1, int bc_len) {
|
||
int pos, len, op;
|
||
uint8_t *bc_buf;
|
||
|
||
bc_buf = js_malloc (s->ctx, bc_len);
|
||
if (!bc_buf) return -1;
|
||
memcpy (bc_buf, bc_buf1, bc_len);
|
||
|
||
pos = 0;
|
||
while (pos < bc_len) {
|
||
op = bc_buf[pos];
|
||
len = short_opcode_info (op).size;
|
||
switch (short_opcode_info (op).fmt) {
|
||
case OP_FMT_key:
|
||
case OP_FMT_key_u8:
|
||
case OP_FMT_key_u16:
|
||
case OP_FMT_key_label_u16:
|
||
/* Key operand is a cpool index; cpool values serialized separately */
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
pos += len;
|
||
}
|
||
|
||
if (is_be ()) bc_byte_swap (bc_buf, bc_len);
|
||
|
||
dbuf_put (&s->dbuf, bc_buf, bc_len);
|
||
|
||
js_free (s->ctx, bc_buf);
|
||
return 0;
|
||
}
|
||
|
||
static void JS_WriteString (BCWriterState *s, JSText *p) {
|
||
int i;
|
||
int len = (int)JSText_len (p);
|
||
/* UTF-32: write length, then each character as 32-bit value */
|
||
bc_put_leb128 (s, (uint32_t)len);
|
||
for (i = 0; i < len; i++) {
|
||
uint32_t c = string_get (p, i);
|
||
bc_put_u32 (s, c);
|
||
}
|
||
}
|
||
|
||
static int JS_WriteObjectRec (BCWriterState *s, JSValue obj);
|
||
|
||
static int JS_WriteObjectTag (BCWriterState *s, JSValue obj) {
|
||
JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj);
|
||
uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr);
|
||
uint32_t i, prop_count;
|
||
int pass;
|
||
|
||
bc_put_u8 (s, BC_TAG_OBJECT);
|
||
prop_count = 0;
|
||
|
||
/* Two-pass: count then write */
|
||
for (pass = 0; pass < 2; pass++) {
|
||
if (pass == 1) bc_put_leb128 (s, prop_count);
|
||
for (i = 1; i <= mask; i++) {
|
||
JSValue k = rec->slots[i].key;
|
||
if (!rec_key_is_empty (k) && !rec_key_is_tomb (k) && JS_IsText (k)) {
|
||
if (pass == 0) {
|
||
prop_count++;
|
||
} else {
|
||
/* Write key as JSValue text directly */
|
||
bc_put_key (s, k);
|
||
if (JS_WriteObjectRec (s, rec->slots[i].val)) goto fail;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
fail:
|
||
return -1;
|
||
}
|
||
|
||
static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) {
|
||
uint32_t tag;
|
||
|
||
if (js_check_stack_overflow (s->ctx, 0)) {
|
||
JS_ThrowStackOverflow (s->ctx);
|
||
return -1;
|
||
}
|
||
|
||
tag = JS_VALUE_GET_NORM_TAG (obj);
|
||
switch (tag) {
|
||
case JS_TAG_NULL:
|
||
bc_put_u8 (s, BC_TAG_NULL);
|
||
break;
|
||
case JS_TAG_BOOL:
|
||
bc_put_u8 (s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT (obj));
|
||
break;
|
||
case JS_TAG_INT:
|
||
bc_put_u8 (s, BC_TAG_INT32);
|
||
bc_put_sleb128 (s, JS_VALUE_GET_INT (obj));
|
||
break;
|
||
case JS_TAG_FLOAT64: {
|
||
JSFloat64Union u;
|
||
bc_put_u8 (s, BC_TAG_FLOAT64);
|
||
u.d = JS_VALUE_GET_FLOAT64 (obj);
|
||
bc_put_u64 (s, u.u64);
|
||
} break;
|
||
case JS_TAG_STRING_IMM: {
|
||
/* Immediate ASCII string */
|
||
int len = MIST_GetImmediateASCIILen (obj);
|
||
char buf[8];
|
||
for (int i = 0; i < len; i++)
|
||
buf[i] = MIST_GetImmediateASCIIChar (obj, i);
|
||
JSValue tmp = js_new_string8_len (s->ctx, buf, len);
|
||
if (JS_IsException (tmp)) goto fail;
|
||
JSText *p = JS_VALUE_GET_STRING (tmp);
|
||
bc_put_u8 (s, BC_TAG_STRING);
|
||
JS_WriteString (s, p);
|
||
} break;
|
||
case JS_TAG_PTR:
|
||
/* Check if this is a heap string */
|
||
if (JS_IsText (obj)) {
|
||
JSText *p = JS_VALUE_GET_STRING (obj);
|
||
bc_put_u8 (s, BC_TAG_STRING);
|
||
JS_WriteString (s, p);
|
||
break;
|
||
}
|
||
/* Check if this is an intrinsic array */
|
||
if (JS_IsArray (obj)) {
|
||
JSArray *arr = JS_VALUE_GET_ARRAY (obj);
|
||
uint32_t i;
|
||
bc_put_u8 (s, BC_TAG_ARRAY);
|
||
bc_put_leb128 (s, arr->len);
|
||
for (i = 0; i < arr->len; i++) {
|
||
if (JS_WriteObjectRec (s, arr->values[i])) goto fail;
|
||
}
|
||
} else {
|
||
JSRecord *p = JS_VALUE_GET_OBJ (obj);
|
||
int ret, idx;
|
||
|
||
/* Always use object_list for cycle detection */
|
||
idx = js_object_list_find (s->ctx, &s->object_list, p);
|
||
if (idx >= 0) {
|
||
if (s->allow_reference) {
|
||
bc_put_u8 (s, BC_TAG_OBJECT_REFERENCE);
|
||
bc_put_leb128 (s, idx);
|
||
break;
|
||
} else {
|
||
JS_ThrowTypeError (s->ctx, "circular reference");
|
||
goto fail;
|
||
}
|
||
}
|
||
if (js_object_list_add (s->ctx, &s->object_list, p)) goto fail;
|
||
switch (REC_GET_CLASS_ID(p)) {
|
||
case JS_CLASS_OBJECT:
|
||
ret = JS_WriteObjectTag (s, obj);
|
||
break;
|
||
default:
|
||
JS_ThrowTypeError (s->ctx, "unsupported object class");
|
||
ret = -1;
|
||
break;
|
||
}
|
||
if (ret) goto fail;
|
||
}
|
||
break;
|
||
default:
|
||
JS_ThrowInternalError (s->ctx, "unsupported tag (%d)", tag);
|
||
goto fail;
|
||
}
|
||
return 0;
|
||
|
||
fail:
|
||
return -1;
|
||
}
|
||
|
||
/* create the atom table */
|
||
static int JS_WriteObjectAtoms (BCWriterState *s) {
|
||
DynBuf dbuf1;
|
||
int atoms_size;
|
||
|
||
dbuf1 = s->dbuf;
|
||
js_dbuf_init (s->ctx, &s->dbuf);
|
||
bc_put_u8 (s, BC_VERSION);
|
||
|
||
/* Atoms removed - write empty atom table */
|
||
bc_put_leb128 (s, 0);
|
||
/* XXX: should check for OOM in above phase */
|
||
|
||
/* move the atoms at the start */
|
||
/* XXX: could just append dbuf1 data, but it uses more memory if
|
||
dbuf1 is larger than dbuf */
|
||
atoms_size = s->dbuf.size;
|
||
if (dbuf_realloc (&dbuf1, dbuf1.size + atoms_size)) goto fail;
|
||
memmove (dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size);
|
||
memcpy (dbuf1.buf, s->dbuf.buf, atoms_size);
|
||
dbuf1.size += atoms_size;
|
||
dbuf_free (&s->dbuf);
|
||
s->dbuf = dbuf1;
|
||
return 0;
|
||
fail:
|
||
dbuf_free (&dbuf1);
|
||
return -1;
|
||
}
|
||
|
||
uint8_t *JS_WriteObject2 (JSContext *ctx, size_t *psize, JSValue obj, int flags, uint8_t ***psab_tab, size_t *psab_tab_len) {
|
||
BCWriterState ss, *s = &ss;
|
||
|
||
memset (s, 0, sizeof (*s));
|
||
s->ctx = ctx;
|
||
s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0);
|
||
s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0);
|
||
s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0);
|
||
js_dbuf_init (ctx, &s->dbuf);
|
||
js_object_list_init (&s->object_list);
|
||
|
||
if (JS_WriteObjectRec (s, obj)) goto fail;
|
||
if (JS_WriteObjectAtoms (s)) goto fail;
|
||
js_object_list_end (ctx, &s->object_list);
|
||
*psize = s->dbuf.size;
|
||
if (psab_tab) *psab_tab = s->sab_tab;
|
||
if (psab_tab_len) *psab_tab_len = s->sab_tab_len;
|
||
return s->dbuf.buf;
|
||
fail:
|
||
js_object_list_end (ctx, &s->object_list);
|
||
dbuf_free (&s->dbuf);
|
||
*psize = 0;
|
||
if (psab_tab) *psab_tab = NULL;
|
||
if (psab_tab_len) *psab_tab_len = 0;
|
||
return NULL;
|
||
}
|
||
|
||
uint8_t *JS_WriteObject (JSContext *ctx, size_t *psize, JSValue obj, int flags) {
|
||
return JS_WriteObject2 (ctx, psize, obj, flags, NULL, NULL);
|
||
}
|
||
|
||
typedef struct BCReaderState {
|
||
JSContext *ctx;
|
||
const uint8_t *buf_start, *ptr, *buf_end;
|
||
int error_state;
|
||
BOOL allow_sab : 8;
|
||
BOOL allow_bytecode : 8;
|
||
BOOL is_rom_data : 8;
|
||
BOOL allow_reference : 8;
|
||
/* object references */
|
||
JSRecord **objects;
|
||
int objects_count;
|
||
int objects_size;
|
||
|
||
#ifdef DUMP_READ_OBJECT
|
||
const uint8_t *ptr_last;
|
||
int level;
|
||
#endif
|
||
} BCReaderState;
|
||
|
||
#ifdef DUMP_READ_OBJECT
|
||
static void __attribute__ ((format (printf, 2, 3)))
|
||
bc_read_trace (BCReaderState *s, const char *fmt, ...) {
|
||
va_list ap;
|
||
int i, n, n0;
|
||
|
||
if (!s->ptr_last) s->ptr_last = s->buf_start;
|
||
|
||
n = n0 = 0;
|
||
if (s->ptr > s->ptr_last || s->ptr == s->buf_start) {
|
||
n0 = printf ("%04x: ", (int)(s->ptr_last - s->buf_start));
|
||
n += n0;
|
||
}
|
||
for (i = 0; s->ptr_last < s->ptr; i++) {
|
||
if ((i & 7) == 0 && i > 0) {
|
||
printf ("\n%*s", n0, "");
|
||
n = n0;
|
||
}
|
||
n += printf (" %02x", *s->ptr_last++);
|
||
}
|
||
if (*fmt == '}') s->level--;
|
||
if (n < 32 + s->level * 2) { printf ("%*s", 32 + s->level * 2 - n, ""); }
|
||
va_start (ap, fmt);
|
||
vfprintf (stdout, fmt, ap);
|
||
va_end (ap);
|
||
if (strchr (fmt, '{')) s->level++;
|
||
}
|
||
#else
|
||
#define bc_read_trace(...)
|
||
#endif
|
||
|
||
static int bc_read_error_end (BCReaderState *s) {
|
||
if (!s->error_state) {
|
||
JS_ThrowSyntaxError (s->ctx, "read after the end of the buffer");
|
||
}
|
||
return s->error_state = -1;
|
||
}
|
||
|
||
static int bc_get_u8 (BCReaderState *s, uint8_t *pval) {
|
||
if (unlikely (s->buf_end - s->ptr < 1)) {
|
||
*pval = 0; /* avoid warning */
|
||
return bc_read_error_end (s);
|
||
}
|
||
*pval = *s->ptr++;
|
||
return 0;
|
||
}
|
||
|
||
static int bc_get_u16 (BCReaderState *s, uint16_t *pval) {
|
||
uint16_t v;
|
||
if (unlikely (s->buf_end - s->ptr < 2)) {
|
||
*pval = 0; /* avoid warning */
|
||
return bc_read_error_end (s);
|
||
}
|
||
v = get_u16 (s->ptr);
|
||
if (is_be ()) v = bswap16 (v);
|
||
*pval = v;
|
||
s->ptr += 2;
|
||
return 0;
|
||
}
|
||
|
||
static __maybe_unused int bc_get_u32 (BCReaderState *s, uint32_t *pval) {
|
||
uint32_t v;
|
||
if (unlikely (s->buf_end - s->ptr < 4)) {
|
||
*pval = 0; /* avoid warning */
|
||
return bc_read_error_end (s);
|
||
}
|
||
v = get_u32 (s->ptr);
|
||
if (is_be ()) v = bswap32 (v);
|
||
*pval = v;
|
||
s->ptr += 4;
|
||
return 0;
|
||
}
|
||
|
||
static int bc_get_u64 (BCReaderState *s, uint64_t *pval) {
|
||
uint64_t v;
|
||
if (unlikely (s->buf_end - s->ptr < 8)) {
|
||
*pval = 0; /* avoid warning */
|
||
return bc_read_error_end (s);
|
||
}
|
||
v = get_u64 (s->ptr);
|
||
if (is_be ()) v = bswap64 (v);
|
||
*pval = v;
|
||
s->ptr += 8;
|
||
return 0;
|
||
}
|
||
|
||
static int bc_get_leb128 (BCReaderState *s, uint32_t *pval) {
|
||
int ret;
|
||
ret = get_leb128 (pval, s->ptr, s->buf_end);
|
||
if (unlikely (ret < 0)) return bc_read_error_end (s);
|
||
s->ptr += ret;
|
||
return 0;
|
||
}
|
||
|
||
static int bc_get_sleb128 (BCReaderState *s, int32_t *pval) {
|
||
int ret;
|
||
ret = get_sleb128 (pval, s->ptr, s->buf_end);
|
||
if (unlikely (ret < 0)) return bc_read_error_end (s);
|
||
s->ptr += ret;
|
||
return 0;
|
||
}
|
||
|
||
/* XXX: used to read an `int` with a positive value */
|
||
static int bc_get_leb128_int (BCReaderState *s, int *pval) {
|
||
return bc_get_leb128 (s, (uint32_t *)pval);
|
||
}
|
||
|
||
static int bc_get_leb128_u16 (BCReaderState *s, uint16_t *pval) {
|
||
uint32_t val;
|
||
if (bc_get_leb128 (s, &val)) {
|
||
*pval = 0;
|
||
return -1;
|
||
}
|
||
*pval = val;
|
||
return 0;
|
||
}
|
||
|
||
static int bc_get_buf (BCReaderState *s, uint8_t *buf, uint32_t buf_len) {
|
||
if (buf_len != 0) {
|
||
if (unlikely (!buf || s->buf_end - s->ptr < buf_len))
|
||
return bc_read_error_end (s);
|
||
memcpy (buf, s->ptr, buf_len);
|
||
s->ptr += buf_len;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Read a JSValue key (text) from bytecode stream */
|
||
static int bc_get_key (BCReaderState *s, JSValue *pkey) {
|
||
uint32_t len;
|
||
if (bc_get_leb128 (s, &len)) return -1;
|
||
if (len == 0) {
|
||
*pkey = JS_KEY_empty;
|
||
return 0;
|
||
}
|
||
/* Read UTF-8 bytes */
|
||
char *buf = alloca (len + 1);
|
||
for (uint32_t i = 0; i < len; i++) {
|
||
uint8_t byte;
|
||
if (bc_get_u8 (s, &byte)) return -1;
|
||
buf[i] = (char)byte;
|
||
}
|
||
buf[len] = '\0';
|
||
/* Create interned key from UTF-8 */
|
||
*pkey = js_key_new_len (s->ctx, buf, len);
|
||
return JS_IsNull (*pkey) ? -1 : 0;
|
||
}
|
||
|
||
static JSText *JS_ReadString (BCReaderState *s) {
|
||
uint32_t len;
|
||
size_t size;
|
||
JSText *p;
|
||
uint32_t i;
|
||
|
||
if (bc_get_leb128 (s, &len)) return NULL;
|
||
/* len is uint32_t, JS_STRING_LEN_MAX is 56 bits, so this always fits */
|
||
p = js_alloc_string (s->ctx, len);
|
||
if (!p) {
|
||
s->error_state = -1;
|
||
return NULL;
|
||
}
|
||
/* UTF-32: each character is 32-bit */
|
||
size = (size_t)len * sizeof (uint32_t);
|
||
if ((s->buf_end - s->ptr) < size) {
|
||
bc_read_error_end (s);
|
||
/* GC handles cleanup - partial string will be collected */
|
||
return NULL;
|
||
}
|
||
for (i = 0; i < len; i++) {
|
||
uint32_t c = get_u32 (s->ptr);
|
||
if (is_be ()) c = bswap32 (c);
|
||
string_put (p, i, c);
|
||
s->ptr += 4;
|
||
}
|
||
#ifdef DUMP_READ_OBJECT
|
||
JS_DumpString (s->ctx->rt, p);
|
||
printf ("\n");
|
||
#endif
|
||
return p;
|
||
}
|
||
|
||
static uint32_t bc_get_flags (uint32_t flags, int *pidx, int n) {
|
||
uint32_t val;
|
||
/* XXX: this does not work for n == 32 */
|
||
val = (flags >> *pidx) & ((1U << n) - 1);
|
||
*pidx += n;
|
||
return val;
|
||
}
|
||
|
||
static int JS_ReadFunctionBytecode (BCReaderState *s, JSFunctionBytecode *b, int byte_code_offset, uint32_t bc_len) {
|
||
uint8_t *bc_buf;
|
||
int pos, len, op;
|
||
|
||
if (s->is_rom_data) {
|
||
/* directly use the input buffer */
|
||
if (unlikely (s->buf_end - s->ptr < bc_len)) return bc_read_error_end (s);
|
||
bc_buf = (uint8_t *)s->ptr;
|
||
s->ptr += bc_len;
|
||
} else {
|
||
bc_buf = (void *)((uint8_t *)b + byte_code_offset);
|
||
if (bc_get_buf (s, bc_buf, bc_len)) return -1;
|
||
}
|
||
b->byte_code_buf = bc_buf;
|
||
|
||
if (is_be ()) bc_byte_swap (bc_buf, bc_len);
|
||
|
||
pos = 0;
|
||
while (pos < bc_len) {
|
||
op = bc_buf[pos];
|
||
len = short_opcode_info (op).size;
|
||
switch (short_opcode_info (op).fmt) {
|
||
case OP_FMT_key:
|
||
case OP_FMT_key_u8:
|
||
case OP_FMT_key_u16:
|
||
case OP_FMT_key_label_u16:
|
||
/* Key operand is a cpool index; cpool values deserialized separately */
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
pos += len;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static JSValue JS_ReadObjectRec (BCReaderState *s);
|
||
|
||
static int BC_add_object_ref1 (BCReaderState *s, JSRecord *p) {
|
||
if (s->allow_reference) {
|
||
if (js_resize_array (s->ctx, (void *)&s->objects, sizeof (s->objects[0]), &s->objects_size, s->objects_count + 1))
|
||
return -1;
|
||
s->objects[s->objects_count++] = p;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int BC_add_object_ref (BCReaderState *s, JSValue obj) {
|
||
return BC_add_object_ref1 (s, JS_VALUE_GET_OBJ (obj));
|
||
}
|
||
|
||
static JSValue JS_ReadFunctionTag (BCReaderState *s) {
|
||
JSContext *ctx = s->ctx;
|
||
JSFunctionBytecode bc, *b;
|
||
JSValue obj = JS_NULL;
|
||
uint16_t v16;
|
||
uint8_t v8;
|
||
int idx, i, local_count;
|
||
int function_size, cpool_offset, byte_code_offset;
|
||
int closure_var_offset, vardefs_offset;
|
||
|
||
memset (&bc, 0, sizeof (bc));
|
||
bc.header = objhdr_make (0, OBJ_CODE, false, false, false, false);
|
||
|
||
if (bc_get_u16 (s, &v16)) goto fail;
|
||
idx = 0;
|
||
bc.has_prototype = bc_get_flags (v16, &idx, 1);
|
||
bc.has_simple_parameter_list = bc_get_flags (v16, &idx, 1);
|
||
bc.func_kind = bc_get_flags (v16, &idx, 2);
|
||
bc.has_debug = bc_get_flags (v16, &idx, 1);
|
||
bc.is_direct_or_indirect_eval = bc_get_flags (v16, &idx, 1);
|
||
bc.read_only_bytecode = s->is_rom_data;
|
||
if (bc_get_u8 (s, &v8)) goto fail;
|
||
bc.js_mode = v8;
|
||
if (bc_get_key (s, &bc.func_name)) goto fail;
|
||
if (bc_get_leb128_u16 (s, &bc.arg_count)) goto fail;
|
||
if (bc_get_leb128_u16 (s, &bc.var_count)) goto fail;
|
||
if (bc_get_leb128_u16 (s, &bc.defined_arg_count)) goto fail;
|
||
if (bc_get_leb128_u16 (s, &bc.stack_size)) goto fail;
|
||
if (bc_get_leb128_int (s, &bc.closure_var_count)) goto fail;
|
||
if (bc_get_leb128_int (s, &bc.cpool_count)) goto fail;
|
||
if (bc_get_leb128_int (s, &bc.byte_code_len)) goto fail;
|
||
if (bc_get_leb128_int (s, &local_count)) goto fail;
|
||
|
||
if (bc.has_debug) {
|
||
function_size = sizeof (*b);
|
||
} else {
|
||
function_size = offsetof (JSFunctionBytecode, debug);
|
||
}
|
||
cpool_offset = function_size;
|
||
function_size += bc.cpool_count * sizeof (*bc.cpool);
|
||
vardefs_offset = function_size;
|
||
function_size += local_count * sizeof (*bc.vardefs);
|
||
closure_var_offset = function_size;
|
||
function_size += bc.closure_var_count * sizeof (*bc.closure_var);
|
||
byte_code_offset = function_size;
|
||
if (!bc.read_only_bytecode) { function_size += bc.byte_code_len; }
|
||
|
||
b = js_mallocz (ctx, function_size);
|
||
if (!b) return JS_EXCEPTION;
|
||
|
||
memcpy (b, &bc, offsetof (JSFunctionBytecode, debug));
|
||
b->header = objhdr_make (0, OBJ_CODE, false, false, false, false);
|
||
if (local_count != 0) {
|
||
b->vardefs = (void *)((uint8_t *)b + vardefs_offset);
|
||
}
|
||
if (b->closure_var_count != 0) {
|
||
b->closure_var = (void *)((uint8_t *)b + closure_var_offset);
|
||
}
|
||
if (b->cpool_count != 0) {
|
||
b->cpool = (void *)((uint8_t *)b + cpool_offset);
|
||
}
|
||
|
||
obj = JS_MKPTR (b);
|
||
|
||
#ifdef DUMP_READ_OBJECT
|
||
bc_read_trace (s, "name: ");
|
||
print_atom (s->ctx, b->func_name);
|
||
printf ("\n");
|
||
#endif
|
||
bc_read_trace (s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", b->arg_count, b->var_count, b->defined_arg_count, b->closure_var_count, b->cpool_count);
|
||
bc_read_trace (s, "stack=%d bclen=%d locals=%d\n", b->stack_size, b->byte_code_len, local_count);
|
||
|
||
if (local_count != 0) {
|
||
bc_read_trace (s, "vars {\n");
|
||
for (i = 0; i < local_count; i++) {
|
||
JSVarDef *vd = &b->vardefs[i];
|
||
if (bc_get_key (s, &vd->var_name)) goto fail;
|
||
if (bc_get_leb128_int (s, &vd->scope_level)) goto fail;
|
||
if (bc_get_leb128_int (s, &vd->scope_next)) goto fail;
|
||
vd->scope_next--;
|
||
if (bc_get_u8 (s, &v8)) goto fail;
|
||
idx = 0;
|
||
vd->var_kind = bc_get_flags (v8, &idx, 4);
|
||
vd->is_const = bc_get_flags (v8, &idx, 1);
|
||
vd->is_lexical = bc_get_flags (v8, &idx, 1);
|
||
vd->is_captured = bc_get_flags (v8, &idx, 1);
|
||
#ifdef DUMP_READ_OBJECT
|
||
bc_read_trace (s, "name: ");
|
||
print_atom (s->ctx, vd->var_name);
|
||
printf ("\n");
|
||
#endif
|
||
}
|
||
bc_read_trace (s, "}\n");
|
||
}
|
||
if (b->closure_var_count != 0) {
|
||
bc_read_trace (s, "closure vars {\n");
|
||
for (i = 0; i < b->closure_var_count; i++) {
|
||
JSClosureVar *cv = &b->closure_var[i];
|
||
int var_idx;
|
||
if (bc_get_key (s, &cv->var_name)) goto fail;
|
||
if (bc_get_leb128_int (s, &var_idx)) goto fail;
|
||
cv->var_idx = var_idx;
|
||
if (bc_get_u8 (s, &v8)) goto fail;
|
||
idx = 0;
|
||
cv->is_local = bc_get_flags (v8, &idx, 1);
|
||
cv->is_arg = bc_get_flags (v8, &idx, 1);
|
||
cv->is_const = bc_get_flags (v8, &idx, 1);
|
||
cv->is_lexical = bc_get_flags (v8, &idx, 1);
|
||
cv->var_kind = bc_get_flags (v8, &idx, 4);
|
||
#ifdef DUMP_READ_OBJECT
|
||
bc_read_trace (s, "name: ");
|
||
print_atom (s->ctx, cv->var_name);
|
||
printf ("\n");
|
||
#endif
|
||
}
|
||
bc_read_trace (s, "}\n");
|
||
}
|
||
{
|
||
bc_read_trace (s, "bytecode {\n");
|
||
if (JS_ReadFunctionBytecode (s, b, byte_code_offset, b->byte_code_len))
|
||
goto fail;
|
||
bc_read_trace (s, "}\n");
|
||
}
|
||
if (b->has_debug) {
|
||
/* read optional debug information */
|
||
bc_read_trace (s, "debug {\n");
|
||
if (bc_get_key (s, &b->debug.filename)) goto fail;
|
||
#ifdef DUMP_READ_OBJECT
|
||
bc_read_trace (s, "filename: ");
|
||
print_atom (s->ctx, b->debug.filename);
|
||
printf ("\n");
|
||
#endif
|
||
if (bc_get_leb128_int (s, &b->debug.pc2line_len)) goto fail;
|
||
if (b->debug.pc2line_len) {
|
||
b->debug.pc2line_buf = js_mallocz (ctx, b->debug.pc2line_len);
|
||
if (!b->debug.pc2line_buf) goto fail;
|
||
if (bc_get_buf (s, b->debug.pc2line_buf, b->debug.pc2line_len))
|
||
goto fail;
|
||
}
|
||
if (bc_get_leb128_int (s, &b->debug.source_len)) goto fail;
|
||
if (b->debug.source_len) {
|
||
bc_read_trace (s, "source: %d bytes\n", b->source_len);
|
||
b->debug.source = js_mallocz (ctx, b->debug.source_len);
|
||
if (!b->debug.source) goto fail;
|
||
if (bc_get_buf (s, (uint8_t *)b->debug.source, b->debug.source_len))
|
||
goto fail;
|
||
}
|
||
bc_read_trace (s, "}\n");
|
||
}
|
||
if (b->cpool_count != 0) {
|
||
bc_read_trace (s, "cpool {\n");
|
||
for (i = 0; i < b->cpool_count; i++) {
|
||
JSValue val;
|
||
val = JS_ReadObjectRec (s);
|
||
if (JS_IsException (val)) goto fail;
|
||
b->cpool[i] = val;
|
||
}
|
||
bc_read_trace (s, "}\n");
|
||
}
|
||
return obj;
|
||
fail:
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
static JSValue JS_ReadObjectTag (BCReaderState *s) {
|
||
JSContext *ctx = s->ctx;
|
||
JSValue obj;
|
||
uint32_t prop_count, i;
|
||
JSValue key;
|
||
JSValue val;
|
||
int ret;
|
||
|
||
obj = JS_NewObject (ctx);
|
||
if (BC_add_object_ref (s, obj)) goto fail;
|
||
if (bc_get_leb128 (s, &prop_count)) goto fail;
|
||
for (i = 0; i < prop_count; i++) {
|
||
if (bc_get_key (s, &key)) goto fail;
|
||
#ifdef DUMP_READ_OBJECT
|
||
bc_read_trace (s, "propname: ");
|
||
JS_DumpValue (key);
|
||
printf ("\n");
|
||
#endif
|
||
val = JS_ReadObjectRec (s);
|
||
if (JS_IsException (val)) {
|
||
goto fail;
|
||
}
|
||
ret = JS_SetPropertyInternal (ctx, obj, key, val);
|
||
if (ret < 0) goto fail;
|
||
}
|
||
return obj;
|
||
fail:
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
static JSValue JS_ReadArray (BCReaderState *s, int tag) {
|
||
JSContext *ctx = s->ctx;
|
||
JSValue obj;
|
||
uint32_t len, i;
|
||
JSValue val;
|
||
BOOL is_template;
|
||
|
||
is_template = (tag == BC_TAG_TEMPLATE_OBJECT);
|
||
|
||
if (bc_get_leb128 (s, &len)) return JS_EXCEPTION;
|
||
|
||
/* Create intrinsic array with preallocated capacity */
|
||
obj = JS_NewArrayLen (ctx, len);
|
||
if (JS_IsException (obj)) return JS_EXCEPTION;
|
||
|
||
/* Note: intrinsic arrays don't support object reference tracking
|
||
for circular references - they're simpler value containers */
|
||
|
||
/* Read and set each element directly */
|
||
JSArray *arr = JS_VALUE_GET_ARRAY (obj);
|
||
for (i = 0; i < len; i++) {
|
||
val = JS_ReadObjectRec (s);
|
||
if (JS_IsException (val)) goto fail;
|
||
/* Replace the null placeholder with the read value */
|
||
arr->values[i] = val;
|
||
}
|
||
|
||
/* Template objects have additional 'raw' property - not supported for
|
||
* intrinsic arrays */
|
||
if (is_template) {
|
||
val = JS_ReadObjectRec (s);
|
||
if (JS_IsException (val)) goto fail;
|
||
/* Skip the raw property for intrinsic arrays */
|
||
}
|
||
return obj;
|
||
fail:
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
static JSValue JS_ReadObjectRec (BCReaderState *s) {
|
||
JSContext *ctx = s->ctx;
|
||
uint8_t tag;
|
||
JSValue obj = JS_NULL;
|
||
|
||
if (js_check_stack_overflow (ctx, 0)) return JS_ThrowStackOverflow (ctx);
|
||
|
||
if (bc_get_u8 (s, &tag)) return JS_EXCEPTION;
|
||
|
||
bc_read_trace (s, "%s {\n", bc_tag_str[tag]);
|
||
|
||
switch (tag) {
|
||
case BC_TAG_NULL:
|
||
obj = JS_NULL;
|
||
break;
|
||
case BC_TAG_BOOL_FALSE:
|
||
case BC_TAG_BOOL_TRUE:
|
||
obj = JS_NewBool (ctx, tag - BC_TAG_BOOL_FALSE);
|
||
break;
|
||
case BC_TAG_INT32: {
|
||
int32_t val;
|
||
if (bc_get_sleb128 (s, &val)) return JS_EXCEPTION;
|
||
bc_read_trace (s, "%d\n", val);
|
||
obj = JS_NewInt32 (ctx, val);
|
||
} break;
|
||
case BC_TAG_FLOAT64: {
|
||
JSFloat64Union u;
|
||
if (bc_get_u64 (s, &u.u64)) return JS_EXCEPTION;
|
||
bc_read_trace (s, "%g\n", u.d);
|
||
obj = __JS_NewFloat64 (ctx, u.d);
|
||
} break;
|
||
case BC_TAG_STRING: {
|
||
JSText *p;
|
||
p = JS_ReadString (s);
|
||
if (!p) return JS_EXCEPTION;
|
||
obj = JS_MKPTR (p);
|
||
} break;
|
||
case BC_TAG_FUNCTION_BYTECODE:
|
||
if (!s->allow_bytecode) goto invalid_tag;
|
||
obj = JS_ReadFunctionTag (s);
|
||
break;
|
||
case BC_TAG_OBJECT:
|
||
obj = JS_ReadObjectTag (s);
|
||
break;
|
||
case BC_TAG_ARRAY:
|
||
case BC_TAG_TEMPLATE_OBJECT:
|
||
obj = JS_ReadArray (s, tag);
|
||
break;
|
||
case BC_TAG_OBJECT_REFERENCE: {
|
||
uint32_t val;
|
||
if (!s->allow_reference)
|
||
return JS_ThrowSyntaxError (ctx, "object references are not allowed");
|
||
if (bc_get_leb128 (s, &val)) return JS_EXCEPTION;
|
||
bc_read_trace (s, "%u\n", val);
|
||
if (val >= s->objects_count) {
|
||
return JS_ThrowSyntaxError (ctx, "invalid object reference (%u >= %u)", val, s->objects_count);
|
||
}
|
||
obj = JS_MKPTR (s->objects[val]);
|
||
} break;
|
||
default:
|
||
invalid_tag:
|
||
return JS_ThrowSyntaxError (ctx, "invalid tag (tag=%d pos=%u)", tag, (unsigned int)(s->ptr - s->buf_start));
|
||
}
|
||
bc_read_trace (s, "}\n");
|
||
return obj;
|
||
}
|
||
|
||
static int JS_ReadObjectAtoms (BCReaderState *s) {
|
||
uint8_t v8;
|
||
uint32_t atom_count;
|
||
|
||
if (bc_get_u8 (s, &v8)) return -1;
|
||
if (v8 != BC_VERSION) {
|
||
JS_ThrowSyntaxError (s->ctx, "invalid version (%d expected=%d)", v8, BC_VERSION);
|
||
return -1;
|
||
}
|
||
/* Read atom count - should always be 0 for new bytecode */
|
||
if (bc_get_leb128 (s, &atom_count)) return -1;
|
||
bc_read_trace (s, "%d atom indexes\n", atom_count);
|
||
if (atom_count != 0) {
|
||
JS_ThrowSyntaxError (s->ctx, "legacy atom format not supported");
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void bc_reader_free (BCReaderState *s) {
|
||
js_free (s->ctx, s->objects);
|
||
}
|
||
|
||
JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags) {
|
||
BCReaderState ss, *s = &ss;
|
||
JSValue obj;
|
||
|
||
ctx->binary_object_count += 1;
|
||
ctx->binary_object_size += buf_len;
|
||
|
||
memset (s, 0, sizeof (*s));
|
||
s->ctx = ctx;
|
||
s->buf_start = buf;
|
||
s->buf_end = buf + buf_len;
|
||
s->ptr = buf;
|
||
s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0);
|
||
s->is_rom_data = ((flags & JS_READ_OBJ_ROM_DATA) != 0);
|
||
s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0);
|
||
s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0);
|
||
if (JS_ReadObjectAtoms (s)) {
|
||
obj = JS_EXCEPTION;
|
||
} else {
|
||
obj = JS_ReadObjectRec (s);
|
||
}
|
||
bc_reader_free (s);
|
||
return obj;
|
||
}
|
||
|
||
/*******************************************************************/
|
||
/* JSCompiledUnit Serialization */
|
||
|
||
/* Magic number for compiled unit files */
|
||
#define COMPILED_UNIT_MAGIC 0x43454C4C /* "CELL" */
|
||
#define COMPILED_UNIT_VERSION 1
|
||
|
||
/* Write a JSCompiledUnit to a byte buffer.
|
||
Returns allocated buffer (caller must free), or NULL on error.
|
||
The format is:
|
||
- magic (4 bytes): "CELL"
|
||
- version (1 byte)
|
||
- flags (1 byte): has_debug
|
||
- local_count (2 bytes)
|
||
- stack_size (2 bytes)
|
||
- cpool_count (4 bytes)
|
||
- byte_code_len (4 bytes)
|
||
- cpool values (variable)
|
||
- bytecode (byte_code_len bytes)
|
||
- debug section (if has_debug)
|
||
*/
|
||
uint8_t *JS_WriteCompiledUnit (JSContext *ctx, JSCompiledUnit *unit, size_t *out_len) {
|
||
DynBuf dbuf;
|
||
dbuf_init (&dbuf);
|
||
|
||
/* Magic */
|
||
dbuf_put_u32 (&dbuf, COMPILED_UNIT_MAGIC);
|
||
|
||
/* Version */
|
||
dbuf_putc (&dbuf, COMPILED_UNIT_VERSION);
|
||
|
||
/* Flags */
|
||
uint8_t flags = 0;
|
||
if (unit->has_debug) flags |= 1;
|
||
dbuf_putc (&dbuf, flags);
|
||
|
||
/* Stack requirements */
|
||
dbuf_put_u16 (&dbuf, unit->local_count);
|
||
dbuf_put_u16 (&dbuf, unit->stack_size);
|
||
|
||
/* Counts */
|
||
dbuf_put_u32 (&dbuf, unit->cpool_count);
|
||
dbuf_put_u32 (&dbuf, unit->byte_code_len);
|
||
|
||
/* Write constant pool (simplified - just strings for now) */
|
||
for (int i = 0; i < unit->cpool_count; i++) {
|
||
JSValue val = unit->cpool[i];
|
||
uint32_t tag = JS_VALUE_GET_TAG (val);
|
||
|
||
if (tag == JS_TAG_INT) {
|
||
dbuf_putc (&dbuf, 1); /* type: int */
|
||
int32_t v = JS_VALUE_GET_INT (val);
|
||
dbuf_put_u32 (&dbuf, (uint32_t)v);
|
||
} else if (tag == JS_TAG_FLOAT64) {
|
||
dbuf_putc (&dbuf, 2); /* type: float */
|
||
double d = JS_VALUE_GET_FLOAT64 (val);
|
||
dbuf_put (&dbuf, (uint8_t *)&d, sizeof (d));
|
||
} else if (JS_IsText (val)) {
|
||
dbuf_putc (&dbuf, 3); /* type: string */
|
||
const char *str = JS_ToCString (ctx, val);
|
||
if (str) {
|
||
size_t len = strlen (str);
|
||
dbuf_put_u32 (&dbuf, (uint32_t)len);
|
||
dbuf_put (&dbuf, (uint8_t *)str, len);
|
||
JS_FreeCString (ctx, str);
|
||
} else {
|
||
dbuf_put_u32 (&dbuf, 0);
|
||
}
|
||
} else {
|
||
dbuf_putc (&dbuf, 0); /* type: null/unsupported */
|
||
}
|
||
}
|
||
|
||
/* Write bytecode */
|
||
dbuf_put (&dbuf, unit->byte_code_buf, unit->byte_code_len);
|
||
|
||
/* Write debug section if present */
|
||
if (unit->has_debug) {
|
||
/* Filename */
|
||
const char *fname = JS_ToCString (ctx, unit->debug.filename);
|
||
if (fname) {
|
||
size_t len = strlen (fname);
|
||
dbuf_put_u32 (&dbuf, (uint32_t)len);
|
||
dbuf_put (&dbuf, (uint8_t *)fname, len);
|
||
JS_FreeCString (ctx, fname);
|
||
} else {
|
||
dbuf_put_u32 (&dbuf, 0);
|
||
}
|
||
|
||
/* source_len, pc2line_len */
|
||
dbuf_put_u32 (&dbuf, unit->debug.source_len);
|
||
dbuf_put_u32 (&dbuf, unit->debug.pc2line_len);
|
||
|
||
/* pc2line_buf */
|
||
if (unit->debug.pc2line_len > 0 && unit->debug.pc2line_buf) {
|
||
dbuf_put (&dbuf, unit->debug.pc2line_buf, unit->debug.pc2line_len);
|
||
}
|
||
|
||
/* source */
|
||
if (unit->debug.source_len > 0 && unit->debug.source) {
|
||
dbuf_put (&dbuf, (uint8_t *)unit->debug.source, unit->debug.source_len);
|
||
}
|
||
}
|
||
|
||
*out_len = dbuf.size;
|
||
return dbuf.buf;
|
||
}
|
||
|
||
/* Read a JSCompiledUnit from a byte buffer.
|
||
Returns unit on success, NULL on error. */
|
||
JSCompiledUnit *JS_ReadCompiledUnit (JSContext *ctx, const uint8_t *buf, size_t buf_len) {
|
||
const uint8_t *p = buf;
|
||
const uint8_t *end = buf + buf_len;
|
||
|
||
if (buf_len < 18) return NULL; /* Minimum header size */
|
||
|
||
/* Check magic */
|
||
uint32_t magic = get_u32 (p); p += 4;
|
||
if (magic != COMPILED_UNIT_MAGIC) return NULL;
|
||
|
||
/* Version */
|
||
uint8_t version = *p++;
|
||
if (version != COMPILED_UNIT_VERSION) return NULL;
|
||
|
||
/* Flags */
|
||
uint8_t flags = *p++;
|
||
BOOL has_debug = (flags & 1) != 0;
|
||
|
||
/* Stack requirements */
|
||
uint16_t local_count = get_u16 (p); p += 2;
|
||
uint16_t stack_size = get_u16 (p); p += 2;
|
||
|
||
/* Counts */
|
||
uint32_t cpool_count = get_u32 (p); p += 4;
|
||
uint32_t byte_code_len = get_u32 (p); p += 4;
|
||
|
||
/* Calculate allocation size */
|
||
size_t unit_size;
|
||
if (has_debug) {
|
||
unit_size = sizeof (JSCompiledUnit);
|
||
} else {
|
||
unit_size = offsetof (JSCompiledUnit, debug);
|
||
}
|
||
size_t cpool_offset = unit_size;
|
||
unit_size += cpool_count * sizeof (JSValue);
|
||
size_t bc_offset = unit_size;
|
||
unit_size += byte_code_len;
|
||
|
||
/* Allocate unit */
|
||
JSCompiledUnit *unit = pjs_mallocz (unit_size);
|
||
if (!unit) return NULL;
|
||
|
||
/* Initialize header */
|
||
unit->header = objhdr_make (0, OBJ_CODE, false, false, false, false);
|
||
unit->has_debug = has_debug;
|
||
unit->read_only_bytecode = 0;
|
||
unit->local_count = local_count;
|
||
unit->stack_size = stack_size;
|
||
unit->cpool_count = cpool_count;
|
||
unit->byte_code_len = byte_code_len;
|
||
|
||
/* Setup pointers */
|
||
if (cpool_count > 0) {
|
||
unit->cpool = (JSValue *)((uint8_t *)unit + cpool_offset);
|
||
} else {
|
||
unit->cpool = NULL;
|
||
}
|
||
unit->byte_code_buf = (uint8_t *)unit + bc_offset;
|
||
|
||
/* Read constant pool */
|
||
for (uint32_t i = 0; i < cpool_count; i++) {
|
||
if (p >= end) goto fail;
|
||
uint8_t type = *p++;
|
||
|
||
switch (type) {
|
||
case 0: /* null */
|
||
unit->cpool[i] = JS_NULL;
|
||
break;
|
||
case 1: /* int */
|
||
if (p + 4 > end) goto fail;
|
||
unit->cpool[i] = JS_NewInt32 (ctx, (int32_t)get_u32 (p));
|
||
p += 4;
|
||
break;
|
||
case 2: /* float */
|
||
if (p + 8 > end) goto fail;
|
||
{
|
||
double d;
|
||
memcpy (&d, p, sizeof (d));
|
||
unit->cpool[i] = JS_NewFloat64 (ctx, d);
|
||
p += 8;
|
||
}
|
||
break;
|
||
case 3: /* string */
|
||
if (p + 4 > end) goto fail;
|
||
{
|
||
uint32_t len = get_u32 (p); p += 4;
|
||
if (p + len > end) goto fail;
|
||
unit->cpool[i] = JS_NewStringLen (ctx, (const char *)p, len);
|
||
p += len;
|
||
}
|
||
break;
|
||
default:
|
||
unit->cpool[i] = JS_NULL;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Read bytecode */
|
||
if (p + byte_code_len > end) goto fail;
|
||
memcpy (unit->byte_code_buf, p, byte_code_len);
|
||
p += byte_code_len;
|
||
|
||
/* Read debug section if present */
|
||
if (has_debug) {
|
||
/* Filename */
|
||
if (p + 4 > end) goto fail;
|
||
uint32_t fname_len = get_u32 (p); p += 4;
|
||
if (p + fname_len > end) goto fail;
|
||
if (fname_len > 0) {
|
||
unit->debug.filename = JS_NewStringLen (ctx, (const char *)p, fname_len);
|
||
} else {
|
||
unit->debug.filename = JS_NULL;
|
||
}
|
||
p += fname_len;
|
||
|
||
/* source_len, pc2line_len */
|
||
if (p + 8 > end) goto fail;
|
||
unit->debug.source_len = get_u32 (p); p += 4;
|
||
unit->debug.pc2line_len = get_u32 (p); p += 4;
|
||
|
||
/* pc2line_buf */
|
||
if (unit->debug.pc2line_len > 0) {
|
||
if (p + unit->debug.pc2line_len > end) goto fail;
|
||
unit->debug.pc2line_buf = js_malloc (ctx, unit->debug.pc2line_len);
|
||
if (!unit->debug.pc2line_buf) goto fail;
|
||
memcpy (unit->debug.pc2line_buf, p, unit->debug.pc2line_len);
|
||
p += unit->debug.pc2line_len;
|
||
} else {
|
||
unit->debug.pc2line_buf = NULL;
|
||
}
|
||
|
||
/* source */
|
||
if (unit->debug.source_len > 0) {
|
||
if (p + unit->debug.source_len > end) goto fail;
|
||
unit->debug.source = js_malloc (ctx, unit->debug.source_len + 1);
|
||
if (!unit->debug.source) goto fail;
|
||
memcpy (unit->debug.source, p, unit->debug.source_len);
|
||
unit->debug.source[unit->debug.source_len] = '\0';
|
||
p += unit->debug.source_len;
|
||
} else {
|
||
unit->debug.source = NULL;
|
||
}
|
||
}
|
||
|
||
return unit;
|
||
|
||
fail:
|
||
pjs_free (unit);
|
||
return NULL;
|
||
}
|
||
|
||
/*******************************************************************/
|
||
/* CellModule Serialization (context-neutral) */
|
||
|
||
/* Free a CellModule and all its contents */
|
||
void cell_module_free (CellModule *mod) {
|
||
if (!mod) return;
|
||
|
||
/* Free string table */
|
||
if (mod->string_data) pjs_free (mod->string_data);
|
||
if (mod->string_offsets) pjs_free (mod->string_offsets);
|
||
|
||
/* Free units */
|
||
if (mod->units) {
|
||
for (uint32_t i = 0; i < mod->unit_count; i++) {
|
||
CellUnit *u = &mod->units[i];
|
||
if (u->constants) pjs_free (u->constants);
|
||
if (u->bytecode) pjs_free (u->bytecode);
|
||
if (u->upvalues) pjs_free (u->upvalues);
|
||
if (u->externals) pjs_free (u->externals);
|
||
if (u->pc2line) pjs_free (u->pc2line);
|
||
}
|
||
pjs_free (mod->units);
|
||
}
|
||
|
||
/* Free source */
|
||
if (mod->source) pjs_free (mod->source);
|
||
|
||
pjs_free (mod);
|
||
}
|
||
|
||
/* Write a CellModule to a byte buffer (context-neutral format).
|
||
Returns allocated buffer (caller must free with pjs_free), or NULL on error.
|
||
Format:
|
||
- magic (4 bytes): 0x4C4C4543 "CELL"
|
||
- version (1 byte)
|
||
- flags (1 byte)
|
||
- string_count (4 bytes)
|
||
- string_data_size (4 bytes)
|
||
- string_data (string_data_size bytes)
|
||
- string_offsets (string_count * 4 bytes)
|
||
- unit_count (4 bytes)
|
||
- for each unit:
|
||
- const_count (4 bytes)
|
||
- for each const: type (1 byte), value (4-8 bytes)
|
||
- bytecode_len (4 bytes)
|
||
- bytecode (bytecode_len bytes)
|
||
- arg_count (2 bytes)
|
||
- var_count (2 bytes)
|
||
- stack_size (2 bytes)
|
||
- upvalue_count (2 bytes)
|
||
- for each upvalue: kind (1 byte), index (2 bytes)
|
||
- external_count (4 bytes)
|
||
- for each external: pc_offset (4), name_sid (4), kind (1)
|
||
- pc2line_len (4 bytes)
|
||
- pc2line (pc2line_len bytes)
|
||
- name_sid (4 bytes)
|
||
- source_len (4 bytes)
|
||
- source (source_len bytes)
|
||
*/
|
||
uint8_t *cell_module_write (CellModule *mod, size_t *out_len) {
|
||
DynBuf buf;
|
||
dbuf_init (&buf);
|
||
|
||
/* Header */
|
||
dbuf_put_u32 (&buf, mod->magic);
|
||
dbuf_putc (&buf, mod->version);
|
||
dbuf_putc (&buf, mod->flags);
|
||
|
||
/* String table */
|
||
dbuf_put_u32 (&buf, mod->string_count);
|
||
dbuf_put_u32 (&buf, mod->string_data_size);
|
||
if (mod->string_data_size > 0 && mod->string_data) {
|
||
dbuf_put (&buf, mod->string_data, mod->string_data_size);
|
||
}
|
||
if (mod->string_count > 0 && mod->string_offsets) {
|
||
for (uint32_t i = 0; i < mod->string_count; i++) {
|
||
dbuf_put_u32 (&buf, mod->string_offsets[i]);
|
||
}
|
||
}
|
||
|
||
/* Units */
|
||
dbuf_put_u32 (&buf, mod->unit_count);
|
||
for (uint32_t u = 0; u < mod->unit_count; u++) {
|
||
CellUnit *unit = &mod->units[u];
|
||
|
||
/* Constants */
|
||
dbuf_put_u32 (&buf, unit->const_count);
|
||
for (uint32_t c = 0; c < unit->const_count; c++) {
|
||
CellConst *cc = &unit->constants[c];
|
||
dbuf_putc (&buf, cc->type);
|
||
switch (cc->type) {
|
||
case CELL_CONST_NULL:
|
||
break;
|
||
case CELL_CONST_INT:
|
||
dbuf_put_u32 (&buf, (uint32_t)cc->i32);
|
||
break;
|
||
case CELL_CONST_FLOAT: {
|
||
JSFloat64Union fu;
|
||
fu.d = cc->f64;
|
||
dbuf_put_u32 (&buf, (uint32_t)(fu.u64 & 0xFFFFFFFF));
|
||
dbuf_put_u32 (&buf, (uint32_t)(fu.u64 >> 32));
|
||
break;
|
||
}
|
||
case CELL_CONST_STRING:
|
||
case CELL_CONST_UNIT:
|
||
dbuf_put_u32 (&buf, cc->string_sid);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Bytecode */
|
||
dbuf_put_u32 (&buf, unit->bytecode_len);
|
||
if (unit->bytecode_len > 0 && unit->bytecode) {
|
||
dbuf_put (&buf, unit->bytecode, unit->bytecode_len);
|
||
}
|
||
|
||
/* Stack requirements */
|
||
dbuf_put_u16 (&buf, unit->arg_count);
|
||
dbuf_put_u16 (&buf, unit->var_count);
|
||
dbuf_put_u16 (&buf, unit->stack_size);
|
||
|
||
/* Upvalues */
|
||
dbuf_put_u16 (&buf, unit->upvalue_count);
|
||
for (uint16_t i = 0; i < unit->upvalue_count; i++) {
|
||
dbuf_putc (&buf, unit->upvalues[i].kind);
|
||
dbuf_put_u16 (&buf, unit->upvalues[i].index);
|
||
}
|
||
|
||
/* Externals */
|
||
dbuf_put_u32 (&buf, unit->external_count);
|
||
for (uint32_t i = 0; i < unit->external_count; i++) {
|
||
dbuf_put_u32 (&buf, unit->externals[i].pc_offset);
|
||
dbuf_put_u32 (&buf, unit->externals[i].name_sid);
|
||
dbuf_putc (&buf, unit->externals[i].kind);
|
||
}
|
||
|
||
/* Debug */
|
||
dbuf_put_u32 (&buf, unit->pc2line_len);
|
||
if (unit->pc2line_len > 0 && unit->pc2line) {
|
||
dbuf_put (&buf, unit->pc2line, unit->pc2line_len);
|
||
}
|
||
dbuf_put_u32 (&buf, unit->name_sid);
|
||
}
|
||
|
||
/* Source */
|
||
dbuf_put_u32 (&buf, mod->source_len);
|
||
if (mod->source_len > 0 && mod->source) {
|
||
dbuf_put (&buf, (uint8_t *)mod->source, mod->source_len);
|
||
}
|
||
|
||
if (buf.error) {
|
||
dbuf_free (&buf);
|
||
*out_len = 0;
|
||
return NULL;
|
||
}
|
||
|
||
*out_len = buf.size;
|
||
return buf.buf;
|
||
}
|
||
|
||
/* Read a CellModule from a byte buffer.
|
||
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
|
||
CellModule *cell_module_read (const uint8_t *buf, size_t buf_len) {
|
||
const uint8_t *p = buf;
|
||
const uint8_t *end = buf + buf_len;
|
||
|
||
if (buf_len < 14) return NULL; /* minimum header size */
|
||
|
||
CellModule *mod = pjs_mallocz (sizeof (CellModule));
|
||
if (!mod) return NULL;
|
||
|
||
/* Header */
|
||
mod->magic = get_u32 (p); p += 4;
|
||
if (mod->magic != CELL_MODULE_MAGIC) goto fail;
|
||
mod->version = *p++;
|
||
if (mod->version != CELL_MODULE_VERSION) goto fail;
|
||
mod->flags = *p++;
|
||
|
||
/* String table */
|
||
if (p + 8 > end) goto fail;
|
||
mod->string_count = get_u32 (p); p += 4;
|
||
mod->string_data_size = get_u32 (p); p += 4;
|
||
|
||
if (mod->string_data_size > 0) {
|
||
if (p + mod->string_data_size > end) goto fail;
|
||
mod->string_data = pjs_malloc (mod->string_data_size);
|
||
if (!mod->string_data) goto fail;
|
||
memcpy (mod->string_data, p, mod->string_data_size);
|
||
p += mod->string_data_size;
|
||
}
|
||
|
||
if (mod->string_count > 0) {
|
||
if (p + mod->string_count * 4 > end) goto fail;
|
||
mod->string_offsets = pjs_malloc (mod->string_count * sizeof (uint32_t));
|
||
if (!mod->string_offsets) goto fail;
|
||
for (uint32_t i = 0; i < mod->string_count; i++) {
|
||
mod->string_offsets[i] = get_u32 (p); p += 4;
|
||
}
|
||
}
|
||
|
||
/* Units */
|
||
if (p + 4 > end) goto fail;
|
||
mod->unit_count = get_u32 (p); p += 4;
|
||
|
||
if (mod->unit_count > 0) {
|
||
mod->units = pjs_mallocz (mod->unit_count * sizeof (CellUnit));
|
||
if (!mod->units) goto fail;
|
||
|
||
for (uint32_t u = 0; u < mod->unit_count; u++) {
|
||
CellUnit *unit = &mod->units[u];
|
||
|
||
/* Constants */
|
||
if (p + 4 > end) goto fail;
|
||
unit->const_count = get_u32 (p); p += 4;
|
||
|
||
if (unit->const_count > 0) {
|
||
unit->constants = pjs_mallocz (unit->const_count * sizeof (CellConst));
|
||
if (!unit->constants) goto fail;
|
||
|
||
for (uint32_t c = 0; c < unit->const_count; c++) {
|
||
if (p + 1 > end) goto fail;
|
||
unit->constants[c].type = *p++;
|
||
switch (unit->constants[c].type) {
|
||
case CELL_CONST_NULL:
|
||
break;
|
||
case CELL_CONST_INT:
|
||
if (p + 4 > end) goto fail;
|
||
unit->constants[c].i32 = (int32_t)get_u32 (p); p += 4;
|
||
break;
|
||
case CELL_CONST_FLOAT: {
|
||
if (p + 8 > end) goto fail;
|
||
JSFloat64Union fu;
|
||
fu.u64 = get_u32 (p);
|
||
fu.u64 |= ((uint64_t)get_u32 (p + 4)) << 32;
|
||
p += 8;
|
||
unit->constants[c].f64 = fu.d;
|
||
break;
|
||
}
|
||
case CELL_CONST_STRING:
|
||
case CELL_CONST_UNIT:
|
||
if (p + 4 > end) goto fail;
|
||
unit->constants[c].string_sid = get_u32 (p); p += 4;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Bytecode */
|
||
if (p + 4 > end) goto fail;
|
||
unit->bytecode_len = get_u32 (p); p += 4;
|
||
|
||
if (unit->bytecode_len > 0) {
|
||
if (p + unit->bytecode_len > end) goto fail;
|
||
unit->bytecode = pjs_malloc (unit->bytecode_len);
|
||
if (!unit->bytecode) goto fail;
|
||
memcpy (unit->bytecode, p, unit->bytecode_len);
|
||
p += unit->bytecode_len;
|
||
}
|
||
|
||
/* Stack requirements */
|
||
if (p + 6 > end) goto fail;
|
||
unit->arg_count = get_u16 (p); p += 2;
|
||
unit->var_count = get_u16 (p); p += 2;
|
||
unit->stack_size = get_u16 (p); p += 2;
|
||
|
||
/* Upvalues */
|
||
if (p + 2 > end) goto fail;
|
||
unit->upvalue_count = get_u16 (p); p += 2;
|
||
|
||
if (unit->upvalue_count > 0) {
|
||
unit->upvalues = pjs_malloc (unit->upvalue_count * sizeof (CellCapDesc));
|
||
if (!unit->upvalues) goto fail;
|
||
for (uint16_t i = 0; i < unit->upvalue_count; i++) {
|
||
if (p + 3 > end) goto fail;
|
||
unit->upvalues[i].kind = *p++;
|
||
unit->upvalues[i].index = get_u16 (p); p += 2;
|
||
}
|
||
}
|
||
|
||
/* Externals */
|
||
if (p + 4 > end) goto fail;
|
||
unit->external_count = get_u32 (p); p += 4;
|
||
|
||
if (unit->external_count > 0) {
|
||
unit->externals = pjs_malloc (unit->external_count * sizeof (CellExternalReloc));
|
||
if (!unit->externals) goto fail;
|
||
for (uint32_t i = 0; i < unit->external_count; i++) {
|
||
if (p + 9 > end) goto fail;
|
||
unit->externals[i].pc_offset = get_u32 (p); p += 4;
|
||
unit->externals[i].name_sid = get_u32 (p); p += 4;
|
||
unit->externals[i].kind = *p++;
|
||
}
|
||
}
|
||
|
||
/* Debug */
|
||
if (p + 4 > end) goto fail;
|
||
unit->pc2line_len = get_u32 (p); p += 4;
|
||
|
||
if (unit->pc2line_len > 0) {
|
||
if (p + unit->pc2line_len > end) goto fail;
|
||
unit->pc2line = pjs_malloc (unit->pc2line_len);
|
||
if (!unit->pc2line) goto fail;
|
||
memcpy (unit->pc2line, p, unit->pc2line_len);
|
||
p += unit->pc2line_len;
|
||
}
|
||
|
||
if (p + 4 > end) goto fail;
|
||
unit->name_sid = get_u32 (p); p += 4;
|
||
}
|
||
}
|
||
|
||
/* Source */
|
||
if (p + 4 > end) goto fail;
|
||
mod->source_len = get_u32 (p); p += 4;
|
||
|
||
if (mod->source_len > 0) {
|
||
if (p + mod->source_len > end) goto fail;
|
||
mod->source = pjs_malloc (mod->source_len + 1);
|
||
if (!mod->source) goto fail;
|
||
memcpy (mod->source, p, mod->source_len);
|
||
mod->source[mod->source_len] = '\0';
|
||
p += mod->source_len;
|
||
}
|
||
|
||
return mod;
|
||
|
||
fail:
|
||
cell_module_free (mod);
|
||
return NULL;
|
||
}
|
||
|
||
/* Helper: get string from CellModule string table */
|
||
static const char *cell_module_get_string (CellModule *mod, uint32_t sid, uint32_t *out_len) {
|
||
if (sid >= mod->string_count) return NULL;
|
||
uint32_t offset = mod->string_offsets[sid];
|
||
uint32_t next_offset = (sid + 1 < mod->string_count)
|
||
? mod->string_offsets[sid + 1]
|
||
: mod->string_data_size;
|
||
*out_len = next_offset - offset;
|
||
return (const char *)(mod->string_data + offset);
|
||
}
|
||
|
||
/* Integrate a CellModule with an environment and execute.
|
||
This materializes the string table into the target context's stone arena,
|
||
creates runtime bytecode, patches external relocations, and returns the
|
||
main unit wrapped as a callable function.
|
||
|
||
Parameters:
|
||
- ctx: target context
|
||
- mod: context-neutral module (ownership NOT transferred)
|
||
- env: stoned record for environment (or JS_NULL)
|
||
|
||
Returns: callable function value, or JS_EXCEPTION on error.
|
||
*/
|
||
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env) {
|
||
JSValue *string_table = NULL;
|
||
JSFunctionBytecode **units = NULL;
|
||
JSValue result = JS_EXCEPTION;
|
||
uint32_t i, j;
|
||
|
||
if (mod->unit_count == 0) {
|
||
JS_ThrowTypeError (ctx, "module has no units");
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
/* Step 1: Materialize string table into context's stone arena */
|
||
if (mod->string_count > 0) {
|
||
string_table = pjs_mallocz (mod->string_count * sizeof (JSValue));
|
||
if (!string_table) goto fail;
|
||
|
||
for (i = 0; i < mod->string_count; i++) {
|
||
uint32_t len;
|
||
const char *str = cell_module_get_string (mod, i, &len);
|
||
if (!str) {
|
||
string_table[i] = JS_NULL;
|
||
} else {
|
||
/* Intern as a stoned key */
|
||
string_table[i] = js_key_new_len (ctx, str, len);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Step 2: Create JSFunctionBytecode for each unit */
|
||
units = pjs_mallocz (mod->unit_count * sizeof (JSFunctionBytecode *));
|
||
if (!units) goto fail;
|
||
|
||
for (i = 0; i < mod->unit_count; i++) {
|
||
CellUnit *cu = &mod->units[i];
|
||
|
||
/* Calculate bytecode structure size */
|
||
int function_size = sizeof (JSFunctionBytecode);
|
||
int cpool_offset = function_size;
|
||
function_size += cu->const_count * sizeof (JSValue);
|
||
int byte_code_offset = function_size;
|
||
function_size += cu->bytecode_len;
|
||
|
||
JSFunctionBytecode *b = pjs_mallocz (function_size);
|
||
if (!b) goto fail;
|
||
units[i] = b;
|
||
|
||
/* Initialize header */
|
||
b->header = objhdr_make (0, OBJ_CODE, false, false, false, false);
|
||
b->arg_count = cu->arg_count;
|
||
b->var_count = cu->var_count;
|
||
b->defined_arg_count = cu->arg_count; /* Same as arg_count for simple functions */
|
||
b->has_simple_parameter_list = 1; /* Assume simple parameter list */
|
||
b->stack_size = cu->stack_size;
|
||
b->cpool_count = cu->const_count;
|
||
b->byte_code_len = cu->bytecode_len;
|
||
|
||
/* Set up pointers */
|
||
b->cpool = (JSValue *)((uint8_t *)b + cpool_offset);
|
||
b->byte_code_buf = (uint8_t *)b + byte_code_offset;
|
||
|
||
/* Materialize constants */
|
||
for (j = 0; j < cu->const_count; j++) {
|
||
CellConst *cc = &cu->constants[j];
|
||
switch (cc->type) {
|
||
case CELL_CONST_NULL:
|
||
b->cpool[j] = JS_NULL;
|
||
break;
|
||
case CELL_CONST_INT:
|
||
b->cpool[j] = JS_NewInt32 (ctx, cc->i32);
|
||
break;
|
||
case CELL_CONST_FLOAT:
|
||
b->cpool[j] = JS_NewFloat64 (ctx, cc->f64);
|
||
break;
|
||
case CELL_CONST_STRING:
|
||
if (cc->string_sid < mod->string_count) {
|
||
b->cpool[j] = string_table[cc->string_sid];
|
||
} else {
|
||
b->cpool[j] = JS_NULL;
|
||
}
|
||
break;
|
||
case CELL_CONST_UNIT:
|
||
/* Will be patched after all units are created */
|
||
b->cpool[j] = JS_NULL;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Copy bytecode */
|
||
memcpy (b->byte_code_buf, cu->bytecode, cu->bytecode_len);
|
||
|
||
/* Set function name from string table */
|
||
if (cu->name_sid < mod->string_count) {
|
||
b->func_name = string_table[cu->name_sid];
|
||
} else {
|
||
b->func_name = JS_KEY_empty;
|
||
}
|
||
}
|
||
|
||
/* Step 3: Patch unit references in cpool */
|
||
for (i = 0; i < mod->unit_count; i++) {
|
||
CellUnit *cu = &mod->units[i];
|
||
JSFunctionBytecode *b = units[i];
|
||
|
||
for (j = 0; j < cu->const_count; j++) {
|
||
if (cu->constants[j].type == CELL_CONST_UNIT) {
|
||
uint32_t uid = cu->constants[j].unit_id;
|
||
if (uid < mod->unit_count) {
|
||
b->cpool[j] = JS_MKPTR (units[uid]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Step 4: Patch external relocations for main unit (unit 0) */
|
||
{
|
||
CellUnit *cu = &mod->units[0];
|
||
JSFunctionBytecode *b = units[0];
|
||
uint8_t *bc = b->byte_code_buf;
|
||
|
||
/* Get env record if provided */
|
||
JSRecord *env_rec = NULL;
|
||
if (!JS_IsNull (env) && JS_IsRecord (env)) {
|
||
env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env);
|
||
}
|
||
|
||
for (j = 0; j < cu->external_count; j++) {
|
||
CellExternalReloc *rel = &cu->externals[j];
|
||
uint32_t pc = rel->pc_offset;
|
||
uint32_t name_sid = rel->name_sid;
|
||
|
||
if (name_sid >= mod->string_count) continue;
|
||
JSValue name = string_table[name_sid];
|
||
|
||
if (rel->kind == EXT_GET) {
|
||
/* Try env first */
|
||
if (env_rec) {
|
||
int slot = rec_find_slot (env_rec, name);
|
||
if (slot > 0) {
|
||
bc[pc] = OP_get_env_slot;
|
||
put_u16 (bc + pc + 1, (uint16_t)slot);
|
||
bc[pc + 3] = OP_nop;
|
||
bc[pc + 4] = OP_nop;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/* Try global */
|
||
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj);
|
||
int slot = rec_find_slot (global, name);
|
||
if (slot > 0) {
|
||
bc[pc] = OP_get_global_slot;
|
||
put_u16 (bc + pc + 1, (uint16_t)slot);
|
||
bc[pc + 3] = OP_nop;
|
||
bc[pc + 4] = OP_nop;
|
||
continue;
|
||
}
|
||
|
||
/* Link error */
|
||
char buf[64];
|
||
JS_ThrowReferenceError (ctx, "'%s' is not defined",
|
||
JS_KeyGetStr (ctx, buf, sizeof (buf), name));
|
||
goto fail;
|
||
|
||
} else if (rel->kind == EXT_SET) {
|
||
/* Try env first (writable) */
|
||
if (env_rec) {
|
||
int slot = rec_find_slot (env_rec, name);
|
||
if (slot > 0) {
|
||
bc[pc] = OP_set_env_slot;
|
||
put_u16 (bc + pc + 1, (uint16_t)slot);
|
||
bc[pc + 3] = OP_nop;
|
||
bc[pc + 4] = OP_nop;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/* Try global */
|
||
JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj);
|
||
int slot = rec_find_slot (global, name);
|
||
if (slot > 0) {
|
||
bc[pc] = OP_set_global_slot;
|
||
put_u16 (bc + pc + 1, (uint16_t)slot);
|
||
bc[pc + 3] = OP_nop;
|
||
bc[pc + 4] = OP_nop;
|
||
continue;
|
||
}
|
||
|
||
/* Link error */
|
||
char buf[64];
|
||
JS_ThrowReferenceError (ctx, "cannot assign to '%s' - not found",
|
||
JS_KeyGetStr (ctx, buf, sizeof (buf), name));
|
||
goto fail;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Step 5: Create closure from main unit and set env_record */
|
||
{
|
||
JSValue linked = JS_MKPTR (units[0]);
|
||
linked = js_closure (ctx, linked, NULL);
|
||
if (JS_IsException (linked)) goto fail;
|
||
|
||
/* Set env_record on the function */
|
||
JSFunction *f = JS_VALUE_GET_FUNCTION (linked);
|
||
f->u.func.env_record = env;
|
||
|
||
result = linked;
|
||
}
|
||
|
||
/* Success - don't free units (now owned by result closure) */
|
||
pjs_free (string_table);
|
||
pjs_free (units);
|
||
return result;
|
||
|
||
fail:
|
||
/* Free allocated units on failure */
|
||
if (units) {
|
||
for (i = 0; i < mod->unit_count; i++) {
|
||
if (units[i]) pjs_free (units[i]);
|
||
}
|
||
pjs_free (units);
|
||
}
|
||
if (string_table) pjs_free (string_table);
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
/*******************************************************************/
|
||
/* JSFunctionBytecode to CellModule conversion */
|
||
|
||
/* Helper structure for building string table */
|
||
typedef struct {
|
||
JSValue *strings; /* array of JSValue strings */
|
||
uint32_t count;
|
||
uint32_t capacity;
|
||
} StringTableBuilder;
|
||
|
||
static void stb_init (StringTableBuilder *stb) {
|
||
stb->strings = NULL;
|
||
stb->count = 0;
|
||
stb->capacity = 0;
|
||
}
|
||
|
||
static void stb_free (StringTableBuilder *stb) {
|
||
if (stb->strings) pjs_free (stb->strings);
|
||
stb->strings = NULL;
|
||
stb->count = 0;
|
||
stb->capacity = 0;
|
||
}
|
||
|
||
/* Add a string to the builder, return its string_id.
|
||
If string already exists, return existing id. */
|
||
static uint32_t stb_add (StringTableBuilder *stb, JSValue str) {
|
||
/* Check if already present */
|
||
for (uint32_t i = 0; i < stb->count; i++) {
|
||
if (stb->strings[i] == str) return i;
|
||
}
|
||
|
||
/* Add new entry */
|
||
if (stb->count >= stb->capacity) {
|
||
uint32_t new_cap = stb->capacity ? stb->capacity * 2 : 16;
|
||
JSValue *new_arr = pjs_realloc (stb->strings, new_cap * sizeof (JSValue));
|
||
if (!new_arr) return UINT32_MAX;
|
||
stb->strings = new_arr;
|
||
stb->capacity = new_cap;
|
||
}
|
||
|
||
stb->strings[stb->count] = str;
|
||
return stb->count++;
|
||
}
|
||
|
||
/* Helper structure for collecting bytecodes */
|
||
typedef struct {
|
||
JSFunctionBytecode **funcs;
|
||
uint32_t count;
|
||
uint32_t capacity;
|
||
} FuncCollector;
|
||
|
||
static void fc_init (FuncCollector *fc) {
|
||
fc->funcs = NULL;
|
||
fc->count = 0;
|
||
fc->capacity = 0;
|
||
}
|
||
|
||
static void fc_free (FuncCollector *fc) {
|
||
if (fc->funcs) pjs_free (fc->funcs);
|
||
fc->funcs = NULL;
|
||
fc->count = 0;
|
||
fc->capacity = 0;
|
||
}
|
||
|
||
/* Add a function to collector, return its unit_id */
|
||
static uint32_t fc_add (FuncCollector *fc, JSFunctionBytecode *b) {
|
||
/* Check if already present */
|
||
for (uint32_t i = 0; i < fc->count; i++) {
|
||
if (fc->funcs[i] == b) return i;
|
||
}
|
||
|
||
/* Add new entry */
|
||
if (fc->count >= fc->capacity) {
|
||
uint32_t new_cap = fc->capacity ? fc->capacity * 2 : 8;
|
||
JSFunctionBytecode **new_arr = pjs_realloc (fc->funcs, new_cap * sizeof (JSFunctionBytecode *));
|
||
if (!new_arr) return UINT32_MAX;
|
||
fc->funcs = new_arr;
|
||
fc->capacity = new_cap;
|
||
}
|
||
|
||
fc->funcs[fc->count] = b;
|
||
return fc->count++;
|
||
}
|
||
|
||
/* Recursively collect all functions in bytecode tree */
|
||
static int collect_functions (FuncCollector *fc, JSFunctionBytecode *b) {
|
||
uint32_t uid = fc_add (fc, b);
|
||
if (uid == UINT32_MAX) return -1;
|
||
|
||
/* Scan cpool for nested functions */
|
||
for (int i = 0; i < b->cpool_count; i++) {
|
||
JSValue v = b->cpool[i];
|
||
if (JS_VALUE_GET_TAG (v) == JS_TAG_PTR) {
|
||
void *ptr = JS_VALUE_GET_PTR (v);
|
||
objhdr_t hdr = *(objhdr_t *)ptr;
|
||
if (objhdr_type (hdr) == OBJ_CODE) {
|
||
/* This is a nested function */
|
||
if (collect_functions (fc, (JSFunctionBytecode *)ptr) < 0)
|
||
return -1;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Collect all strings from a function's cpool and name */
|
||
static int collect_strings (StringTableBuilder *stb, JSFunctionBytecode *b) {
|
||
/* Function name */
|
||
if (JS_IsText (b->func_name)) {
|
||
if (stb_add (stb, b->func_name) == UINT32_MAX) return -1;
|
||
}
|
||
|
||
/* Filename */
|
||
if (b->has_debug && JS_IsText (b->debug.filename)) {
|
||
if (stb_add (stb, b->debug.filename) == UINT32_MAX) return -1;
|
||
}
|
||
|
||
/* Cpool strings */
|
||
for (int i = 0; i < b->cpool_count; i++) {
|
||
JSValue v = b->cpool[i];
|
||
if (JS_IsText (v)) {
|
||
if (stb_add (stb, v) == UINT32_MAX) return -1;
|
||
}
|
||
}
|
||
|
||
/* Variable names (for debugging) */
|
||
if (b->vardefs) {
|
||
for (int i = 0; i < b->arg_count + b->var_count; i++) {
|
||
if (JS_IsText (b->vardefs[i].var_name)) {
|
||
if (stb_add (stb, b->vardefs[i].var_name) == UINT32_MAX) return -1;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Closure variable names */
|
||
if (b->closure_var) {
|
||
for (int i = 0; i < b->closure_var_count; i++) {
|
||
if (JS_IsText (b->closure_var[i].var_name)) {
|
||
if (stb_add (stb, b->closure_var[i].var_name) == UINT32_MAX) return -1;
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Find string_id for a JSValue string */
|
||
static uint32_t find_string_id (StringTableBuilder *stb, JSValue str) {
|
||
for (uint32_t i = 0; i < stb->count; i++) {
|
||
if (stb->strings[i] == str) return i;
|
||
}
|
||
return UINT32_MAX;
|
||
}
|
||
|
||
/* Find unit_id for a JSFunctionBytecode */
|
||
static uint32_t find_unit_id (FuncCollector *fc, JSFunctionBytecode *b) {
|
||
for (uint32_t i = 0; i < fc->count; i++) {
|
||
if (fc->funcs[i] == b) return i;
|
||
}
|
||
return UINT32_MAX;
|
||
}
|
||
|
||
/* Convert JSFunctionBytecode tree to CellModule.
|
||
This extracts all functions, builds a shared string table,
|
||
and creates external relocations for unresolved variables.
|
||
|
||
Parameters:
|
||
- ctx: context (for string conversion)
|
||
- main_func: compiled main function bytecode
|
||
|
||
Returns: allocated CellModule (caller must free with cell_module_free), or NULL on error.
|
||
*/
|
||
CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_func) {
|
||
CellModule *mod = NULL;
|
||
StringTableBuilder stb;
|
||
FuncCollector fc;
|
||
DynBuf string_data;
|
||
uint32_t i, j;
|
||
|
||
stb_init (&stb);
|
||
fc_init (&fc);
|
||
dbuf_init (&string_data);
|
||
|
||
/* Step 1: Collect all functions */
|
||
if (collect_functions (&fc, main_func) < 0) goto fail;
|
||
|
||
/* Step 2: Collect all strings from all functions */
|
||
for (i = 0; i < fc.count; i++) {
|
||
if (collect_strings (&stb, fc.funcs[i]) < 0) goto fail;
|
||
}
|
||
|
||
/* Step 3: Allocate module */
|
||
mod = pjs_mallocz (sizeof (CellModule));
|
||
if (!mod) goto fail;
|
||
|
||
mod->magic = CELL_MODULE_MAGIC;
|
||
mod->version = CELL_MODULE_VERSION;
|
||
mod->flags = 0;
|
||
|
||
/* Step 4: Build string table data */
|
||
mod->string_count = stb.count;
|
||
if (stb.count > 0) {
|
||
mod->string_offsets = pjs_malloc (stb.count * sizeof (uint32_t));
|
||
if (!mod->string_offsets) goto fail;
|
||
|
||
for (i = 0; i < stb.count; i++) {
|
||
mod->string_offsets[i] = string_data.size;
|
||
|
||
/* Convert JSValue string to UTF-8 */
|
||
const char *cstr = JS_ToCString (ctx, stb.strings[i]);
|
||
if (cstr) {
|
||
size_t len = strlen (cstr);
|
||
dbuf_put (&string_data, (uint8_t *)cstr, len);
|
||
JS_FreeCString (ctx, cstr);
|
||
}
|
||
}
|
||
|
||
if (string_data.error) goto fail;
|
||
|
||
mod->string_data_size = string_data.size;
|
||
mod->string_data = string_data.buf;
|
||
string_data.buf = NULL; /* Transfer ownership */
|
||
}
|
||
|
||
/* Step 5: Create units */
|
||
mod->unit_count = fc.count;
|
||
mod->units = pjs_mallocz (fc.count * sizeof (CellUnit));
|
||
if (!mod->units) goto fail;
|
||
|
||
for (i = 0; i < fc.count; i++) {
|
||
JSFunctionBytecode *b = fc.funcs[i];
|
||
CellUnit *cu = &mod->units[i];
|
||
|
||
/* Function name */
|
||
if (JS_IsText (b->func_name)) {
|
||
cu->name_sid = find_string_id (&stb, b->func_name);
|
||
} else {
|
||
cu->name_sid = UINT32_MAX;
|
||
}
|
||
|
||
/* Stack requirements */
|
||
cu->arg_count = b->arg_count;
|
||
cu->var_count = b->var_count;
|
||
cu->stack_size = b->stack_size;
|
||
|
||
/* Copy bytecode */
|
||
cu->bytecode_len = b->byte_code_len;
|
||
if (b->byte_code_len > 0) {
|
||
cu->bytecode = pjs_malloc (b->byte_code_len);
|
||
if (!cu->bytecode) goto fail;
|
||
memcpy (cu->bytecode, b->byte_code_buf, b->byte_code_len);
|
||
}
|
||
|
||
/* Build constants */
|
||
cu->const_count = b->cpool_count;
|
||
if (b->cpool_count > 0) {
|
||
cu->constants = pjs_mallocz (b->cpool_count * sizeof (CellConst));
|
||
if (!cu->constants) goto fail;
|
||
|
||
for (j = 0; j < (uint32_t)b->cpool_count; j++) {
|
||
JSValue v = b->cpool[j];
|
||
CellConst *cc = &cu->constants[j];
|
||
|
||
if (JS_IsNull (v)) {
|
||
cc->type = CELL_CONST_NULL;
|
||
} else if (JS_VALUE_GET_TAG (v) == JS_TAG_INT) {
|
||
cc->type = CELL_CONST_INT;
|
||
cc->i32 = JS_VALUE_GET_INT (v);
|
||
} else if (JS_VALUE_GET_TAG (v) == JS_TAG_FLOAT64) {
|
||
cc->type = CELL_CONST_FLOAT;
|
||
cc->f64 = JS_VALUE_GET_FLOAT64 (v);
|
||
} else if (JS_IsText (v)) {
|
||
cc->type = CELL_CONST_STRING;
|
||
cc->string_sid = find_string_id (&stb, v);
|
||
} else if (JS_VALUE_GET_TAG (v) == JS_TAG_PTR) {
|
||
void *ptr = JS_VALUE_GET_PTR (v);
|
||
objhdr_t hdr = *(objhdr_t *)ptr;
|
||
if (objhdr_type (hdr) == OBJ_CODE) {
|
||
/* Nested function reference */
|
||
cc->type = CELL_CONST_UNIT;
|
||
cc->unit_id = find_unit_id (&fc, (JSFunctionBytecode *)ptr);
|
||
} else {
|
||
cc->type = CELL_CONST_NULL;
|
||
}
|
||
} else {
|
||
cc->type = CELL_CONST_NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Build upvalue descriptors from closure_var */
|
||
cu->upvalue_count = b->closure_var_count;
|
||
if (b->closure_var_count > 0 && b->closure_var) {
|
||
cu->upvalues = pjs_malloc (b->closure_var_count * sizeof (CellCapDesc));
|
||
if (!cu->upvalues) goto fail;
|
||
|
||
for (j = 0; j < (uint32_t)b->closure_var_count; j++) {
|
||
JSClosureVar *cv = &b->closure_var[j];
|
||
cu->upvalues[j].kind = cv->is_local ? CAP_FROM_PARENT_LOCAL : CAP_FROM_PARENT_UPVALUE;
|
||
cu->upvalues[j].index = cv->var_idx;
|
||
}
|
||
}
|
||
|
||
/* Scan bytecode for external relocations (OP_get_var, OP_put_var, etc.) */
|
||
{
|
||
DynBuf relocs;
|
||
dbuf_init (&relocs);
|
||
|
||
uint8_t *bc = cu->bytecode;
|
||
int pos = 0;
|
||
while (pos < (int)cu->bytecode_len) {
|
||
uint8_t op = bc[pos];
|
||
int len = short_opcode_info (op).size;
|
||
|
||
if (op == OP_get_var || op == OP_get_var_undef) {
|
||
CellExternalReloc rel;
|
||
rel.pc_offset = pos;
|
||
uint32_t cpool_idx = get_u32 (bc + pos + 1);
|
||
if (cpool_idx < (uint32_t)b->cpool_count && JS_IsText (b->cpool[cpool_idx])) {
|
||
rel.name_sid = find_string_id (&stb, b->cpool[cpool_idx]);
|
||
} else {
|
||
rel.name_sid = UINT32_MAX;
|
||
}
|
||
rel.kind = EXT_GET;
|
||
dbuf_put (&relocs, (uint8_t *)&rel, sizeof (rel));
|
||
} else if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) {
|
||
CellExternalReloc rel;
|
||
rel.pc_offset = pos;
|
||
uint32_t cpool_idx = get_u32 (bc + pos + 1);
|
||
if (cpool_idx < (uint32_t)b->cpool_count && JS_IsText (b->cpool[cpool_idx])) {
|
||
rel.name_sid = find_string_id (&stb, b->cpool[cpool_idx]);
|
||
} else {
|
||
rel.name_sid = UINT32_MAX;
|
||
}
|
||
rel.kind = EXT_SET;
|
||
dbuf_put (&relocs, (uint8_t *)&rel, sizeof (rel));
|
||
}
|
||
|
||
pos += len;
|
||
}
|
||
|
||
if (relocs.size > 0 && !relocs.error) {
|
||
cu->external_count = relocs.size / sizeof (CellExternalReloc);
|
||
cu->externals = (CellExternalReloc *)relocs.buf;
|
||
relocs.buf = NULL; /* Transfer ownership */
|
||
}
|
||
dbuf_free (&relocs);
|
||
}
|
||
|
||
/* Copy debug info */
|
||
if (b->has_debug) {
|
||
cu->pc2line_len = b->debug.pc2line_len;
|
||
if (b->debug.pc2line_len > 0 && b->debug.pc2line_buf) {
|
||
cu->pc2line = pjs_malloc (b->debug.pc2line_len);
|
||
if (!cu->pc2line) goto fail;
|
||
memcpy (cu->pc2line, b->debug.pc2line_buf, b->debug.pc2line_len);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Step 6: Copy source from main function */
|
||
if (main_func->has_debug && main_func->debug.source_len > 0 && main_func->debug.source) {
|
||
mod->source_len = main_func->debug.source_len;
|
||
mod->source = pjs_malloc (mod->source_len + 1);
|
||
if (!mod->source) goto fail;
|
||
memcpy (mod->source, main_func->debug.source, mod->source_len);
|
||
mod->source[mod->source_len] = '\0';
|
||
}
|
||
|
||
/* Success */
|
||
stb_free (&stb);
|
||
fc_free (&fc);
|
||
dbuf_free (&string_data);
|
||
return mod;
|
||
|
||
fail:
|
||
stb_free (&stb);
|
||
fc_free (&fc);
|
||
dbuf_free (&string_data);
|
||
if (mod) cell_module_free (mod);
|
||
return NULL;
|
||
}
|
||
|
||
/* Compile source code directly to CellModule (context-neutral format).
|
||
This is a convenience function that combines JS_Compile + cell_module_from_bytecode.
|
||
|
||
Parameters:
|
||
- ctx: context for compilation
|
||
- input: source code (must be null-terminated)
|
||
- input_len: length of source code
|
||
- filename: source filename for debug info
|
||
|
||
Returns: allocated CellModule (caller must free with cell_module_free), or NULL on error.
|
||
*/
|
||
CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename) {
|
||
JSValue bytecode = JS_Compile (ctx, input, input_len, filename);
|
||
if (JS_IsException (bytecode)) {
|
||
return NULL;
|
||
}
|
||
|
||
JSFunctionBytecode *b = JS_VALUE_GET_PTR (bytecode);
|
||
CellModule *mod = cell_module_from_bytecode (ctx, b);
|
||
|
||
/* Note: bytecode is not freed here - it's still valid and could be used
|
||
with JS_Integrate if desired. Caller can free it if not needed. */
|
||
|
||
return mod;
|
||
}
|
||
/* JSON */
|
||
|
||
static int json_parse_expect (JSParseState *s, int tok) {
|
||
if (s->token.val != tok) {
|
||
/* XXX: dump token correctly in all cases */
|
||
return js_parse_error (s, "expecting '%c'", tok);
|
||
}
|
||
return json_next_token (s);
|
||
}
|
||
|
||
static JSValue json_parse_value (JSParseState *s) {
|
||
JSContext *ctx = s->ctx;
|
||
JSValue val = JS_NULL;
|
||
int ret;
|
||
|
||
switch (s->token.val) {
|
||
case '{': {
|
||
JSValue prop_val;
|
||
JSValue prop_name;
|
||
JSGCRef val_ref, tok_ref;
|
||
|
||
JS_PushGCRef (ctx, &val_ref);
|
||
JS_PushGCRef (ctx, &tok_ref);
|
||
|
||
if (json_next_token (s)) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
tok_ref.val = s->token.u.str.str; /* Root the token string before GC */
|
||
|
||
val_ref.val = JS_NewObject (ctx);
|
||
if (JS_IsException (val_ref.val)) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
if (s->token.val != '}') {
|
||
for (;;) {
|
||
if (s->token.val == TOK_STRING) {
|
||
prop_name = js_key_from_string (ctx, tok_ref.val); /* Use rooted token */
|
||
if (JS_IsNull (prop_name)) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
} else if (s->ext_json && s->token.val == TOK_IDENT) {
|
||
prop_name = tok_ref.val; /* Use rooted ident */
|
||
} else {
|
||
js_parse_error (s, "expecting property name");
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
if (json_next_token (s)) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
if (json_parse_expect (s, ':')) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
prop_val = json_parse_value (s);
|
||
if (JS_IsException (prop_val)) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
ret = JS_SetPropertyInternal (ctx, val_ref.val, prop_name, prop_val);
|
||
if (ret < 0) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
|
||
if (s->token.val != ',') break;
|
||
if (json_next_token (s)) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
tok_ref.val = s->token.u.str.str; /* Root the new key token */
|
||
if (s->ext_json && s->token.val == '}') break;
|
||
}
|
||
}
|
||
if (json_parse_expect (s, '}')) {
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
val = val_ref.val;
|
||
JS_PopGCRef (ctx, &tok_ref);
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
} break;
|
||
case '[': {
|
||
JSValue el;
|
||
uint32_t idx;
|
||
JSGCRef val_ref;
|
||
|
||
JS_PushGCRef (ctx, &val_ref);
|
||
|
||
if (json_next_token (s)) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
val_ref.val = JS_NewArray (ctx);
|
||
if (JS_IsException (val_ref.val)) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
if (s->token.val != ']') {
|
||
idx = 0;
|
||
for (;;) {
|
||
el = json_parse_value (s);
|
||
if (JS_IsException (el)) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
ret = JS_SetPropertyUint32 (ctx, val_ref.val, idx, el);
|
||
if (ret < 0) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
if (s->token.val != ',') break;
|
||
if (json_next_token (s)) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
idx++;
|
||
if (s->ext_json && s->token.val == ']') break;
|
||
}
|
||
}
|
||
if (json_parse_expect (s, ']')) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
val = val_ref.val;
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
} break;
|
||
case TOK_STRING: {
|
||
JSGCRef val_ref;
|
||
JS_PushGCRef (ctx, &val_ref);
|
||
val_ref.val = s->token.u.str.str;
|
||
if (json_next_token (s)) {
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
goto fail;
|
||
}
|
||
val = val_ref.val;
|
||
JS_PopGCRef (ctx, &val_ref);
|
||
} break;
|
||
case TOK_NUMBER:
|
||
val = s->token.u.num.val;
|
||
if (json_next_token (s)) goto fail;
|
||
break;
|
||
case TOK_IDENT:
|
||
if (js_key_equal (s->token.u.ident.str, JS_KEY_false)
|
||
|| js_key_equal (s->token.u.ident.str, JS_KEY_true)) {
|
||
val = JS_NewBool (ctx, js_key_equal (s->token.u.ident.str, JS_KEY_true));
|
||
} else if (js_key_equal (s->token.u.ident.str, JS_KEY_null)) {
|
||
val = JS_NULL;
|
||
} else if (js_key_equal_str (s->token.u.ident.str, "NaN") && s->ext_json) {
|
||
/* Note: json5 identifier handling is ambiguous e.g. is
|
||
'{ NaN: 1 }' a valid JSON5 production ? */
|
||
val = JS_NewFloat64 (s->ctx, NAN);
|
||
} else if (js_key_equal_str (s->token.u.ident.str, "Infinity")
|
||
&& s->ext_json) {
|
||
val = JS_NewFloat64 (s->ctx, INFINITY);
|
||
} else {
|
||
goto def_token;
|
||
}
|
||
if (json_next_token (s)) goto fail;
|
||
break;
|
||
default:
|
||
def_token:
|
||
if (s->token.val == TOK_EOF) {
|
||
js_parse_error (s, "Unexpected end of JSON input");
|
||
} else {
|
||
js_parse_error (s, "unexpected token: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr);
|
||
}
|
||
goto fail;
|
||
}
|
||
return val;
|
||
fail:
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
JSValue JS_ParseJSON2 (JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int flags) {
|
||
JSParseState s1, *s = &s1;
|
||
JSValue val = JS_NULL;
|
||
|
||
js_parse_init (ctx, s, buf, buf_len, filename);
|
||
s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0);
|
||
if (json_next_token (s)) goto fail;
|
||
val = json_parse_value (s);
|
||
if (JS_IsException (val)) goto fail;
|
||
if (s->token.val != TOK_EOF) {
|
||
if (js_parse_error (s, "unexpected data at the end")) goto fail;
|
||
}
|
||
return val;
|
||
fail:
|
||
free_token (s, &s->token);
|
||
return JS_EXCEPTION;
|
||
}
|
||
|
||
JSValue JS_ParseJSON (JSContext *ctx, const char *buf, size_t buf_len, const char *filename) {
|
||
return JS_ParseJSON2 (ctx, buf, buf_len, filename, 0);
|
||
}
|