unleashed-firmware/lib/mjs/mjs_ffi.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

1224 lines
36 KiB
C

/*
* Copyright (c) 2017 Cesanta Software Limited
* All rights reserved
*/
#include "common/mg_str.h"
#include "ffi/ffi.h"
#include "mjs_core.h"
#include "mjs_exec.h"
#include "mjs_internal.h"
#include "mjs_primitive.h"
#include "mjs_string.h"
#include "mjs_util.h"
/*
* on linux this is enabled only if __USE_GNU is defined, but we cannot set it
* because dlfcn could have been included already.
*/
#ifndef RTLD_DEFAULT
#define RTLD_DEFAULT NULL
#endif
static ffi_fn_t* get_cb_impl_by_signature(const mjs_ffi_sig_t* sig);
/*
* Data of the two related arguments: callback function pointer and the
* userdata for it
*/
struct cbdata {
/* JS callback function */
mjs_val_t func;
/* JS userdata */
mjs_val_t userdata;
/* index of the function pointer param */
int8_t func_idx;
/* index of the userdata param */
int8_t userdata_idx;
};
void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle) {
mjs->dlsym = dlsym;
mjs->dlsym_handle = handle;
}
void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol) {
if(mjs->dlsym) {
return mjs->dlsym(mjs->dlsym_handle, symbol);
}
return NULL;
}
static mjs_ffi_ctype_t parse_cval_type(struct mjs* mjs, const char* s, const char* e) {
struct mg_str ms = MG_NULL_STR;
/* Trim leading and trailing whitespace */
while(s < e && isspace((int)*s)) s++;
while(e > s && isspace((int)e[-1])) e--;
ms.p = s;
ms.len = e - s;
if(mg_vcmp(&ms, "void") == 0) {
return MJS_FFI_CTYPE_NONE;
} else if(mg_vcmp(&ms, "userdata") == 0) {
return MJS_FFI_CTYPE_USERDATA;
} else if(mg_vcmp(&ms, "int") == 0) {
return MJS_FFI_CTYPE_INT;
} else if(mg_vcmp(&ms, "bool") == 0) {
return MJS_FFI_CTYPE_BOOL;
} else if(mg_vcmp(&ms, "double") == 0) {
return MJS_FFI_CTYPE_DOUBLE;
} else if(mg_vcmp(&ms, "float") == 0) {
return MJS_FFI_CTYPE_FLOAT;
} else if(mg_vcmp(&ms, "char*") == 0 || mg_vcmp(&ms, "char *") == 0) {
return MJS_FFI_CTYPE_CHAR_PTR;
} else if(mg_vcmp(&ms, "void*") == 0 || mg_vcmp(&ms, "void *") == 0) {
return MJS_FFI_CTYPE_VOID_PTR;
} else if(mg_vcmp(&ms, "struct mg_str") == 0) {
return MJS_FFI_CTYPE_STRUCT_MG_STR;
} else if(mg_vcmp(&ms, "struct mg_str *") == 0 || mg_vcmp(&ms, "struct mg_str*") == 0) {
return MJS_FFI_CTYPE_STRUCT_MG_STR_PTR;
} else {
mjs_prepend_errorf(
mjs, MJS_TYPE_ERROR, "failed to parse val type \"%.*s\"", (int)ms.len, ms.p);
return MJS_FFI_CTYPE_INVALID;
}
}
static const char* find_paren(const char* s, const char* e) {
for(; s < e; s++) {
if(*s == '(') return s;
}
return NULL;
}
static const char* find_closing_paren(const char* s, const char* e) {
int nesting = 1;
while(s < e) {
if(*s == '(') {
nesting++;
} else if(*s == ')') {
if(--nesting == 0) break;
}
s++;
}
return (s < e ? s : NULL);
}
MJS_PRIVATE mjs_err_t mjs_parse_ffi_signature(
struct mjs* mjs,
const char* s,
int sig_len,
mjs_ffi_sig_t* sig,
enum ffi_sig_type sig_type) {
mjs_err_t ret = MJS_OK;
int vtidx = 0;
const char *cur, *e, *tmp_e, *tmp;
struct mg_str rt = MG_NULL_STR, fn = MG_NULL_STR, args = MG_NULL_STR;
mjs_ffi_ctype_t val_type = MJS_FFI_CTYPE_INVALID;
if(sig_len == ~0) {
sig_len = strlen(s);
}
e = s + sig_len;
mjs_ffi_sig_init(sig);
/* Skip leading spaces */
for(cur = s; cur < e && isspace((int)*cur); cur++)
;
/* FInd the first set of parens */
tmp_e = find_paren(cur, e);
if(tmp_e == NULL || tmp_e - s < 2) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "1");
goto clean;
}
tmp = find_closing_paren(tmp_e + 1, e);
if(tmp == NULL) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "2");
goto clean;
}
/* Now see if we have a second set of parens */
args.p = find_paren(tmp + 1, e);
if(args.p == NULL) {
/* We don't - it's a regular function signature */
fn.p = tmp_e - 1;
while(fn.p > cur && isspace((int)*fn.p)) fn.p--;
while(fn.p > cur && (isalnum((int)*fn.p) || *fn.p == '_')) {
fn.p--;
fn.len++;
}
fn.p++;
rt.p = cur;
rt.len = fn.p - rt.p;
/* Stuff inside parens is args */
args.p = tmp_e + 1;
args.len = tmp - args.p;
} else {
/* We do - it's a function pointer, like void (*foo)(...).
* Stuff inside the first pair of parens is the function name */
fn.p = tmp + 1;
fn.len = args.p - tmp;
rt.p = cur;
rt.len = tmp_e - rt.p;
args.p++;
tmp = find_closing_paren(args.p, e);
if(tmp == NULL) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "3");
goto clean;
}
args.len = tmp - args.p;
/*
* We ignore the name and leave sig->fn NULL here, but it will later be
* set to the appropriate callback implementation.
*/
sig->is_callback = 1;
}
val_type = parse_cval_type(mjs, rt.p, rt.p + rt.len);
if(val_type == MJS_FFI_CTYPE_INVALID) {
ret = mjs->error;
goto clean;
}
mjs_ffi_sig_set_val_type(sig, vtidx++, val_type);
/* Parse function name {{{ */
if(!sig->is_callback) {
char buf[100];
if(mjs->dlsym == NULL) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "resolver is not set, call mjs_set_ffi_resolver");
goto clean;
}
snprintf(buf, sizeof(buf), "%.*s", (int)fn.len, fn.p);
sig->fn = (ffi_fn_t*)mjs->dlsym(mjs->dlsym_handle, buf);
if(sig->fn == NULL) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "dlsym('%s') failed", buf);
goto clean;
}
} else {
tmp_e = strchr(tmp_e, ')');
if(tmp_e == NULL) {
ret = MJS_TYPE_ERROR;
goto clean;
}
}
/* Advance cur to the beginning of the arg list */
cur = tmp_e = args.p;
/* Parse all args {{{ */
while(tmp_e - args.p < (ptrdiff_t)args.len) {
int level = 0; /* nested parens level */
int is_fp = 0; /* set to 1 is current arg is a callback function ptr */
tmp_e = cur;
/* Advance tmp_e until the next arg separator */
while(*tmp_e && (level > 0 || (*tmp_e != ',' && *tmp_e != ')'))) {
switch(*tmp_e) {
case '(':
level++;
/*
* only function pointer params can have parens, so, set the flag
* that it's going to be a function pointer
*/
is_fp = 1;
break;
case ')':
level--;
break;
}
tmp_e++;
}
if(tmp_e == cur) break;
/* Parse current arg */
if(is_fp) {
/* Current argument is a callback function pointer */
if(sig->cb_sig != NULL) {
/*
* We already have parsed some callback argument. Currently we don't
* support more than one callback argument, so, return error
* TODO(dfrank): probably improve
*/
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "only one callback is allowed");
goto clean;
}
sig->cb_sig = calloc(sizeof(*sig->cb_sig), 1);
ret = mjs_parse_ffi_signature(mjs, cur, tmp_e - cur, sig->cb_sig, FFI_SIG_CALLBACK);
if(ret != MJS_OK) {
mjs_ffi_sig_free(sig->cb_sig);
free(sig->cb_sig);
sig->cb_sig = NULL;
goto clean;
}
val_type = MJS_FFI_CTYPE_CALLBACK;
} else {
/* Some non-function argument */
val_type = parse_cval_type(mjs, cur, tmp_e);
if(val_type == MJS_FFI_CTYPE_INVALID) {
/* parse_cval_type() has already set error message */
ret = MJS_TYPE_ERROR;
goto clean;
}
}
if(!mjs_ffi_sig_set_val_type(sig, vtidx++, val_type)) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "too many callback args");
goto clean;
}
if(*tmp_e == ',') {
/* Advance cur to the next argument */
cur = tmp_e + 1;
while(*cur == ' ') cur++;
} else {
/* No more arguments */
break;
}
}
/* }}} */
/* Analyze the results and see if they are obviously wrong */
mjs_ffi_sig_validate(mjs, sig, sig_type);
if(!sig->is_valid) {
ret = MJS_TYPE_ERROR;
goto clean;
}
/* If the signature represents a callback, find the suitable implementation */
if(sig->is_callback) {
sig->fn = get_cb_impl_by_signature(sig);
if(sig->fn == NULL) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs,
ret,
"the callback signature is valid, but there's "
"no existing callback implementation for it");
goto clean;
}
}
clean:
if(ret != MJS_OK) {
mjs_prepend_errorf(mjs, ret, "bad ffi signature: \"%.*s\"", sig_len, s);
sig->is_valid = 0;
}
return ret;
}
/* C callbacks implementation {{{ */
/* An argument or a return value for C callback impl */
union ffi_cb_data_val {
void* p;
uintptr_t w;
double d;
float f;
};
struct ffi_cb_data {
union ffi_cb_data_val args[MJS_CB_ARGS_MAX_CNT];
};
static union ffi_cb_data_val ffi_cb_impl_generic(void* param, struct ffi_cb_data* data) {
struct mjs_ffi_cb_args* cbargs = (struct mjs_ffi_cb_args*)param;
mjs_val_t *args, res = MJS_UNDEFINED;
union ffi_cb_data_val ret;
int i;
struct mjs* mjs = cbargs->mjs;
mjs_ffi_ctype_t return_ctype = MJS_FFI_CTYPE_NONE;
mjs_err_t err;
memset(&ret, 0, sizeof(ret));
mjs_own(mjs, &res);
/* There must be at least one argument: a userdata */
assert(cbargs->sig.args_cnt > 0);
/* Create JS arguments */
args = calloc(1, sizeof(mjs_val_t) * cbargs->sig.args_cnt);
for(i = 0; i < cbargs->sig.args_cnt; i++) {
mjs_ffi_ctype_t val_type =
cbargs->sig.val_types[i + 1 /* first val_type is return value type */];
switch(val_type) {
case MJS_FFI_CTYPE_USERDATA:
args[i] = cbargs->userdata;
break;
case MJS_FFI_CTYPE_INT:
args[i] = mjs_mk_number(mjs, (double)data->args[i].w);
break;
case MJS_FFI_CTYPE_BOOL:
args[i] = mjs_mk_boolean(mjs, !!data->args[i].w);
break;
case MJS_FFI_CTYPE_CHAR_PTR: {
const char* s = (char*)data->args[i].w;
if(s == NULL) s = "";
args[i] = mjs_mk_string(mjs, s, ~0, 1);
break;
}
case MJS_FFI_CTYPE_VOID_PTR:
args[i] = mjs_mk_foreign(mjs, (void*)data->args[i].w);
break;
case MJS_FFI_CTYPE_DOUBLE:
args[i] = mjs_mk_number(mjs, data->args[i].d);
break;
case MJS_FFI_CTYPE_FLOAT:
args[i] = mjs_mk_number(mjs, data->args[i].f);
break;
case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: {
struct mg_str* s = (struct mg_str*)(void*)data->args[i].w;
args[i] = mjs_mk_string(mjs, s->p, s->len, 1);
break;
}
default:
/* should never be here */
LOG(LL_ERROR, ("unexpected val type for arg #%d: %d\n", i, val_type));
abort();
}
}
/*
* save return ctype outside of `cbargs` before calling the callback, because
* callback might call `ffi_cb_free()`, which will effectively invalidate
* `cbargs`
*/
return_ctype = cbargs->sig.val_types[0];
/* Call JS function */
LOG(LL_VERBOSE_DEBUG,
("calling JS callback void-void %d from C", mjs_get_int(mjs, cbargs->func)));
err = mjs_apply(mjs, &res, cbargs->func, MJS_UNDEFINED, cbargs->sig.args_cnt, args);
/*
* cbargs might be invalidated by the callback (if it called ffi_cb_free), so
* null it out
*/
cbargs = NULL;
if(err != MJS_OK) {
/*
* There's not much we can do about the error here; let's at least print it
*/
mjs_print_error(mjs, stderr, "MJS callback error", 1 /* print_stack_trace */);
goto clean;
}
/* Get return value, if needed */
switch(return_ctype) {
case MJS_FFI_CTYPE_NONE:
/* do nothing */
break;
case MJS_FFI_CTYPE_INT:
ret.w = mjs_get_int(mjs, res);
break;
case MJS_FFI_CTYPE_BOOL:
ret.w = mjs_get_bool(mjs, res);
break;
case MJS_FFI_CTYPE_VOID_PTR:
ret.p = mjs_get_ptr(mjs, res);
break;
case MJS_FFI_CTYPE_DOUBLE:
ret.d = mjs_get_double(mjs, res);
break;
case MJS_FFI_CTYPE_FLOAT:
ret.f = (float)mjs_get_double(mjs, res);
break;
default:
/* should never be here */
LOG(LL_ERROR, ("unexpected return val type %d\n", return_ctype));
abort();
}
clean:
free(args);
mjs_disown(mjs, &res);
return ret;
}
static void ffi_init_cb_data_wwww(
struct ffi_cb_data* data,
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
memset(data, 0, sizeof(*data));
data->args[0].w = w0;
data->args[1].w = w1;
data->args[2].w = w2;
data->args[3].w = w3;
data->args[4].w = w4;
data->args[5].w = w5;
}
static uintptr_t ffi_cb_impl_wpwwwww(
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
struct ffi_cb_data data;
ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5);
return ffi_cb_impl_generic((void*)w0, &data).w;
}
static uintptr_t ffi_cb_impl_wwpwwww(
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
struct ffi_cb_data data;
ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5);
return ffi_cb_impl_generic((void*)w1, &data).w;
}
static uintptr_t ffi_cb_impl_wwwpwww(
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
struct ffi_cb_data data;
ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5);
return ffi_cb_impl_generic((void*)w2, &data).w;
}
static uintptr_t ffi_cb_impl_wwwwpww(
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
struct ffi_cb_data data;
ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5);
return ffi_cb_impl_generic((void*)w3, &data).w;
}
static uintptr_t ffi_cb_impl_wwwwwpw(
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
struct ffi_cb_data data;
ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5);
return ffi_cb_impl_generic((void*)w4, &data).w;
}
static uintptr_t ffi_cb_impl_wwwwwwp(
uintptr_t w0,
uintptr_t w1,
uintptr_t w2,
uintptr_t w3,
uintptr_t w4,
uintptr_t w5) {
struct ffi_cb_data data;
ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5);
return ffi_cb_impl_generic((void*)w5, &data).w;
}
static uintptr_t ffi_cb_impl_wpd(uintptr_t w0, double d1) {
struct ffi_cb_data data;
memset(&data, 0, sizeof(data));
data.args[0].w = w0;
data.args[1].d = d1;
return ffi_cb_impl_generic((void*)w0, &data).w;
}
static uintptr_t ffi_cb_impl_wdp(double d0, uintptr_t w1) {
struct ffi_cb_data data;
memset(&data, 0, sizeof(data));
data.args[0].d = d0;
data.args[1].w = w1;
return ffi_cb_impl_generic((void*)w1, &data).w;
}
/* }}} */
static struct mjs_ffi_cb_args**
ffi_get_matching(struct mjs_ffi_cb_args** plist, mjs_val_t func, mjs_val_t userdata) {
for(; *plist != NULL; plist = &((*plist)->next)) {
if((*plist)->func == func && (*plist)->userdata == userdata) {
break;
}
}
return plist;
}
static ffi_fn_t* get_cb_impl_by_signature(const mjs_ffi_sig_t* sig) {
if(sig->is_valid) {
int i;
int double_cnt = 0;
int float_cnt = 0;
int userdata_idx = 0 /* not a valid value: index 0 means return value */;
for(i = 1 /*0th item is a return value*/; i < MJS_CB_SIGNATURE_MAX_SIZE; i++) {
mjs_ffi_ctype_t type = sig->val_types[i];
switch(type) {
case MJS_FFI_CTYPE_DOUBLE:
double_cnt++;
break;
case MJS_FFI_CTYPE_FLOAT:
float_cnt++;
break;
case MJS_FFI_CTYPE_USERDATA:
assert(userdata_idx == 0); /* Otherwise is_valid should be 0 */
userdata_idx = i;
break;
default:
break;
}
}
if(float_cnt > 0) {
/* TODO(dfrank): add support for floats in callbacks */
return NULL;
}
assert(userdata_idx > 0); /* Otherwise is_valid should be 0 */
if(sig->args_cnt <= MJS_CB_ARGS_MAX_CNT) {
if(mjs_ffi_is_regular_word_or_void(sig->val_types[0])) {
/* Return type is a word or void */
switch(double_cnt) {
case 0:
/* No double arguments */
switch(userdata_idx) {
case 1:
return (ffi_fn_t*)ffi_cb_impl_wpwwwww;
case 2:
return (ffi_fn_t*)ffi_cb_impl_wwpwwww;
case 3:
return (ffi_fn_t*)ffi_cb_impl_wwwpwww;
case 4:
return (ffi_fn_t*)ffi_cb_impl_wwwwpww;
case 5:
return (ffi_fn_t*)ffi_cb_impl_wwwwwpw;
case 6:
return (ffi_fn_t*)ffi_cb_impl_wwwwwwp;
default:
/* should never be here */
abort();
}
break;
case 1:
/* 1 double argument */
switch(userdata_idx) {
case 1:
return (ffi_fn_t*)ffi_cb_impl_wpd;
case 2:
return (ffi_fn_t*)ffi_cb_impl_wdp;
}
break;
}
}
} else {
/* Too many arguments for the built-in callback impls */
/* TODO(dfrank): add support for custom app-dependent resolver */
}
}
return NULL;
}
MJS_PRIVATE mjs_val_t mjs_ffi_sig_to_value(struct mjs_ffi_sig* psig) {
if(psig == NULL) {
return MJS_NULL;
} else {
return mjs_legit_pointer_to_value(psig) | MJS_TAG_FUNCTION_FFI;
}
}
MJS_PRIVATE int mjs_is_ffi_sig(mjs_val_t v) {
return (v & MJS_TAG_MASK) == MJS_TAG_FUNCTION_FFI;
}
MJS_PRIVATE struct mjs_ffi_sig* mjs_get_ffi_sig_struct(mjs_val_t v) {
struct mjs_ffi_sig* ret = NULL;
assert(mjs_is_ffi_sig(v));
ret = (struct mjs_ffi_sig*)get_ptr(v);
return ret;
}
MJS_PRIVATE mjs_val_t mjs_mk_ffi_sig(struct mjs* mjs) {
struct mjs_ffi_sig* psig = new_ffi_sig(mjs);
mjs_ffi_sig_init(psig);
return mjs_ffi_sig_to_value(psig);
}
MJS_PRIVATE void mjs_ffi_sig_destructor(struct mjs* mjs, void* psig) {
mjs_ffi_sig_free((mjs_ffi_sig_t*)psig);
(void)mjs;
}
MJS_PRIVATE mjs_err_t mjs_ffi_call(struct mjs* mjs) {
mjs_err_t e = MJS_OK;
const char* sig_str = NULL;
mjs_val_t sig_str_v = mjs_arg(mjs, 0);
mjs_val_t ret_v = MJS_UNDEFINED;
struct mjs_ffi_sig* psig = mjs_get_ffi_sig_struct(mjs_mk_ffi_sig(mjs));
size_t sig_str_len;
sig_str = mjs_get_string(mjs, &sig_str_v, &sig_str_len);
e = mjs_parse_ffi_signature(mjs, sig_str, sig_str_len, psig, FFI_SIG_FUNC);
if(e != MJS_OK) goto clean;
ret_v = mjs_ffi_sig_to_value(psig);
clean:
mjs_return(mjs, ret_v);
return e;
}
MJS_PRIVATE mjs_err_t mjs_ffi_call2(struct mjs* mjs) {
mjs_err_t ret = MJS_OK;
mjs_ffi_sig_t* psig = NULL;
mjs_ffi_ctype_t rtype;
mjs_val_t sig_v = *vptr(&mjs->stack, mjs_getretvalpos(mjs));
int i, nargs;
struct ffi_arg res;
struct ffi_arg args[FFI_MAX_ARGS_CNT];
struct cbdata cbdata;
/* TODO(dfrank): support multiple callbacks */
mjs_val_t resv = mjs_mk_undefined();
/*
* String arguments, needed to support short strings which are packed into
* mjs_val_t itself
*/
mjs_val_t argvs[FFI_MAX_ARGS_CNT];
struct mg_str argvmgstr[FFI_MAX_ARGS_CNT];
if(mjs_is_ffi_sig(sig_v)) {
psig = mjs_get_ffi_sig_struct(sig_v);
} else {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "non-ffi-callable value");
goto clean;
}
memset(&cbdata, 0, sizeof(cbdata));
cbdata.func_idx = -1;
cbdata.userdata_idx = -1;
rtype = psig->val_types[0];
switch(rtype) {
case MJS_FFI_CTYPE_DOUBLE:
res.ctype = FFI_CTYPE_DOUBLE;
break;
case MJS_FFI_CTYPE_FLOAT:
res.ctype = FFI_CTYPE_FLOAT;
break;
case MJS_FFI_CTYPE_BOOL:
res.ctype = FFI_CTYPE_BOOL;
break;
case MJS_FFI_CTYPE_USERDATA:
case MJS_FFI_CTYPE_INT:
case MJS_FFI_CTYPE_CHAR_PTR:
case MJS_FFI_CTYPE_VOID_PTR:
case MJS_FFI_CTYPE_NONE:
res.ctype = FFI_CTYPE_WORD;
break;
case MJS_FFI_CTYPE_INVALID:
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "wrong ffi return type");
goto clean;
}
res.v.i = 0;
nargs = mjs_stack_size(&mjs->stack) - mjs_get_int(mjs, vtop(&mjs->call_stack));
if(nargs != psig->args_cnt) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs, ret, "got %d actuals, but function takes %d args", nargs, psig->args_cnt);
goto clean;
}
for(i = 0; i < nargs; i++) {
mjs_val_t arg = mjs_arg(mjs, i);
switch(psig->val_types[1 /* retval type */ + i]) {
case MJS_FFI_CTYPE_NONE:
/*
* Void argument: in any case, it's an error, because if C function
* takes no arguments, then the FFI-ed JS function should be called
* without any arguments, and thus we'll not face "void" here.
*/
ret = MJS_TYPE_ERROR;
if(i == 0) {
/* FFI signature is correct, but invocation is wrong */
mjs_prepend_errorf(mjs, ret, "ffi-ed function takes no arguments");
} else {
/*
* FFI signature is wrong: we can't have "void" as a non-first
* "argument"
*/
mjs_prepend_errorf(mjs, ret, "bad ffi arg #%d type: \"void\"", i);
}
goto clean;
case MJS_FFI_CTYPE_USERDATA:
/* Userdata for the callback */
if(cbdata.userdata_idx != -1) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs, ret, "two or more userdata args: #%d and %d", cbdata.userdata_idx, i);
goto clean;
}
cbdata.userdata = arg;
cbdata.userdata_idx = i;
break;
case MJS_FFI_CTYPE_INT: {
int intval = 0;
if(mjs_is_number(arg)) {
intval = mjs_get_int(mjs, arg);
} else if(mjs_is_boolean(arg)) {
intval = mjs_get_bool(mjs, arg);
} else {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs,
ret,
"actual arg #%d is not an int (the type idx is: %s)",
i,
mjs_typeof(arg));
}
ffi_set_word(&args[i], intval);
} break;
case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: {
if(!mjs_is_string(arg)) {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs,
ret,
"actual arg #%d is not a string (the type idx is: %s)",
i,
mjs_typeof(arg));
goto clean;
}
argvs[i] = arg;
argvmgstr[i].p = mjs_get_string(mjs, &argvs[i], &argvmgstr[i].len);
/*
* String argument should be saved separately in order to support
* short strings (which are packed into mjs_val_t itself)
*/
ffi_set_ptr(&args[i], (void*)&argvmgstr[i]);
break;
}
case MJS_FFI_CTYPE_BOOL: {
int intval = 0;
if(mjs_is_number(arg)) {
intval = !!mjs_get_int(mjs, arg);
} else if(mjs_is_boolean(arg)) {
intval = mjs_get_bool(mjs, arg);
} else {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs,
ret,
"actual arg #%d is not a bool (the type idx is: %s)",
i,
mjs_typeof(arg));
}
ffi_set_word(&args[i], intval);
} break;
case MJS_FFI_CTYPE_DOUBLE:
ffi_set_double(&args[i], mjs_get_double(mjs, arg));
break;
case MJS_FFI_CTYPE_FLOAT:
ffi_set_float(&args[i], (float)mjs_get_double(mjs, arg));
break;
case MJS_FFI_CTYPE_CHAR_PTR: {
size_t s;
if(mjs_is_string(arg)) {
/*
* String argument should be saved separately in order to support
* short strings (which are packed into mjs_val_t itself)
*/
argvs[i] = arg;
ffi_set_ptr(&args[i], (void*)mjs_get_string(mjs, &argvs[i], &s));
} else if(mjs_is_null(arg)) {
ffi_set_ptr(&args[i], NULL);
} else {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs,
ret,
"actual arg #%d is not a string (the type idx is: %s)",
i,
mjs_typeof(arg));
goto clean;
}
} break;
case MJS_FFI_CTYPE_VOID_PTR:
if(mjs_is_string(arg)) {
size_t n;
/*
* String argument should be saved separately in order to support
* short strings (which are packed into mjs_val_t itself)
*/
argvs[i] = arg;
ffi_set_ptr(&args[i], (void*)mjs_get_string(mjs, &argvs[i], &n));
} else if(mjs_is_foreign(arg)) {
ffi_set_ptr(&args[i], (void*)mjs_get_ptr(mjs, arg));
} else if(mjs_is_null(arg)) {
ffi_set_ptr(&args[i], NULL);
} else {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "actual arg #%d is not a ptr", i);
goto clean;
}
break;
case MJS_FFI_CTYPE_CALLBACK:
if(mjs_is_function(arg) || mjs_is_foreign(arg) || mjs_is_ffi_sig(arg)) {
/*
* Current argument is a callback function pointer: remember the given
* JS function and the argument index
*/
cbdata.func = arg;
cbdata.func_idx = i;
} else {
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(
mjs,
ret,
"actual arg #%d is not a function, but %s",
i,
mjs_stringify_type((enum mjs_type)arg));
goto clean;
}
break;
case MJS_FFI_CTYPE_INVALID:
/* parse_cval_type() has already set a more detailed error */
ret = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, ret, "wrong arg type");
goto clean;
default:
abort();
break;
}
}
if(cbdata.userdata_idx >= 0 && cbdata.func_idx >= 0) {
struct mjs_ffi_cb_args* cbargs = NULL;
struct mjs_ffi_cb_args** pitem = NULL;
/* the function takes a callback */
/*
* Get cbargs: either reuse the existing one (if the matching item exists),
* or create a new one.
*/
pitem = ffi_get_matching(&mjs->ffi_cb_args, cbdata.func, cbdata.userdata);
if(*pitem == NULL) {
/* No matching cbargs item; we need to add a new one */
cbargs = calloc(1, sizeof(*cbargs));
cbargs->mjs = mjs;
cbargs->func = cbdata.func;
cbargs->userdata = cbdata.userdata;
mjs_ffi_sig_copy(&cbargs->sig, psig->cb_sig);
/* Establish a link to the newly allocated item */
*pitem = cbargs;
} else {
/* Found matching item: reuse it */
cbargs = *pitem;
}
{
union {
ffi_fn_t* fn;
void* p;
} u;
u.fn = psig->cb_sig->fn;
ffi_set_ptr(&args[cbdata.func_idx], u.p);
ffi_set_ptr(&args[cbdata.userdata_idx], cbargs);
}
} else if(!(cbdata.userdata_idx == -1 && cbdata.func_idx == -1)) {
/*
* incomplete signature: it contains either the function pointer or
* userdata. It should contain both or none.
*
* It should be handled in mjs_parse_ffi_signature().
*/
abort();
}
ffi_call_mjs(psig->fn, nargs, &res, args);
switch(rtype) {
case MJS_FFI_CTYPE_CHAR_PTR: {
const char* s = (const char*)(uintptr_t)res.v.i;
if(s != NULL) {
resv = mjs_mk_string(mjs, s, ~0, 1);
} else {
resv = MJS_NULL;
}
break;
}
case MJS_FFI_CTYPE_VOID_PTR:
resv = mjs_mk_foreign(mjs, (void*)(uintptr_t)res.v.i);
break;
case MJS_FFI_CTYPE_INT:
resv = mjs_mk_number(mjs, (int)res.v.i);
break;
case MJS_FFI_CTYPE_BOOL:
resv = mjs_mk_boolean(mjs, !!res.v.i);
break;
case MJS_FFI_CTYPE_DOUBLE:
resv = mjs_mk_number(mjs, res.v.d);
break;
case MJS_FFI_CTYPE_FLOAT:
resv = mjs_mk_number(mjs, res.v.f);
break;
default:
resv = mjs_mk_undefined();
break;
}
clean:
/*
* If there was some error, prepend an error message with the subject
* signature
*/
if(ret != MJS_OK) {
mjs_prepend_errorf(mjs, ret, "failed to call FFIed function");
/* TODO(dfrank) stringify mjs_ffi_sig_t in some human-readable format */
}
mjs_return(mjs, resv);
return ret;
}
/*
* TODO(dfrank): make it return boolean (when booleans are supported), instead
* of a number
*/
MJS_PRIVATE void mjs_ffi_cb_free(struct mjs* mjs) {
mjs_val_t ret = mjs_mk_number(mjs, 0);
mjs_val_t func = mjs_arg(mjs, 0);
mjs_val_t userdata = mjs_arg(mjs, 1);
if(mjs_is_function(func)) {
struct mjs_ffi_cb_args** pitem = ffi_get_matching(&mjs->ffi_cb_args, func, userdata);
if(*pitem != NULL) {
/* Found matching item: remove it from the linked list, and free */
struct mjs_ffi_cb_args* cbargs = *pitem;
*pitem = cbargs->next;
mjs_ffi_sig_free(&cbargs->sig);
free(cbargs);
ret = mjs_mk_number(mjs, 1);
}
} else {
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing argument 'func'");
}
mjs_return(mjs, ret);
}
void mjs_ffi_args_free_list(struct mjs* mjs) {
ffi_cb_args_t* next = mjs->ffi_cb_args;
while(next != NULL) {
ffi_cb_args_t* cur = next;
next = next->next;
free(cur);
}
}
MJS_PRIVATE void mjs_ffi_sig_init(mjs_ffi_sig_t* sig) {
memset(sig, 0, sizeof(*sig));
}
MJS_PRIVATE void mjs_ffi_sig_copy(mjs_ffi_sig_t* to, const mjs_ffi_sig_t* from) {
memcpy(to, from, sizeof(*to));
if(from->cb_sig != NULL) {
to->cb_sig = calloc(sizeof(*to->cb_sig), 1);
mjs_ffi_sig_copy(to->cb_sig, from->cb_sig);
}
}
MJS_PRIVATE void mjs_ffi_sig_free(mjs_ffi_sig_t* sig) {
if(sig->cb_sig != NULL) {
free(sig->cb_sig);
sig->cb_sig = NULL;
}
}
MJS_PRIVATE int mjs_ffi_sig_set_val_type(mjs_ffi_sig_t* sig, int idx, mjs_ffi_ctype_t type) {
if(idx < MJS_CB_SIGNATURE_MAX_SIZE) {
sig->val_types[idx] = type;
return 1;
} else {
/* Index is too large */
return 0;
}
}
MJS_PRIVATE int
mjs_ffi_sig_validate(struct mjs* mjs, mjs_ffi_sig_t* sig, enum ffi_sig_type sig_type) {
int ret = 0;
int i;
int callback_idx = 0;
int userdata_idx = 0;
sig->is_valid = 0;
switch(sig_type) {
case FFI_SIG_FUNC:
/* Make sure return type is fine */
if(sig->val_types[0] != MJS_FFI_CTYPE_NONE && sig->val_types[0] != MJS_FFI_CTYPE_INT &&
sig->val_types[0] != MJS_FFI_CTYPE_BOOL && sig->val_types[0] != MJS_FFI_CTYPE_DOUBLE &&
sig->val_types[0] != MJS_FFI_CTYPE_FLOAT &&
sig->val_types[0] != MJS_FFI_CTYPE_VOID_PTR &&
sig->val_types[0] != MJS_FFI_CTYPE_CHAR_PTR) {
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid return value type");
goto clean;
}
break;
case FFI_SIG_CALLBACK:
/* Make sure return type is fine */
if(sig->val_types[0] != MJS_FFI_CTYPE_NONE && sig->val_types[0] != MJS_FFI_CTYPE_INT &&
sig->val_types[0] != MJS_FFI_CTYPE_BOOL && sig->val_types[0] != MJS_FFI_CTYPE_DOUBLE &&
sig->val_types[0] != MJS_FFI_CTYPE_FLOAT &&
sig->val_types[0] != MJS_FFI_CTYPE_VOID_PTR) {
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid return value type");
goto clean;
}
}
/* Handle argument types */
for(i = 1; i < MJS_CB_SIGNATURE_MAX_SIZE; i++) {
mjs_ffi_ctype_t type = sig->val_types[i];
switch(type) {
case MJS_FFI_CTYPE_USERDATA:
if(userdata_idx != 0) {
/* There must be at most one userdata arg, but we have more */
mjs_prepend_errorf(
mjs,
MJS_TYPE_ERROR,
"more than one userdata arg: #%d and #%d",
(userdata_idx - 1),
(i - 1));
goto clean;
}
userdata_idx = i;
break;
case MJS_FFI_CTYPE_CALLBACK:
switch(sig_type) {
case FFI_SIG_FUNC:
break;
case FFI_SIG_CALLBACK:
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "callback can't take another callback");
goto clean;
}
callback_idx = i;
break;
case MJS_FFI_CTYPE_INT:
case MJS_FFI_CTYPE_BOOL:
case MJS_FFI_CTYPE_VOID_PTR:
case MJS_FFI_CTYPE_CHAR_PTR:
case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR:
case MJS_FFI_CTYPE_DOUBLE:
case MJS_FFI_CTYPE_FLOAT:
/* Do nothing */
break;
case MJS_FFI_CTYPE_NONE:
/* No more arguments */
goto args_over;
default:
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "invalid ffi_ctype: %d", type);
goto clean;
}
sig->args_cnt++;
}
args_over:
switch(sig_type) {
case FFI_SIG_FUNC:
if(!((callback_idx > 0 && userdata_idx > 0) || (callback_idx == 0 && userdata_idx == 0))) {
mjs_prepend_errorf(
mjs,
MJS_TYPE_ERROR,
"callback and userdata should be either both "
"present or both absent");
goto clean;
}
break;
case FFI_SIG_CALLBACK:
if(userdata_idx == 0) {
/* No userdata arg */
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "no userdata arg");
goto clean;
}
break;
}
ret = 1;
clean:
if(ret) {
sig->is_valid = 1;
}
return ret;
}
MJS_PRIVATE int mjs_ffi_is_regular_word(mjs_ffi_ctype_t type) {
switch(type) {
case MJS_FFI_CTYPE_INT:
case MJS_FFI_CTYPE_BOOL:
return 1;
default:
return 0;
}
}
MJS_PRIVATE int mjs_ffi_is_regular_word_or_void(mjs_ffi_ctype_t type) {
return (type == MJS_FFI_CTYPE_NONE || mjs_ffi_is_regular_word(type));
}
#ifdef _WIN32
void* dlsym(void* handle, const char* name) {
static HANDLE msvcrt_dll;
void* sym = NULL;
if(msvcrt_dll == NULL) msvcrt_dll = GetModuleHandle("msvcrt.dll");
if((sym = GetProcAddress(GetModuleHandle(NULL), name)) == NULL) {
sym = GetProcAddress(msvcrt_dll, name);
}
return sym;
}
#elif !defined(__unix__) && !defined(__APPLE__)
void* dlsym(void* handle, const char* name) {
(void)handle;
(void)name;
return NULL;
}
#endif