quickjs
#include <stdio.h>
#include <stdarg.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <limits.h>
#include <sys/stat.h>
#include <dirent.h>
#if defined(_WIN32)
#include <windows.h>
#include <conio.h>
#include <utime.h>
#else
#include <dlfcn.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#if defined(__APPLE__)
typedef sig_t sighandler_t;
#if !defined(environ)
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
#endif
#endif /* __APPLE__ */
#endif
#if !defined(_WIN32)
/* enable the os.Worker API. IT relies on POSIX threads */
#define USE_WORKER
#endif
#ifdef USE_WORKER
#include <pthread.h>
#include <stdatomic.h>
#endif
#include "cutils.h"
#include "list.h"
#include "quickjs-libc.h"
/* TODO:
- add socket calls
*/
typedef struct {
struct list_head link;
int fd;
JSValue rw_func[2];
} JSOSRWHandler;
typedef struct {
struct list_head link;
int sig_num;
JSValue func;
} JSOSSignalHandler;
typedef struct {
struct list_head link;
BOOL has_object;
int64_t timeout;
JSValue func;
} JSOSTimer;
typedef struct {
struct list_head link;
uint8_t *data;
size_t data_len;
/* list of SharedArrayBuffers, necessary to free the message */
uint8_t **sab_tab;
size_t sab_tab_len;
} JSWorkerMessage;
typedef struct {
int ref_count;
#ifdef USE_WORKER
pthread_mutex_t mutex;
#endif
struct list_head msg_queue; /* list of JSWorkerMessage.link */
int read_fd;
int write_fd;
} JSWorkerMessagePipe;
typedef struct {
struct list_head link;
JSWorkerMessagePipe *recv_pipe;
JSValue on_message_func;
} JSWorkerMessageHandler;
typedef struct JSThreadState {
struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */
struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
struct list_head os_timers; /* list of JSOSTimer.link */
struct list_head port_list; /* list of JSWorkerMessageHandler.link */
int eval_script_recurse; /* only used in the main thread */
/* not used in the main thread */
JSWorkerMessagePipe *recv_pipe, *send_pipe;
} JSThreadState;
static uint64_t os_pending_signals;
static int (*os_poll_func)(JSContext *ctx);
static void js_std_dbuf_init(JSContext *ctx, DynBuf *s)
{
dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt);
}
static BOOL my_isdigit(int c)
{
return (c >= '0' && c <= '9');
}
static JSValue js_printf_internal(JSContext *ctx,
int argc, JSValueConst *argv, FILE *fp)
{
char fmtbuf[32];
uint8_t cbuf[UTF8_CHAR_LEN_MAX+1];
JSValue res;
DynBuf dbuf;
const char *fmt_str;
const uint8_t *fmt, *fmt_end;
const uint8_t *p;
char *q;
int i, c, len, mod;
size_t fmt_len;
int32_t int32_arg;
int64_t int64_arg;
double double_arg;
const char *string_arg;
/* Use indirect call to dbuf_printf to prevent gcc warning */
int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf;
js_std_dbuf_init(ctx, &dbuf);
if (argc > 0) {
fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]);
if (!fmt_str)
goto fail;
i = 1;
fmt = (const uint8_t *)fmt_str;
fmt_end = fmt + fmt_len;
while (fmt < fmt_end) {
for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++)
continue;
dbuf_put(&dbuf, p, fmt - p);
if (fmt >= fmt_end)
break;
q = fmtbuf;
*q++ = *fmt++; /* copy '%' */
/* flags */
for(;;) {
c = *fmt;
if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' ||
c == '\'') {
if (q >= fmtbuf + sizeof(fmtbuf) - 1)
goto invalid;
*q++ = c;
fmt++;
} else {
break;
}
}
/* width */
if (*fmt == '*') {
if (i >= argc)
goto missing;
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
goto fail;
q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg);
fmt++;
} else {
while (my_isdigit(*fmt)) {
if (q >= fmtbuf + sizeof(fmtbuf) - 1)
goto invalid;
*q++ = *fmt++;
}
}
if (*fmt == '.') {
if (q >= fmtbuf + sizeof(fmtbuf) - 1)
goto invalid;
*q++ = *fmt++;
if (*fmt == '*') {
if (i >= argc)
goto missing;
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
goto fail;
q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg);
fmt++;
} else {
while (my_isdigit(*fmt)) {
if (q >= fmtbuf + sizeof(fmtbuf) - 1)
goto invalid;
*q++ = *fmt++;
}
}
}
/* we only support the "l" modifier for 64 bit numbers */
mod = ' ';
if (*fmt == 'l') {
mod = *fmt++;
}
/* type */
c = *fmt++;
if (q >= fmtbuf + sizeof(fmtbuf) - 1)
goto invalid;
*q++ = c;
*q = '\0';
switch (c) {
case 'c':
if (i >= argc)
goto missing;
if (JS_IsString(argv[i])) {
string_arg = JS_ToCString(ctx, argv[i++]);
if (!string_arg)
goto fail;
JS_FreeCString(ctx, string_arg);
} else {
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
goto fail;
}
/* handle utf-8 encoding explicitly */
if ((unsigned)int32_arg > 0x10FFFF)
int32_arg = 0xFFFD;
/* ignore conversion flags, width and precision */
len = unicode_to_utf8(cbuf, int32_arg);
dbuf_put(&dbuf, cbuf, len);
break;
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
if (i >= argc)
goto missing;
if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++]))
goto fail;
if (mod == 'l') {
/* 64 bit number */
#if defined(_WIN32)
if (q >= fmtbuf + sizeof(fmtbuf) - 3)
goto invalid;
q[2] = q[-1];
q[-1] = 'I';
q[0] = '6';
q[1] = '4';
q[3] = '\0';
dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg);
#else
if (q >= fmtbuf + sizeof(fmtbuf) - 2)
goto invalid;
q[1] = q[-1];
q[-1] = q[0] = 'l';
q[2] = '\0';
dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg);
#endif
} else {
dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg);
}
break;
case 's':
if (i >= argc)
goto missing;
/* XXX: handle strings containing null characters */
string_arg = JS_ToCString(ctx, argv[i++]);
if (!string_arg)
goto fail;
dbuf_printf_fun(&dbuf, fmtbuf, string_arg);
JS_FreeCString(ctx, string_arg);
break;
case 'e':
case 'f':
case 'g':
case 'a':
case 'E':
case 'F':
case 'G':
case 'A':
if (i >= argc)
goto missing;
if (JS_ToFloat64(ctx, &double_arg, argv[i++]))
goto fail;
dbuf_printf_fun(&dbuf, fmtbuf, double_arg);
break;
case '%':
dbuf_putc(&dbuf, '%');
break;
default:
/* XXX: should support an extension mechanism */
invalid:
JS_ThrowTypeError(ctx, "invalid conversion specifier in format string");
goto fail;
missing:
JS_ThrowReferenceError(ctx, "missing argument for conversion specifier");
goto fail;
}
}
JS_FreeCString(ctx, fmt_str);
}
if (dbuf.error) {
res = JS_ThrowOutOfMemory(ctx);
} else {
if (fp) {
len = fwrite(dbuf.buf, 1, dbuf.size, fp);
res = JS_NewInt32(ctx, len);
} else {
res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size);
}
}
dbuf_free(&dbuf);
return res;
fail:
dbuf_free(&dbuf);
return JS_EXCEPTION;
}
uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename)
{
FILE *f;
uint8_t *buf;
size_t buf_len;
long lret;
f = fopen(filename, "rb");
if (!f)
return NULL;
if (fseek(f, 0, SEEK_END) < 0)
goto fail;
lret = ftell(f);
if (lret < 0)
goto fail;
/* XXX: on Linux, ftell() return LONG_MAX for directories */
if (lret == LONG_MAX) {
errno = EISDIR;
goto fail;
}
buf_len = lret;
if (fseek(f, 0, SEEK_SET) < 0)
goto fail;
if (ctx)
buf = js_malloc(ctx, buf_len + 1);
else
buf = malloc(buf_len + 1);
if (!buf)
goto fail;
if (fread(buf, 1, buf_len, f) != buf_len) {
errno = EIO;
if (ctx)
js_free(ctx, buf);
else
free(buf);
fail:
fclose(f);
return NULL;
}
buf[buf_len] = '\0';
fclose(f);
*pbuf_len = buf_len;
return buf;
}
/* load and evaluate a file */
static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
uint8_t *buf;
const char *filename;
JSValue ret;
size_t buf_len;
filename = JS_ToCString(ctx, argv[0]);
if (!filename)
return JS_EXCEPTION;
buf = js_load_file(ctx, &buf_len, filename);
if (!buf) {
JS_ThrowReferenceError(ctx, "could not load '%s'", filename);
JS_FreeCString(ctx, filename);
return JS_EXCEPTION;
}
ret = JS_Eval(ctx, (char *)buf, buf_len, filename,
JS_EVAL_TYPE_GLOBAL);
js_free(ctx, buf);
JS_FreeCString(ctx, filename);
return ret;
}
/* load a file as a UTF-8 encoded string */
static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
uint8_t *buf;
const char *filename;
JSValue ret;
size_t buf_len;
filename = JS_ToCString(ctx, argv[0]);
if (!filename)
return JS_EXCEPTION;
buf = js_load_file(ctx, &buf_len, filename);
JS_FreeCString(ctx, filename);
if (!buf)
return JS_NULL;
ret = JS_NewStringLen(ctx, (char *)buf, buf_len);
js_free(ctx, buf);
return ret;
}
typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx,
const char *module_name);
#if defined(_WIN32)
static JSModuleDef *js_module_loader_so(JSContext *ctx,
const char *module_name)
{
JS_ThrowReferenceError(ctx, "shared library modules are not supported yet");
return NULL;
}
#else
static JSModuleDef *js_module_loader_so(JSContext *ctx,
const char *module_name)
{
JSModuleDef *m;
void *hd;
JSInitModuleFunc *init;
char *filename;
if (!strchr(module_name, '/')) {
/* must add a '/' so that the DLL is not searched in the
system library paths */
filename = js_malloc(ctx, strlen(module_name) + 2 + 1);
if (!filename)
return NULL;
strcpy(filename, "./");
strcpy(filename + 2, module_name);
} else {
filename = (char *)module_name;
}
/* C module */
hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
if (filename != module_name)
js_free(ctx, filename);
if (!hd) {
goto fail;
}
init = dlsym(hd, "js_init_module");
if (!init) {
goto fail;
}
m = init(ctx, module_name);
if (!m) {
fail:
if (hd)
dlclose(hd);
return NULL;
}
return m;
}
#endif /* !_WIN32 */
int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
JS_BOOL use_realpath, JS_BOOL is_main)
{
JSModuleDef *m;
char buf[PATH_MAX + 16];
JSValue meta_obj;
JSAtom module_name_atom;
const char *module_name;
assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE);
m = JS_VALUE_GET_PTR(func_val);
module_name_atom = JS_GetModuleName(ctx, m);
module_name = JS_AtomToCString(ctx, module_name_atom);
JS_FreeAtom(ctx, module_name_atom);
if (!module_name)
return -1;
if (!strchr(module_name, ':')) {
strcpy(buf, "file://");
#if !defined(_WIN32)
/* realpath() cannot be used with modules compiled with qjsc
because the corresponding module source code is not
necessarily present */
if (use_realpath) {
char *res = realpath(module_name, buf + strlen(buf));
if (!res) {
JS_ThrowTypeError(ctx, "realpath failure");
JS_FreeCString(ctx, module_name);
return -1;
}
} else
#endif
{
pstrcat(buf, sizeof(buf), module_name);
}
} else {
pstrcpy(buf, sizeof(buf), module_name);
}
JS_FreeCString(ctx, module_name