/* * Copyright (c) 2017 Cesanta Software Limited * All rights reserved */ #include "common/cs_file.h" #include "common/cs_varint.h" #include "mjs_array.h" #include "mjs_bcode.h" #include "mjs_core.h" #include "mjs_exec.h" #include "mjs_internal.h" #include "mjs_object.h" #include "mjs_parser.h" #include "mjs_primitive.h" #include "mjs_string.h" #include "mjs_tok.h" #include "mjs_util.h" #include "mjs_array_buf.h" #if MJS_GENERATE_JSC && defined(CS_MMAP) #include #endif /* * Pushes call stack frame. Offset is a global bcode offset. Retval_stack_idx * is an index in mjs->stack at which return value should be written later. */ static void call_stack_push_frame(struct mjs* mjs, size_t offset, mjs_val_t retval_stack_idx) { /* Pop `this` value, and apply it */ mjs_val_t this_obj = mjs_pop_val(&mjs->arg_stack); /* * NOTE: the layout is described by enum mjs_call_stack_frame_item */ push_mjs_val(&mjs->call_stack, mjs->vals.this_obj); mjs->vals.this_obj = this_obj; push_mjs_val(&mjs->call_stack, mjs_mk_number(mjs, (double)offset)); push_mjs_val(&mjs->call_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->scopes))); push_mjs_val( &mjs->call_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->loop_addresses))); push_mjs_val(&mjs->call_stack, retval_stack_idx); } /* * Restores call stack frame. Returns the return address. */ static size_t call_stack_restore_frame(struct mjs* mjs) { size_t retval_stack_idx, return_address, scope_index, loop_addr_index; assert(mjs_stack_size(&mjs->call_stack) >= CALL_STACK_FRAME_ITEMS_CNT); /* * NOTE: the layout is described by enum mjs_call_stack_frame_item */ retval_stack_idx = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); loop_addr_index = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); scope_index = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); return_address = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); mjs->vals.this_obj = mjs_pop_val(&mjs->call_stack); /* Remove created scopes */ while(mjs_stack_size(&mjs->scopes) > scope_index) { mjs_pop_val(&mjs->scopes); } /* Remove loop addresses */ while(mjs_stack_size(&mjs->loop_addresses) > loop_addr_index) { mjs_pop_val(&mjs->loop_addresses); } /* Shrink stack, leave return value on top */ mjs->stack.len = retval_stack_idx * sizeof(mjs_val_t); /* Jump to the return address */ return return_address; } static mjs_val_t mjs_find_scope(struct mjs* mjs, mjs_val_t key) { size_t num_scopes = mjs_stack_size(&mjs->scopes); while(num_scopes > 0) { mjs_val_t scope = *vptr(&mjs->scopes, num_scopes - 1); num_scopes--; if(mjs_get_own_property_v(mjs, scope, key) != NULL) return scope; } mjs_set_errorf(mjs, MJS_REFERENCE_ERROR, "[%s] is not defined", mjs_get_cstring(mjs, &key)); return MJS_UNDEFINED; } mjs_val_t mjs_get_this(struct mjs* mjs) { return mjs->vals.this_obj; } static double do_arith_op(double da, double db, int op, bool* resnan) { *resnan = false; if(isnan(da) || isnan(db)) { *resnan = true; return 0; } /* clang-format off */ switch (op) { case TOK_MINUS: return da - db; case TOK_PLUS: return da + db; case TOK_MUL: return da * db; case TOK_DIV: if (db != 0) { return da / db; } else { /* TODO(dfrank): add support for Infinity and return it here */ *resnan = true; return 0; } case TOK_REM: /* * TODO(dfrank): probably support remainder operation as it is in JS * (which works with non-integer divisor). */ db = (int) db; if (db != 0) { bool neg = false; if (da < 0) { neg = true; da = -da; } if (db < 0) { db = -db; } da = (double) ((int64_t) da % (int64_t) db); if (neg) { da = -da; } return da; } else { *resnan = true; return 0; } case TOK_AND: return (double) ((int64_t) da & (int64_t) db); case TOK_OR: return (double) ((int64_t) da | (int64_t) db); case TOK_XOR: return (double) ((int64_t) da ^ (int64_t) db); case TOK_LSHIFT: return (double) ((int64_t) da << (int64_t) db); case TOK_RSHIFT: return (double) ((int64_t) da >> (int64_t) db); case TOK_URSHIFT: return (double) ((uint32_t) da >> (uint32_t) db); } /* clang-format on */ *resnan = true; return 0; } static void set_no_autoconversion_error(struct mjs* mjs) { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "implicit type conversion is prohibited"); } static mjs_val_t do_op(struct mjs* mjs, mjs_val_t a, mjs_val_t b, int op) { mjs_val_t ret = MJS_UNDEFINED; bool resnan = false; if((mjs_is_foreign(a) || mjs_is_number(a)) && (mjs_is_foreign(b) || mjs_is_number(b))) { int is_result_ptr = 0; double da, db, result; if(mjs_is_foreign(a) && mjs_is_foreign(b)) { /* When two operands are pointers, only subtraction is supported */ if(op != TOK_MINUS) { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands"); } } else if(mjs_is_foreign(a) || mjs_is_foreign(b)) { /* * When one of the operands is a pointer, only + and - are supported, * and the result is a pointer. */ if(op != TOK_MINUS && op != TOK_PLUS) { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands"); } is_result_ptr = 1; } da = mjs_is_number(a) ? mjs_get_double(mjs, a) : (double)(uintptr_t)mjs_get_ptr(mjs, a); db = mjs_is_number(b) ? mjs_get_double(mjs, b) : (double)(uintptr_t)mjs_get_ptr(mjs, b); result = do_arith_op(da, db, op, &resnan); if(resnan) { ret = MJS_TAG_NAN; } else { /* * If at least one of the operands was a pointer, result should also be * a pointer */ ret = is_result_ptr ? mjs_mk_foreign(mjs, (void*)(uintptr_t)result) : mjs_mk_number(mjs, result); } } else if(mjs_is_string(a) && mjs_is_string(b) && (op == TOK_PLUS)) { ret = s_concat(mjs, a, b); } else { set_no_autoconversion_error(mjs); } return ret; } static void op_assign(struct mjs* mjs, int op) { mjs_val_t val = mjs_pop(mjs); mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj) && mjs_is_string(key)) { mjs_val_t v = mjs_get_v(mjs, obj, key); mjs_set_v(mjs, obj, key, do_op(mjs, v, val, op)); mjs_push(mjs, v); } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand"); } } static int check_equal(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { int ret = 0; if(a == MJS_TAG_NAN && b == MJS_TAG_NAN) { ret = 0; } else if(a == b) { ret = 1; } else if(mjs_is_number(a) && mjs_is_number(b)) { /* * The case of equal numbers is handled above, so here the result is always * false */ ret = 0; } else if(mjs_is_string(a) && mjs_is_string(b)) { ret = s_cmp(mjs, a, b) == 0; } else if(mjs_is_foreign(a) && b == MJS_NULL) { ret = mjs_get_ptr(mjs, a) == NULL; } else if(a == MJS_NULL && mjs_is_foreign(b)) { ret = mjs_get_ptr(mjs, b) == NULL; } else { ret = 0; } return ret; } static void exec_expr(struct mjs* mjs, int op) { switch(op) { case TOK_DOT: break; case TOK_MINUS: case TOK_PLUS: case TOK_MUL: case TOK_DIV: case TOK_REM: case TOK_XOR: case TOK_AND: case TOK_OR: case TOK_LSHIFT: case TOK_RSHIFT: case TOK_URSHIFT: { mjs_val_t b = mjs_pop(mjs); mjs_val_t a = mjs_pop(mjs); mjs_push(mjs, do_op(mjs, a, b, op)); break; } case TOK_UNARY_MINUS: { double a = mjs_get_double(mjs, mjs_pop(mjs)); mjs_push(mjs, mjs_mk_number(mjs, -a)); break; } case TOK_NOT: { mjs_val_t val = mjs_pop(mjs); mjs_push(mjs, mjs_mk_boolean(mjs, !mjs_is_truthy(mjs, val))); break; } case TOK_TILDA: { double a = mjs_get_double(mjs, mjs_pop(mjs)); mjs_push(mjs, mjs_mk_number(mjs, (double)(~(int64_t)a))); break; } case TOK_UNARY_PLUS: break; case TOK_EQ: mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use ===, not =="); break; case TOK_NE: mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use !==, not !="); break; case TOK_EQ_EQ: { mjs_val_t a = mjs_pop(mjs); mjs_val_t b = mjs_pop(mjs); mjs_push(mjs, mjs_mk_boolean(mjs, check_equal(mjs, a, b))); break; } case TOK_NE_NE: { mjs_val_t a = mjs_pop(mjs); mjs_val_t b = mjs_pop(mjs); mjs_push(mjs, mjs_mk_boolean(mjs, !check_equal(mjs, a, b))); break; } case TOK_LT: { double b = mjs_get_double(mjs, mjs_pop(mjs)); double a = mjs_get_double(mjs, mjs_pop(mjs)); mjs_push(mjs, mjs_mk_boolean(mjs, a < b)); break; } case TOK_GT: { double b = mjs_get_double(mjs, mjs_pop(mjs)); double a = mjs_get_double(mjs, mjs_pop(mjs)); mjs_push(mjs, mjs_mk_boolean(mjs, a > b)); break; } case TOK_LE: { double b = mjs_get_double(mjs, mjs_pop(mjs)); double a = mjs_get_double(mjs, mjs_pop(mjs)); mjs_push(mjs, mjs_mk_boolean(mjs, a <= b)); break; } case TOK_GE: { double b = mjs_get_double(mjs, mjs_pop(mjs)); double a = mjs_get_double(mjs, mjs_pop(mjs)); mjs_push(mjs, mjs_mk_boolean(mjs, a >= b)); break; } case TOK_ASSIGN: { mjs_val_t val = mjs_pop(mjs); mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj)) { mjs_set_v(mjs, obj, key, val); } else if(mjs_is_data_view(obj)) { mjs_err_t err = mjs_dataview_set_prop(mjs, obj, key, val); if(err != MJS_OK) { mjs_prepend_errorf(mjs, err, ""); } } else if(mjs_is_foreign(obj)) { /* * We don't have setters, so in order to support properties which behave * like setters, we have to parse key right here, instead of having real * built-in prototype objects */ int ikey = mjs_get_int(mjs, key); int ival = mjs_get_int(mjs, val); if(!mjs_is_number(key)) { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number"); val = MJS_UNDEFINED; } else if(!mjs_is_number(val) || ival < 0 || ival > 0xff) { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "only number 0 .. 255 can be assigned"); val = MJS_UNDEFINED; } else { uint8_t* ptr = (uint8_t*)mjs_get_ptr(mjs, obj); *(ptr + ikey) = (uint8_t)ival; } } else { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "unsupported object type"); } mjs_push(mjs, val); break; } case TOK_POSTFIX_PLUS: { mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj) && mjs_is_string(key)) { mjs_val_t v = mjs_get_v(mjs, obj, key); mjs_val_t v1 = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_PLUS); mjs_set_v(mjs, obj, key, v1); mjs_push(mjs, v); } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++"); } break; } case TOK_POSTFIX_MINUS: { mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj) && mjs_is_string(key)) { mjs_val_t v = mjs_get_v(mjs, obj, key); mjs_val_t v1 = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_MINUS); mjs_set_v(mjs, obj, key, v1); mjs_push(mjs, v); } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --"); } break; } case TOK_MINUS_MINUS: { mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj) && mjs_is_string(key)) { mjs_val_t v = mjs_get_v(mjs, obj, key); v = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_MINUS); mjs_set_v(mjs, obj, key, v); mjs_push(mjs, v); } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --"); } break; } case TOK_PLUS_PLUS: { mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_is_object(obj) && mjs_is_string(key)) { mjs_val_t v = mjs_get_v(mjs, obj, key); v = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_PLUS); mjs_set_v(mjs, obj, key, v); mjs_push(mjs, v); } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++"); } break; } /* * NOTE: TOK_LOGICAL_AND and TOK_LOGICAL_OR don't need to be here, because * they are just naturally handled by the short-circuit evaluation. * See PARSE_LTR_BINOP() macro in mjs_parser.c. */ /* clang-format off */ case TOK_MINUS_ASSIGN: op_assign(mjs, TOK_MINUS); break; case TOK_PLUS_ASSIGN: op_assign(mjs, TOK_PLUS); break; case TOK_MUL_ASSIGN: op_assign(mjs, TOK_MUL); break; case TOK_DIV_ASSIGN: op_assign(mjs, TOK_DIV); break; case TOK_REM_ASSIGN: op_assign(mjs, TOK_REM); break; case TOK_AND_ASSIGN: op_assign(mjs, TOK_AND); break; case TOK_OR_ASSIGN: op_assign(mjs, TOK_OR); break; case TOK_XOR_ASSIGN: op_assign(mjs, TOK_XOR); break; case TOK_LSHIFT_ASSIGN: op_assign(mjs, TOK_LSHIFT); break; case TOK_RSHIFT_ASSIGN: op_assign(mjs, TOK_RSHIFT); break; case TOK_URSHIFT_ASSIGN: op_assign(mjs, TOK_URSHIFT); break; case TOK_COMMA: break; /* clang-format on */ case TOK_KEYWORD_TYPEOF: mjs_push(mjs, mjs_mk_string(mjs, mjs_typeof(mjs_pop(mjs)), ~0, 1)); break; default: LOG(LL_ERROR, ("Unknown expr: %d", op)); break; } } static int getprop_builtin_string( struct mjs* mjs, mjs_val_t val, const char* name, size_t name_len, mjs_val_t* res) { int isnum = 0; int idx = cstr_to_ulong(name, name_len, &isnum); if(strcmp(name, "length") == 0) { size_t val_len; mjs_get_string(mjs, &val, &val_len); *res = mjs_mk_number(mjs, (double)val_len); return 1; } else if(strcmp(name, "at") == 0 || strcmp(name, "charCodeAt") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_char_code_at); return 1; } else if(strcmp(name, "indexOf") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_index_of); return 1; } else if(strcmp(name, "slice") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_slice); return 1; } else if(isnum) { /* * string subscript: return a new one-byte string if the index * is not out of bounds */ size_t val_len; const char* str = mjs_get_string(mjs, &val, &val_len); if(idx >= 0 && idx < (int)val_len) { *res = mjs_mk_string(mjs, str + idx, 1, 1); } else { *res = MJS_UNDEFINED; } return 1; } return 0; } static int getprop_builtin_array( struct mjs* mjs, mjs_val_t val, const char* name, size_t name_len, mjs_val_t* res) { if(strcmp(name, "splice") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_splice); return 1; } else if(strcmp(name, "push") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_push_internal); return 1; } else if(strcmp(name, "length") == 0) { *res = mjs_mk_number(mjs, mjs_array_length(mjs, val)); return 1; } (void)name_len; return 0; } static int getprop_builtin_foreign( struct mjs* mjs, mjs_val_t val, const char* name, size_t name_len, mjs_val_t* res) { int isnum = 0; int idx = cstr_to_ulong(name, name_len, &isnum); if(!isnum) { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number"); } else { uint8_t* ptr = (uint8_t*)mjs_get_ptr(mjs, val); *res = mjs_mk_number(mjs, *(ptr + idx)); } return 1; } static int getprop_builtin_array_buf( struct mjs* mjs, mjs_val_t val, const char* name, size_t name_len, mjs_val_t* res) { if(strcmp(name, "byteLength") == 0) { size_t len = 0; mjs_array_buf_get_ptr(mjs, val, &len); *res = mjs_mk_number(mjs, len); return 1; } else if(strcmp(name, "getPtr") == 0) { void* ptr = mjs_array_buf_get_ptr(mjs, val, NULL); *res = mjs_mk_foreign(mjs, ptr); return 1; } else if(strcmp(name, "slice") == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_buf_slice); return 1; } (void)name_len; return 0; } static int getprop_builtin_data_view( struct mjs* mjs, mjs_val_t val, const char* name, size_t name_len, mjs_val_t* res) { if(strcmp(name, "byteLength") == 0) { size_t len = 0; mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, val), &len); *res = mjs_mk_number(mjs, len); return 1; } else if(strcmp(name, "length") == 0) { *res = mjs_dataview_get_len(mjs, val); return 1; } else if(strcmp(name, "buffer") == 0) { *res = mjs_dataview_get_buf(mjs, val); return 1; } (void)name_len; return 0; } static void mjs_apply_(struct mjs* mjs) { mjs_val_t res = MJS_UNDEFINED, *args = NULL; mjs_val_t func = mjs->vals.this_obj, v = mjs_arg(mjs, 1); int i, nargs = 0; if(mjs_is_array(v)) { nargs = mjs_array_length(mjs, v); args = calloc(nargs, sizeof(args[0])); for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i); } mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args); free(args); mjs_return(mjs, res); } static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_val_t* res) { size_t n; char* s = NULL; int need_free = 0; int handled = 0; mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free); if(err == MJS_OK) { if(mjs_is_string(val)) { handled = getprop_builtin_string(mjs, val, s, n, res); } else if(s != NULL && n == 5 && strncmp(s, "apply", n) == 0) { *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_apply_); handled = 1; } else if(mjs_is_array(val)) { handled = getprop_builtin_array(mjs, val, s, n, res); } else if(mjs_is_foreign(val)) { handled = getprop_builtin_foreign(mjs, val, s, n, res); } else if(mjs_is_array_buf(val)) { handled = getprop_builtin_array_buf(mjs, val, s, n, res); } else if(mjs_is_data_view(val)) { handled = getprop_builtin_data_view(mjs, val, s, n, res); } } if(need_free) { free(s); s = NULL; } return handled; } MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res) { size_t i; uint8_t prev_opcode = OP_MAX; uint8_t opcode = OP_MAX; /* * remember lengths of all stacks, they will be restored in case of an error */ int stack_len = mjs->stack.len; int call_stack_len = mjs->call_stack.len; int arg_stack_len = mjs->arg_stack.len; int scopes_len = mjs->scopes.len; int loop_addresses_len = mjs->loop_addresses.len; size_t start_off = off; const uint8_t* code; struct mjs_bcode_part bp = *mjs_bcode_part_get_by_offset(mjs, off); mjs_set_errorf(mjs, MJS_OK, NULL); free(mjs->stack_trace); mjs->stack_trace = NULL; off -= bp.start_idx; for(i = off; i < bp.data.len; i++) { mjs->cur_bcode_offset = i; if(mjs->need_gc) { if(maybe_gc(mjs)) { mjs->need_gc = 0; } } #if MJS_AGGRESSIVE_GC maybe_gc(mjs); #endif code = (const uint8_t*)bp.data.p; #if MJS_ENABLE_DEBUG mjs_disasm_single(code, i); #endif prev_opcode = opcode; opcode = code[i]; switch(opcode) { case OP_BCODE_HEADER: { mjs_header_item_t bcode_offset; memcpy( &bcode_offset, code + i + 1 + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET, sizeof(bcode_offset)); i += bcode_offset; } break; case OP_PUSH_NULL: mjs_push(mjs, mjs_mk_null()); break; case OP_PUSH_UNDEF: mjs_push(mjs, mjs_mk_undefined()); break; case OP_PUSH_FALSE: mjs_push(mjs, mjs_mk_boolean(mjs, 0)); break; case OP_PUSH_TRUE: mjs_push(mjs, mjs_mk_boolean(mjs, 1)); break; case OP_PUSH_OBJ: mjs_push(mjs, mjs_mk_object(mjs)); break; case OP_PUSH_ARRAY: mjs_push(mjs, mjs_mk_array(mjs)); break; case OP_PUSH_FUNC: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); mjs_push(mjs, mjs_mk_function(mjs, bp.start_idx + i - n)); i += llen; break; } case OP_PUSH_THIS: mjs_push(mjs, mjs->vals.this_obj); break; case OP_JMP: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); i += n + llen; break; } case OP_JMP_FALSE: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); i += llen; if(!mjs_is_truthy(mjs, mjs_pop(mjs))) { mjs_push(mjs, MJS_UNDEFINED); i += n; } break; } /* * OP_JMP_NEUTRAL_... ops are like as OP_JMP_..., but they are completely * stack-neutral: they just check the TOS, and increment instruction * pointer if the TOS is truthy/falsy. */ case OP_JMP_NEUTRAL_TRUE: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); i += llen; if(mjs_is_truthy(mjs, vtop(&mjs->stack))) { i += n; } break; } case OP_JMP_NEUTRAL_FALSE: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); i += llen; if(!mjs_is_truthy(mjs, vtop(&mjs->stack))) { i += n; } break; } case OP_FIND_SCOPE: { mjs_val_t key = vtop(&mjs->stack); mjs_push(mjs, mjs_find_scope(mjs, key)); break; } case OP_CREATE: { mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); if(mjs_get_own_property_v(mjs, obj, key) == NULL) { mjs_set_v(mjs, obj, key, MJS_UNDEFINED); } break; } case OP_APPEND: { mjs_val_t val = mjs_pop(mjs); mjs_val_t arr = mjs_pop(mjs); mjs_err_t err = mjs_array_push(mjs, arr, val); if(err != MJS_OK) { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "append to non-array"); } break; } case OP_GET: { mjs_val_t obj = mjs_pop(mjs); mjs_val_t key = mjs_pop(mjs); mjs_val_t val = MJS_UNDEFINED; if(!getprop_builtin(mjs, obj, key, &val)) { if(mjs_is_object(obj)) { val = mjs_get_v_proto(mjs, obj, key); } else if((mjs_is_data_view(obj) && (mjs_is_number(key)))) { val = mjs_dataview_get_prop(mjs, obj, key); } else { mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "type error"); } } mjs_push(mjs, val); if(prev_opcode != OP_FIND_SCOPE) { /* * Previous opcode was not OP_FIND_SCOPE, so it's some "custom" * object which might be used as `this`, so, save it */ mjs->vals.last_getprop_obj = obj; } else { /* * Previous opcode was OP_FIND_SCOPE, so we're getting value from * the scope, and it should *not* be used as `this` */ mjs->vals.last_getprop_obj = MJS_UNDEFINED; } break; } case OP_DEL_SCOPE: if(mjs->scopes.len <= 1) { mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "scopes underflow"); } else { mjs_pop_val(&mjs->scopes); } break; case OP_NEW_SCOPE: push_mjs_val(&mjs->scopes, mjs_mk_object(mjs)); break; case OP_PUSH_SCOPE: assert(mjs_stack_size(&mjs->scopes) > 0); mjs_push(mjs, vtop(&mjs->scopes)); break; case OP_PUSH_STR: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); mjs_push(mjs, mjs_mk_string(mjs, (char*)code + i + 1 + llen, n, 1)); i += llen + n; break; } case OP_PUSH_INT: { int llen; int64_t n = cs_varint_decode_unsafe(&code[i + 1], &llen); mjs_push(mjs, mjs_mk_number(mjs, (double)n)); i += llen; break; } case OP_PUSH_DBL: { int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); mjs_push(mjs, mjs_mk_number(mjs, strtod((char*)code + i + 1 + llen, NULL))); i += llen + n; break; } case OP_FOR_IN_NEXT: { /* * Data stack layout: * ... <-- Bottom of the data stack * (string) * (object) * <-- Top of the data stack */ mjs_val_t* iterator = vptr(&mjs->stack, -1); mjs_val_t obj = *vptr(&mjs->stack, -2); if(mjs_is_object(obj)) { mjs_val_t var_name = *vptr(&mjs->stack, -3); mjs_val_t key = mjs_next(mjs, obj, iterator); if(key != MJS_UNDEFINED) { mjs_val_t scope = mjs_find_scope(mjs, var_name); mjs_set_v(mjs, scope, var_name, key); } } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "can't iterate over non-object value"); } break; } case OP_RETURN: { /* * Return address is saved as a global bcode offset, so we need to * convert it to the local offset */ size_t off_ret = call_stack_restore_frame(mjs); if(off_ret != MJS_BCODE_OFFSET_EXIT) { bp = *mjs_bcode_part_get_by_offset(mjs, off_ret); code = (const uint8_t*)bp.data.p; i = off_ret - bp.start_idx; LOG(LL_VERBOSE_DEBUG, ("RETURNING TO %d", (int)off_ret + 1)); } else { goto clean; } // mjs_dump(mjs, 0, stdout); break; } case OP_ARGS: { /* * If OP_ARGS follows OP_GET, then last_getprop_obj is set to `this` * value; otherwise, last_getprop_obj is irrelevant and we have to * reset it to `undefined` */ if(prev_opcode != OP_GET) { mjs->vals.last_getprop_obj = MJS_UNDEFINED; } /* * Push last_getprop_obj, which is going to be used as `this`, see * OP_CALL */ push_mjs_val(&mjs->arg_stack, mjs->vals.last_getprop_obj); /* * Push current size of data stack, it's needed to place arguments * properly */ push_mjs_val(&mjs->arg_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->stack))); break; } case OP_CALL: { // LOG(LL_INFO, ("BEFORE CALL")); // mjs_dump(mjs, 0, stdout); int func_pos; mjs_val_t* func; mjs_val_t retval_stack_idx = vtop(&mjs->arg_stack); func_pos = mjs_get_int(mjs, retval_stack_idx) - 1; func = vptr(&mjs->stack, func_pos); /* Drop data stack size (pushed by OP_ARGS) */ mjs_pop_val(&mjs->arg_stack); if(mjs_is_function(*func)) { size_t off_call; call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); /* * Function offset is a global bcode offset, so we need to convert it * to the local offset */ off_call = mjs_get_func_addr(*func) - 1; bp = *mjs_bcode_part_get_by_offset(mjs, off_call); code = (const uint8_t*)bp.data.p; i = off_call - bp.start_idx; *func = MJS_UNDEFINED; // Return value // LOG(LL_VERBOSE_DEBUG, ("CALLING %d", i + 1)); } else if(mjs_is_string(*func) || mjs_is_ffi_sig(*func)) { /* Call ffi-ed function */ call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); /* Perform the ffi-ed function call */ mjs_ffi_call2(mjs); call_stack_restore_frame(mjs); } else if(mjs_is_foreign(*func)) { /* Call cfunction */ call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); /* Perform the cfunction call */ ((void (*)(struct mjs*))mjs_get_ptr(mjs, *func))(mjs); call_stack_restore_frame(mjs); } else { mjs_set_errorf(mjs, MJS_TYPE_ERROR, "calling non-callable"); } break; } case OP_SET_ARG: { int llen1, llen2, n, arg_no = cs_varint_decode_unsafe(&code[i + 1], &llen1); mjs_val_t obj, key, v; n = cs_varint_decode_unsafe(&code[i + llen1 + 1], &llen2); key = mjs_mk_string(mjs, (char*)code + i + 1 + llen1 + llen2, n, 1); obj = vtop(&mjs->scopes); v = mjs_arg(mjs, arg_no); mjs_set_v(mjs, obj, key, v); i += llen1 + llen2 + n; break; } case OP_SETRETVAL: { if(mjs_stack_size(&mjs->call_stack) < CALL_STACK_FRAME_ITEMS_CNT) { mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "cannot return"); } else { size_t retval_pos = mjs_get_int( mjs, *vptr(&mjs->call_stack, -1 - CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX)); *vptr(&mjs->stack, retval_pos - 1) = mjs_pop(mjs); } // LOG(LL_INFO, ("AFTER SETRETVAL")); // mjs_dump(mjs, 0, stdout); break; } case OP_EXPR: { int op = code[i + 1]; exec_expr(mjs, op); i++; break; } case OP_DROP: { mjs_pop(mjs); break; } case OP_DUP: { mjs_push(mjs, vtop(&mjs->stack)); break; } case OP_SWAP: { mjs_val_t a = mjs_pop(mjs); mjs_val_t b = mjs_pop(mjs); mjs_push(mjs, a); mjs_push(mjs, b); break; } case OP_LOOP: { int l1, l2, off = cs_varint_decode_unsafe(&code[i + 1], &l1); /* push scope index */ push_mjs_val( &mjs->loop_addresses, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->scopes))); /* push break offset */ push_mjs_val( &mjs->loop_addresses, mjs_mk_number(mjs, (double)(i + 1 /* OP_LOOP */ + l1 + off))); off = cs_varint_decode_unsafe(&code[i + 1 + l1], &l2); /* push continue offset */ push_mjs_val( &mjs->loop_addresses, mjs_mk_number(mjs, (double)(i + 1 /* OP_LOOP*/ + l1 + l2 + off))); i += l1 + l2; break; } case OP_CONTINUE: { if(mjs_stack_size(&mjs->loop_addresses) >= 3) { size_t scopes_len = mjs_get_int(mjs, *vptr(&mjs->loop_addresses, -3)); assert(mjs_stack_size(&mjs->scopes) >= scopes_len); mjs->scopes.len = scopes_len * sizeof(mjs_val_t); /* jump to "continue" address */ i = mjs_get_int(mjs, vtop(&mjs->loop_addresses)) - 1; } else { mjs_set_errorf(mjs, MJS_SYNTAX_ERROR, "misplaced 'continue'"); } } break; case OP_BREAK: { if(mjs_stack_size(&mjs->loop_addresses) >= 3) { size_t scopes_len; /* drop "continue" address */ mjs_pop_val(&mjs->loop_addresses); /* pop "break" address and jump to it */ i = mjs_get_int(mjs, mjs_pop_val(&mjs->loop_addresses)) - 1; /* restore scope index */ scopes_len = mjs_get_int(mjs, mjs_pop_val(&mjs->loop_addresses)); assert(mjs_stack_size(&mjs->scopes) >= scopes_len); mjs->scopes.len = scopes_len * sizeof(mjs_val_t); LOG(LL_VERBOSE_DEBUG, ("BREAKING TO %d", (int)i + 1)); } else { mjs_set_errorf(mjs, MJS_SYNTAX_ERROR, "misplaced 'break'"); } } break; case OP_NOP: break; case OP_EXIT: i = bp.data.len; break; default: #if MJS_ENABLE_DEBUG mjs_dump(mjs, 1); #endif mjs_set_errorf( mjs, MJS_INTERNAL_ERROR, "Unknown opcode: %d, off %d+%d", (int)opcode, (int)bp.start_idx, (int)i); i = bp.data.len; break; } if(mjs->exec_flags_poller) { mjs->exec_flags_poller(mjs); } if(mjs->error != MJS_OK) { if(mjs->error == MJS_NEED_EXIT) { mjs->error = MJS_OK; goto clean; } mjs_gen_stack_trace(mjs, bp.start_idx + i - 1 /* undo the i++ */); /* restore stack lenghts */ mjs->stack.len = stack_len; mjs->call_stack.len = call_stack_len; mjs->arg_stack.len = arg_stack_len; mjs->scopes.len = scopes_len; mjs->loop_addresses.len = loop_addresses_len; /* script will evaluate to `undefined` */ mjs_push(mjs, MJS_UNDEFINED); break; } } clean: /* Remember result of the evaluation of this bcode part */ mjs_bcode_part_get_by_offset(mjs, start_off)->exec_res = mjs->error; *res = mjs_pop(mjs); return mjs->error; } MJS_PRIVATE mjs_err_t mjs_exec_internal( struct mjs* mjs, const char* path, const char* src, int generate_jsc, mjs_val_t* res) { size_t off = mjs->bcode_len; mjs_val_t r = MJS_UNDEFINED; mjs->error = mjs_parse(path, src, mjs); #if MJS_ENABLE_DEBUG if(cs_log_level >= LL_VERBOSE_DEBUG) mjs_dump(mjs, 1); #endif if(generate_jsc == -1) generate_jsc = mjs->generate_jsc; if(mjs->error == MJS_OK) { #if MJS_GENERATE_JSC && defined(CS_MMAP) if(generate_jsc && path != NULL) { const char* jsext = ".js"; int basename_len = (int)strlen(path) - strlen(jsext); if(basename_len > 0 && strcmp(path + basename_len, jsext) == 0) { /* source file has a .js extension: create a .jsc counterpart */ int rewrite = 1; int read_mmapped = 1; /* construct .jsc filename */ const char* jscext = ".jsc"; char filename_jsc[basename_len + strlen(jscext) + 1 /* nul-term */]; memcpy(filename_jsc, path, basename_len); strcpy(filename_jsc + basename_len, jscext); /* get last bcode part */ struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, mjs_bcode_parts_cnt(mjs) - 1); /* * before writing .jsc file, check if it already exists and has the * same contents * * TODO(dfrank): probably store crc32 before the bcode data, and only * compare it. */ { size_t size; char* data = cs_mmap_file(filename_jsc, &size); if(data != NULL) { if(size == bp->data.len) { if(memcmp(data, bp->data.p, size) == 0) { /* .jsc file is up to date, so don't rewrite it */ rewrite = 0; } } munmap(data, size); } } /* try to open .jsc file for writing */ if(rewrite) { FILE* fp = fopen(filename_jsc, "wb"); if(fp != NULL) { /* write last bcode part to .jsc */ fwrite(bp->data.p, bp->data.len, 1, fp); fclose(fp); } else { LOG(LL_WARN, ("Failed to open %s for writing", filename_jsc)); read_mmapped = 0; } } if(read_mmapped) { /* free RAM buffer with last bcode part */ free((void*)bp->data.p); /* mmap .jsc file and set last bcode part buffer to it */ bp->data.p = cs_mmap_file(filename_jsc, &bp->data.len); bp->in_rom = 1; } } } #else (void)generate_jsc; #endif mjs_execute(mjs, off, &r); } if(res != NULL) *res = r; return mjs->error; } mjs_err_t mjs_exec(struct mjs* mjs, const char* src, mjs_val_t* res) { return mjs_exec_internal(mjs, "", src, 0 /* generate_jsc */, res); } mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res) { mjs_err_t error = MJS_FILE_READ_ERROR; mjs_val_t r = MJS_UNDEFINED; size_t size; char* source_code = cs_read_file(path, &size); if(source_code == NULL) { error = MJS_FILE_READ_ERROR; mjs_prepend_errorf(mjs, error, "failed to read file \"%s\"", path); goto clean; } r = MJS_UNDEFINED; error = mjs_exec_internal(mjs, path, source_code, -1, &r); free(source_code); clean: if(res != NULL) *res = r; return error; } mjs_err_t mjs_call(struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, ...) { va_list ap; int i; mjs_err_t ret; mjs_val_t* args = calloc(nargs, sizeof(mjs_val_t)); va_start(ap, nargs); for(i = 0; i < nargs; i++) { args[i] = va_arg(ap, mjs_val_t); } va_end(ap); ret = mjs_apply(mjs, res, func, this_val, nargs, args); free(args); return ret; } mjs_err_t mjs_apply( struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, mjs_val_t* args) { mjs_val_t r, prev_this_val, retval_stack_idx, *resp; int i; if(!mjs_is_function(func) && !mjs_is_foreign(func) && !mjs_is_ffi_sig(func)) { return mjs_set_errorf(mjs, MJS_TYPE_ERROR, "calling non-callable"); } LOG(LL_VERBOSE_DEBUG, ("applying func %d", (int)mjs_get_func_addr(func))); prev_this_val = mjs->vals.this_obj; /* Push callable which will be later replaced with the return value */ mjs_push(mjs, func); resp = vptr(&mjs->stack, -1); /* Remember index by which return value should be written */ retval_stack_idx = mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->stack)); // Push all arguments for(i = 0; i < nargs; i++) { mjs_push(mjs, args[i]); } /* Push this value to arg_stack, call_stack_push_frame() expects that */ push_mjs_val(&mjs->arg_stack, this_val); /* Push call stack frame, just like OP_CALL does that */ call_stack_push_frame(mjs, MJS_BCODE_OFFSET_EXIT, retval_stack_idx); if(mjs_is_foreign(func)) { ((void (*)(struct mjs*))mjs_get_ptr(mjs, func))(mjs); if(res != NULL) *res = *resp; } else if(mjs_is_ffi_sig(func)) { mjs_ffi_call2(mjs); if(res != NULL) *res = *resp; } else { size_t addr = mjs_get_func_addr(func); mjs_execute(mjs, addr, &r); if(res != NULL) *res = r; } /* * If there was an error, we need to restore frame and do the cleanup * which is otherwise done by OP_RETURN */ if(mjs->error != MJS_OK) { call_stack_restore_frame(mjs); // Pop cell at which the returned value should've been written mjs_pop(mjs); } mjs->vals.this_obj = prev_this_val; return mjs->error; }