quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

qjs.c (16972B)


      1 /*
      2  * QuickJS stand alone interpreter
      3  *
      4  * Copyright (c) 2017-2021 Fabrice Bellard
      5  * Copyright (c) 2017-2021 Charlie Gordon
      6  *
      7  * Permission is hereby granted, free of charge, to any person obtaining a copy
      8  * of this software and associated documentation files (the "Software"), to deal
      9  * in the Software without restriction, including without limitation the rights
     10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     11  * copies of the Software, and to permit persons to whom the Software is
     12  * furnished to do so, subject to the following conditions:
     13  *
     14  * The above copyright notice and this permission notice shall be included in
     15  * all copies or substantial portions of the Software.
     16  *
     17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     23  * THE SOFTWARE.
     24  */
     25 #include <stdlib.h>
     26 #include <stdio.h>
     27 #include <stdarg.h>
     28 #include <inttypes.h>
     29 #include <string.h>
     30 #include <assert.h>
     31 #include <unistd.h>
     32 #include <errno.h>
     33 #include <fcntl.h>
     34 #include <time.h>
     35 #if defined(__APPLE__)
     36 #include <malloc/malloc.h>
     37 #elif defined(__linux__) || defined(__GLIBC__)
     38 #include <malloc.h>
     39 #elif defined(__FreeBSD__)
     40 #include <malloc_np.h>
     41 #endif
     42 
     43 #include "cutils.h"
     44 #include "quickjs-libc.h"
     45 
     46 extern const uint8_t qjsc_repl[];
     47 extern const uint32_t qjsc_repl_size;
     48 
     49 static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
     50                     const char *filename, int eval_flags)
     51 {
     52     JSValue val;
     53     int ret;
     54 
     55     if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
     56         /* for the modules, we compile then run to be able to set
     57            import.meta */
     58         val = JS_Eval(ctx, buf, buf_len, filename,
     59                       eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
     60         if (!JS_IsException(val)) {
     61             js_module_set_import_meta(ctx, val, TRUE, TRUE);
     62             val = JS_EvalFunction(ctx, val);
     63         }
     64         val = js_std_await(ctx, val);
     65     } else {
     66         val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
     67     }
     68     if (JS_IsException(val)) {
     69         js_std_dump_error(ctx);
     70         ret = -1;
     71     } else {
     72         ret = 0;
     73     }
     74     JS_FreeValue(ctx, val);
     75     return ret;
     76 }
     77 
     78 static int eval_file(JSContext *ctx, const char *filename, int module, int strict)
     79 {
     80     uint8_t *buf;
     81     int ret, eval_flags;
     82     size_t buf_len;
     83 
     84     buf = js_load_file(ctx, &buf_len, filename);
     85     if (!buf) {
     86         perror(filename);
     87         exit(1);
     88     }
     89 
     90     if (module < 0) {
     91         module = (has_suffix(filename, ".mjs") ||
     92                   JS_DetectModule((const char *)buf, buf_len));
     93     }
     94     if (module) {
     95         eval_flags = JS_EVAL_TYPE_MODULE;
     96     } else {
     97         eval_flags = JS_EVAL_TYPE_GLOBAL;
     98         if (strict)
     99             eval_flags |= JS_EVAL_FLAG_STRICT;
    100     }
    101     ret = eval_buf(ctx, buf, buf_len, filename, eval_flags);
    102     js_free(ctx, buf);
    103     return ret;
    104 }
    105 
    106 /* also used to initialize the worker context */
    107 static JSContext *JS_NewCustomContext(JSRuntime *rt)
    108 {
    109     JSContext *ctx;
    110     ctx = JS_NewContext(rt);
    111     if (!ctx)
    112         return NULL;
    113     /* system modules */
    114     js_init_module_std(ctx, "std");
    115     js_init_module_os(ctx, "os");
    116     return ctx;
    117 }
    118 
    119 #if defined(__APPLE__)
    120 #define MALLOC_OVERHEAD  0
    121 #else
    122 #define MALLOC_OVERHEAD  8
    123 #endif
    124 
    125 struct trace_malloc_data {
    126     uint8_t *base;
    127 };
    128 
    129 static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr,
    130                                                 struct trace_malloc_data *dp)
    131 {
    132     return ptr - dp->base;
    133 }
    134 
    135 /* default memory allocation functions with memory limitation */
    136 static size_t js_trace_malloc_usable_size(const void *ptr)
    137 {
    138 #if defined(__APPLE__)
    139     return malloc_size(ptr);
    140 #elif defined(_WIN32)
    141     return _msize((void *)ptr);
    142 #elif defined(__EMSCRIPTEN__)
    143     return 0;
    144 #elif defined(__linux__) || defined(__GLIBC__)
    145     return malloc_usable_size((void *)ptr);
    146 #else
    147     /* change this to `return 0;` if compilation fails */
    148     return malloc_usable_size((void *)ptr);
    149 #endif
    150 }
    151 
    152 static void
    153 #ifdef _WIN32
    154 /* mingw printf is used */
    155 __attribute__((format(gnu_printf, 2, 3)))
    156 #else
    157 __attribute__((format(printf, 2, 3)))
    158 #endif
    159     js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...)
    160 {
    161     va_list ap;
    162     int c;
    163 
    164     va_start(ap, fmt);
    165     while ((c = *fmt++) != '\0') {
    166         if (c == '%') {
    167             /* only handle %p and %zd */
    168             if (*fmt == 'p') {
    169                 uint8_t *ptr = va_arg(ap, void *);
    170                 if (ptr == NULL) {
    171                     printf("NULL");
    172                 } else {
    173                     printf("H%+06lld.%zd",
    174                            js_trace_malloc_ptr_offset(ptr, s->opaque),
    175                            js_trace_malloc_usable_size(ptr));
    176                 }
    177                 fmt++;
    178                 continue;
    179             }
    180             if (fmt[0] == 'z' && fmt[1] == 'd') {
    181                 size_t sz = va_arg(ap, size_t);
    182                 printf("%zd", sz);
    183                 fmt += 2;
    184                 continue;
    185             }
    186         }
    187         putc(c, stdout);
    188     }
    189     va_end(ap);
    190 }
    191 
    192 static void js_trace_malloc_init(struct trace_malloc_data *s)
    193 {
    194     free(s->base = malloc(8));
    195 }
    196 
    197 static void *js_trace_malloc(JSMallocState *s, size_t size)
    198 {
    199     void *ptr;
    200 
    201     /* Do not allocate zero bytes: behavior is platform dependent */
    202     assert(size != 0);
    203 
    204     if (unlikely(s->malloc_size + size > s->malloc_limit))
    205         return NULL;
    206     ptr = malloc(size);
    207     js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr);
    208     if (ptr) {
    209         s->malloc_count++;
    210         s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    211     }
    212     return ptr;
    213 }
    214 
    215 static void js_trace_free(JSMallocState *s, void *ptr)
    216 {
    217     if (!ptr)
    218         return;
    219 
    220     js_trace_malloc_printf(s, "F %p\n", ptr);
    221     s->malloc_count--;
    222     s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    223     free(ptr);
    224 }
    225 
    226 static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size)
    227 {
    228     size_t old_size;
    229 
    230     if (!ptr) {
    231         if (size == 0)
    232             return NULL;
    233         return js_trace_malloc(s, size);
    234     }
    235     old_size = js_trace_malloc_usable_size(ptr);
    236     if (size == 0) {
    237         js_trace_malloc_printf(s, "R %zd %p\n", size, ptr);
    238         s->malloc_count--;
    239         s->malloc_size -= old_size + MALLOC_OVERHEAD;
    240         free(ptr);
    241         return NULL;
    242     }
    243     if (s->malloc_size + size - old_size > s->malloc_limit)
    244         return NULL;
    245 
    246     js_trace_malloc_printf(s, "R %zd %p", size, ptr);
    247 
    248     ptr = realloc(ptr, size);
    249     js_trace_malloc_printf(s, " -> %p\n", ptr);
    250     if (ptr) {
    251         s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size;
    252     }
    253     return ptr;
    254 }
    255 
    256 static const JSMallocFunctions trace_mf = {
    257     js_trace_malloc,
    258     js_trace_free,
    259     js_trace_realloc,
    260     js_trace_malloc_usable_size,
    261 };
    262 
    263 static size_t get_suffixed_size(const char *str)
    264 {
    265     char *p;
    266     size_t v;
    267     v = (size_t)strtod(str, &p);
    268     switch(*p) {
    269     case 'G':
    270         v <<= 30;
    271         break;
    272     case 'M':
    273         v <<= 20;
    274         break;
    275     case 'k':
    276     case 'K':
    277         v <<= 10;
    278         break;
    279     default:
    280         if (*p != '\0') {
    281             fprintf(stderr, "qjs: invalid suffix: %s\n", p);
    282             exit(1);
    283         }
    284         break;
    285     }
    286     return v;
    287 }
    288 
    289 #define PROG_NAME "qjs"
    290 
    291 void help(void)
    292 {
    293     printf("QuickJS version " CONFIG_VERSION "\n"
    294            "usage: " PROG_NAME " [options] [file [args]]\n"
    295            "-h  --help         list options\n"
    296            "-e  --eval EXPR    evaluate EXPR\n"
    297            "-i  --interactive  go to interactive mode\n"
    298            "-m  --module       load as ES6 module (default=autodetect)\n"
    299            "    --script       load as ES6 script (default=autodetect)\n"
    300            "    --strict       force strict mode\n"
    301            "-I  --include file include an additional file\n"
    302            "    --std          make 'std' and 'os' available to the loaded script\n"
    303            "-T  --trace        trace memory allocation\n"
    304            "-d  --dump         dump the memory usage stats\n"
    305            "    --memory-limit n  limit the memory usage to 'n' bytes (SI suffixes allowed)\n"
    306            "    --stack-size n    limit the stack size to 'n' bytes (SI suffixes allowed)\n"
    307            "    --no-unhandled-rejection  ignore unhandled promise rejections\n"
    308            "-s                    strip all the debug info\n"
    309            "    --strip-source    strip the source code\n"
    310            "-q  --quit         just instantiate the interpreter and quit\n");
    311     exit(1);
    312 }
    313 
    314 int main(int argc, char **argv)
    315 {
    316     JSRuntime *rt;
    317     JSContext *ctx;
    318     struct trace_malloc_data trace_data = { NULL };
    319     int optind;
    320     char *expr = NULL;
    321     int interactive = 0;
    322     int dump_memory = 0;
    323     int trace_memory = 0;
    324     int empty_run = 0;
    325     int module = -1;
    326     int strict = 0;
    327     int load_std = 0;
    328     int dump_unhandled_promise_rejection = 1;
    329     size_t memory_limit = 0;
    330     char *include_list[32];
    331     int i, include_count = 0;
    332     int strip_flags = 0;
    333     size_t stack_size = 0;
    334 
    335     /* cannot use getopt because we want to pass the command line to
    336        the script */
    337     optind = 1;
    338     while (optind < argc && *argv[optind] == '-') {
    339         char *arg = argv[optind] + 1;
    340         const char *longopt = "";
    341         /* a single - is not an option, it also stops argument scanning */
    342         if (!*arg)
    343             break;
    344         optind++;
    345         if (*arg == '-') {
    346             longopt = arg + 1;
    347             arg += strlen(arg);
    348             /* -- stops argument scanning */
    349             if (!*longopt)
    350                 break;
    351         }
    352         for (; *arg || *longopt; longopt = "") {
    353             char opt = *arg;
    354             if (opt)
    355                 arg++;
    356             if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) {
    357                 help();
    358                 continue;
    359             }
    360             if (opt == 'e' || !strcmp(longopt, "eval")) {
    361                 if (*arg) {
    362                     expr = arg;
    363                     break;
    364                 }
    365                 if (optind < argc) {
    366                     expr = argv[optind++];
    367                     break;
    368                 }
    369                 fprintf(stderr, "qjs: missing expression for -e\n");
    370                 exit(2);
    371             }
    372             if (opt == 'I' || !strcmp(longopt, "include")) {
    373                 if (optind >= argc) {
    374                     fprintf(stderr, "expecting filename");
    375                     exit(1);
    376                 }
    377                 if (include_count >= countof(include_list)) {
    378                     fprintf(stderr, "too many included files");
    379                     exit(1);
    380                 }
    381                 include_list[include_count++] = argv[optind++];
    382                 continue;
    383             }
    384             if (opt == 'i' || !strcmp(longopt, "interactive")) {
    385                 interactive++;
    386                 continue;
    387             }
    388             if (opt == 'm' || !strcmp(longopt, "module")) {
    389                 module = 1;
    390                 continue;
    391             }
    392             if (!strcmp(longopt, "script")) {
    393                 module = 0;
    394                 continue;
    395             }
    396             if (!strcmp(longopt, "strict")) {
    397                 strict = 1;
    398                 continue;
    399             }
    400             if (opt == 'd' || !strcmp(longopt, "dump")) {
    401                 dump_memory++;
    402                 continue;
    403             }
    404             if (opt == 'T' || !strcmp(longopt, "trace")) {
    405                 trace_memory++;
    406                 continue;
    407             }
    408             if (!strcmp(longopt, "std")) {
    409                 load_std = 1;
    410                 continue;
    411             }
    412             if (!strcmp(longopt, "no-unhandled-rejection")) {
    413                 dump_unhandled_promise_rejection = 0;
    414                 continue;
    415             }
    416             if (opt == 'q' || !strcmp(longopt, "quit")) {
    417                 empty_run++;
    418                 continue;
    419             }
    420             if (!strcmp(longopt, "memory-limit")) {
    421                 if (optind >= argc) {
    422                     fprintf(stderr, "expecting memory limit");
    423                     exit(1);
    424                 }
    425                 memory_limit = get_suffixed_size(argv[optind++]);
    426                 continue;
    427             }
    428             if (!strcmp(longopt, "stack-size")) {
    429                 if (optind >= argc) {
    430                     fprintf(stderr, "expecting stack size");
    431                     exit(1);
    432                 }
    433                 stack_size = get_suffixed_size(argv[optind++]);
    434                 continue;
    435             }
    436             if (opt == 's') {
    437                 strip_flags = JS_STRIP_DEBUG;
    438                 continue;
    439             }
    440             if (!strcmp(longopt, "strip-source")) {
    441                 strip_flags = JS_STRIP_SOURCE;
    442                 continue;
    443             }
    444             if (opt) {
    445                 fprintf(stderr, "qjs: unknown option '-%c'\n", opt);
    446             } else {
    447                 fprintf(stderr, "qjs: unknown option '--%s'\n", longopt);
    448             }
    449             help();
    450         }
    451     }
    452 
    453     if (trace_memory) {
    454         js_trace_malloc_init(&trace_data);
    455         rt = JS_NewRuntime2(&trace_mf, &trace_data);
    456     } else {
    457         rt = JS_NewRuntime();
    458     }
    459     if (!rt) {
    460         fprintf(stderr, "qjs: cannot allocate JS runtime\n");
    461         exit(2);
    462     }
    463     if (memory_limit != 0)
    464         JS_SetMemoryLimit(rt, memory_limit);
    465     if (stack_size != 0)
    466         JS_SetMaxStackSize(rt, stack_size);
    467     JS_SetStripInfo(rt, strip_flags);
    468     js_std_set_worker_new_context_func(JS_NewCustomContext);
    469     js_std_init_handlers(rt);
    470     ctx = JS_NewCustomContext(rt);
    471     if (!ctx) {
    472         fprintf(stderr, "qjs: cannot allocate JS context\n");
    473         exit(2);
    474     }
    475 
    476     /* loader for ES6 modules */
    477     JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL);
    478 
    479     if (dump_unhandled_promise_rejection) {
    480         JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker,
    481                                           NULL);
    482     }
    483 
    484     if (!empty_run) {
    485         js_std_add_helpers(ctx, argc - optind, argv + optind);
    486 
    487         /* make 'std' and 'os' visible to non module code */
    488         if (load_std) {
    489             const char *str = "import * as std from 'std';\n"
    490                 "import * as os from 'os';\n"
    491                 "globalThis.std = std;\n"
    492                 "globalThis.os = os;\n";
    493             eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
    494         }
    495 
    496         for(i = 0; i < include_count; i++) {
    497             if (eval_file(ctx, include_list[i], 0, strict))
    498                 goto fail;
    499         }
    500 
    501         if (expr) {
    502             int eval_flags;
    503             if (module > 0) {
    504                 eval_flags = JS_EVAL_TYPE_MODULE;
    505             } else {
    506                 eval_flags = JS_EVAL_TYPE_GLOBAL;
    507                 if (strict)
    508                     eval_flags |= JS_EVAL_FLAG_STRICT;
    509             }
    510             if (eval_buf(ctx, expr, strlen(expr), "<cmdline>", eval_flags))
    511                 goto fail;
    512         } else
    513         if (optind >= argc) {
    514             /* interactive mode */
    515             interactive = 1;
    516         } else {
    517             const char *filename;
    518             filename = argv[optind];
    519             if (eval_file(ctx, filename, module, strict))
    520                 goto fail;
    521         }
    522         if (interactive) {
    523             JS_SetHostPromiseRejectionTracker(rt, NULL, NULL);
    524             js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0);
    525         }
    526         js_std_loop(ctx);
    527     }
    528 
    529     if (dump_memory) {
    530         JSMemoryUsage stats;
    531         JS_ComputeMemoryUsage(rt, &stats);
    532         JS_DumpMemoryUsage(stdout, &stats, rt);
    533     }
    534     js_std_free_handlers(rt);
    535     JS_FreeContext(ctx);
    536     JS_FreeRuntime(rt);
    537 
    538     if (empty_run && dump_memory) {
    539         clock_t t[5];
    540         double best[5];
    541         int i, j;
    542         for (i = 0; i < 100; i++) {
    543             t[0] = clock();
    544             rt = JS_NewRuntime();
    545             t[1] = clock();
    546             ctx = JS_NewContext(rt);
    547             t[2] = clock();
    548             JS_FreeContext(ctx);
    549             t[3] = clock();
    550             JS_FreeRuntime(rt);
    551             t[4] = clock();
    552             for (j = 4; j > 0; j--) {
    553                 double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC;
    554                 if (i == 0 || best[j] > ms)
    555                     best[j] = ms;
    556             }
    557         }
    558         printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n",
    559                best[1] + best[2] + best[3] + best[4],
    560                best[1], best[2], best[3], best[4]);
    561     }
    562     return 0;
    563  fail:
    564     js_std_free_handlers(rt);
    565     JS_FreeContext(ctx);
    566     JS_FreeRuntime(rt);
    567     return 1;
    568 }