unleashed-firmware/lib/mjs/mjs_exec.c
Nikolay Minaylov 0154018363
[FL-3579, FL-3601, FL-3714] JavaScript runner (#3286)
* FBT: cdefines to env, libs order
* API: strtod, modf, itoa, calloc
* Apps: elk js
* Apps: mjs
* JS: scripts as assets
* mjs: composite resolver
* mjs: stack trace
* ELK JS example removed
* MJS thread, MJS lib modified to support script interruption
* JS console UI
* Module system, BadUSB bindings rework
* JS notifications, simple dialog, BadUSB demo
* Custom dialogs, dialog demo
* MJS as system library, some dirty hacks to make it compile
* Plugin-based js modules
* js_uart(BadUART) module
* js_uart: support for byte array arguments
* Script icon and various fixes
* File browser: multiple extensions filter, running js scripts from app loader
* Running js scripts from archive browser
* JS Runner as system app
* Example scripts moved to /ext/apps/Scripts
* JS bytecode listing generation
* MJS builtin printf cleanup
* JS examples cleanup
* mbedtls version fix
* Unused lib cleanup
* Making PVS happy & TODOs cleanup
* TODOs cleanup #2
* MJS: initial typed arrays support
* JS: fix mem leak in uart destructor

Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2024-02-12 15:54:32 +07:00

1253 lines
41 KiB
C

/*
* 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 <sys/mman.h>
#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
* <iterator_variable_name> (string)
* <object_that_is_iterated> (object)
* <iterator_foreign_ptr> <-- 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, "<stdin>", 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;
}