/* * 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 : ""); 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 , nor */ 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, "") || js_key_equal_str (cv->var_name, "")) 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); }