quickjs-tart

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

qjs.c (16710B)


      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 #ifdef CONFIG_BIGNUM
     49 extern const uint8_t qjsc_qjscalc[];
     50 extern const uint32_t qjsc_qjscalc_size;
     51 #endif
     52 static int bignum_ext;
     53 
     54 static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
     55                     const char *filename, int eval_flags)
     56 {
     57     JSValue val;
     58     int ret;
     59 
     60     if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
     61         /* for the modules, we compile then run to be able to set
     62            import.meta */
     63         val = JS_Eval(ctx, buf, buf_len, filename,
     64                       eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
     65         if (!JS_IsException(val)) {
     66             js_module_set_import_meta(ctx, val, TRUE, TRUE);
     67             val = JS_EvalFunction(ctx, val);
     68         }
     69         val = js_std_await(ctx, val);
     70     } else {
     71         val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
     72     }
     73     if (JS_IsException(val)) {
     74         js_std_dump_error(ctx);
     75         ret = -1;
     76     } else {
     77         ret = 0;
     78     }
     79     JS_FreeValue(ctx, val);
     80     return ret;
     81 }
     82 
     83 static int eval_file(JSContext *ctx, const char *filename, int module)
     84 {
     85     uint8_t *buf;
     86     int ret, eval_flags;
     87     size_t buf_len;
     88 
     89     buf = js_load_file(ctx, &buf_len, filename);
     90     if (!buf) {
     91         perror(filename);
     92         exit(1);
     93     }
     94 
     95     if (module < 0) {
     96         module = (has_suffix(filename, ".mjs") ||
     97                   JS_DetectModule((const char *)buf, buf_len));
     98     }
     99     if (module)
    100         eval_flags = JS_EVAL_TYPE_MODULE;
    101     else
    102         eval_flags = JS_EVAL_TYPE_GLOBAL;
    103     ret = eval_buf(ctx, buf, buf_len, filename, eval_flags);
    104     js_free(ctx, buf);
    105     return ret;
    106 }
    107 
    108 /* also used to initialize the worker context */
    109 static JSContext *JS_NewCustomContext(JSRuntime *rt)
    110 {
    111     JSContext *ctx;
    112     ctx = JS_NewContext(rt);
    113     if (!ctx)
    114         return NULL;
    115 #ifdef CONFIG_BIGNUM
    116     if (bignum_ext) {
    117         JS_AddIntrinsicBigFloat(ctx);
    118         JS_AddIntrinsicBigDecimal(ctx);
    119         JS_AddIntrinsicOperators(ctx);
    120         JS_EnableBignumExt(ctx, TRUE);
    121     }
    122 #endif
    123     /* system modules */
    124     js_init_module_std(ctx, "std");
    125     js_init_module_os(ctx, "os");
    126     return ctx;
    127 }
    128 
    129 #if defined(__APPLE__)
    130 #define MALLOC_OVERHEAD  0
    131 #else
    132 #define MALLOC_OVERHEAD  8
    133 #endif
    134 
    135 struct trace_malloc_data {
    136     uint8_t *base;
    137 };
    138 
    139 static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr,
    140                                                 struct trace_malloc_data *dp)
    141 {
    142     return ptr - dp->base;
    143 }
    144 
    145 /* default memory allocation functions with memory limitation */
    146 static size_t js_trace_malloc_usable_size(const void *ptr)
    147 {
    148 #if defined(__APPLE__)
    149     return malloc_size(ptr);
    150 #elif defined(_WIN32)
    151     return _msize((void *)ptr);
    152 #elif defined(EMSCRIPTEN)
    153     return 0;
    154 #elif defined(__linux__) || defined(__GLIBC__)
    155     return malloc_usable_size((void *)ptr);
    156 #else
    157     /* change this to `return 0;` if compilation fails */
    158     return malloc_usable_size((void *)ptr);
    159 #endif
    160 }
    161 
    162 static void
    163 #ifdef _WIN32
    164 /* mingw printf is used */
    165 __attribute__((format(gnu_printf, 2, 3)))
    166 #else
    167 __attribute__((format(printf, 2, 3)))
    168 #endif
    169     js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...)
    170 {
    171     va_list ap;
    172     int c;
    173 
    174     va_start(ap, fmt);
    175     while ((c = *fmt++) != '\0') {
    176         if (c == '%') {
    177             /* only handle %p and %zd */
    178             if (*fmt == 'p') {
    179                 uint8_t *ptr = va_arg(ap, void *);
    180                 if (ptr == NULL) {
    181                     printf("NULL");
    182                 } else {
    183                     printf("H%+06lld.%zd",
    184                            js_trace_malloc_ptr_offset(ptr, s->opaque),
    185                            js_trace_malloc_usable_size(ptr));
    186                 }
    187                 fmt++;
    188                 continue;
    189             }
    190             if (fmt[0] == 'z' && fmt[1] == 'd') {
    191                 size_t sz = va_arg(ap, size_t);
    192                 printf("%zd", sz);
    193                 fmt += 2;
    194                 continue;
    195             }
    196         }
    197         putc(c, stdout);
    198     }
    199     va_end(ap);
    200 }
    201 
    202 static void js_trace_malloc_init(struct trace_malloc_data *s)
    203 {
    204     free(s->base = malloc(8));
    205 }
    206 
    207 static void *js_trace_malloc(JSMallocState *s, size_t size)
    208 {
    209     void *ptr;
    210 
    211     /* Do not allocate zero bytes: behavior is platform dependent */
    212     assert(size != 0);
    213 
    214     if (unlikely(s->malloc_size + size > s->malloc_limit))
    215         return NULL;
    216     ptr = malloc(size);
    217     js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr);
    218     if (ptr) {
    219         s->malloc_count++;
    220         s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    221     }
    222     return ptr;
    223 }
    224 
    225 static void js_trace_free(JSMallocState *s, void *ptr)
    226 {
    227     if (!ptr)
    228         return;
    229 
    230     js_trace_malloc_printf(s, "F %p\n", ptr);
    231     s->malloc_count--;
    232     s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    233     free(ptr);
    234 }
    235 
    236 static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size)
    237 {
    238     size_t old_size;
    239 
    240     if (!ptr) {
    241         if (size == 0)
    242             return NULL;
    243         return js_trace_malloc(s, size);
    244     }
    245     old_size = js_trace_malloc_usable_size(ptr);
    246     if (size == 0) {
    247         js_trace_malloc_printf(s, "R %zd %p\n", size, ptr);
    248         s->malloc_count--;
    249         s->malloc_size -= old_size + MALLOC_OVERHEAD;
    250         free(ptr);
    251         return NULL;
    252     }
    253     if (s->malloc_size + size - old_size > s->malloc_limit)
    254         return NULL;
    255 
    256     js_trace_malloc_printf(s, "R %zd %p", size, ptr);
    257 
    258     ptr = realloc(ptr, size);
    259     js_trace_malloc_printf(s, " -> %p\n", ptr);
    260     if (ptr) {
    261         s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size;
    262     }
    263     return ptr;
    264 }
    265 
    266 static const JSMallocFunctions trace_mf = {
    267     js_trace_malloc,
    268     js_trace_free,
    269     js_trace_realloc,
    270     js_trace_malloc_usable_size,
    271 };
    272 
    273 #define PROG_NAME "qjs"
    274 
    275 void help(void)
    276 {
    277     printf("QuickJS version " CONFIG_VERSION "\n"
    278            "usage: " PROG_NAME " [options] [file [args]]\n"
    279            "-h  --help         list options\n"
    280            "-e  --eval EXPR    evaluate EXPR\n"
    281            "-i  --interactive  go to interactive mode\n"
    282            "-m  --module       load as ES6 module (default=autodetect)\n"
    283            "    --script       load as ES6 script (default=autodetect)\n"
    284            "-I  --include file include an additional file\n"
    285            "    --std          make 'std' and 'os' available to the loaded script\n"
    286 #ifdef CONFIG_BIGNUM
    287            "    --bignum       enable the bignum extensions (BigFloat, BigDecimal)\n"
    288            "    --qjscalc      load the QJSCalc runtime (default if invoked as qjscalc)\n"
    289 #endif
    290            "-T  --trace        trace memory allocation\n"
    291            "-d  --dump         dump the memory usage stats\n"
    292            "    --memory-limit n       limit the memory usage to 'n' bytes\n"
    293            "    --stack-size n         limit the stack size to 'n' bytes\n"
    294            "    --unhandled-rejection  dump unhandled promise rejections\n"
    295            "-q  --quit         just instantiate the interpreter and quit\n");
    296     exit(1);
    297 }
    298 
    299 int main(int argc, char **argv)
    300 {
    301     JSRuntime *rt;
    302     JSContext *ctx;
    303     struct trace_malloc_data trace_data = { NULL };
    304     int optind;
    305     char *expr = NULL;
    306     int interactive = 0;
    307     int dump_memory = 0;
    308     int trace_memory = 0;
    309     int empty_run = 0;
    310     int module = -1;
    311     int load_std = 0;
    312     int dump_unhandled_promise_rejection = 0;
    313     size_t memory_limit = 0;
    314     char *include_list[32];
    315     int i, include_count = 0;
    316     int load_jscalc = 0;
    317     size_t stack_size = 0;
    318 
    319 #ifdef CONFIG_BIGNUM
    320     /* load jscalc runtime if invoked as 'qjscalc' */
    321     {
    322         const char *p, *exename;
    323         exename = argv[0];
    324         p = strrchr(exename, '/');
    325         if (p)
    326             exename = p + 1;
    327         load_jscalc = !strcmp(exename, "qjscalc");
    328     }
    329 #endif
    330 
    331     /* cannot use getopt because we want to pass the command line to
    332        the script */
    333     optind = 1;
    334     while (optind < argc && *argv[optind] == '-') {
    335         char *arg = argv[optind] + 1;
    336         const char *longopt = "";
    337         /* a single - is not an option, it also stops argument scanning */
    338         if (!*arg)
    339             break;
    340         optind++;
    341         if (*arg == '-') {
    342             longopt = arg + 1;
    343             arg += strlen(arg);
    344             /* -- stops argument scanning */
    345             if (!*longopt)
    346                 break;
    347         }
    348         for (; *arg || *longopt; longopt = "") {
    349             char opt = *arg;
    350             if (opt)
    351                 arg++;
    352             if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) {
    353                 help();
    354                 continue;
    355             }
    356             if (opt == 'e' || !strcmp(longopt, "eval")) {
    357                 if (*arg) {
    358                     expr = arg;
    359                     break;
    360                 }
    361                 if (optind < argc) {
    362                     expr = argv[optind++];
    363                     break;
    364                 }
    365                 fprintf(stderr, "qjs: missing expression for -e\n");
    366                 exit(2);
    367             }
    368             if (opt == 'I' || !strcmp(longopt, "include")) {
    369                 if (optind >= argc) {
    370                     fprintf(stderr, "expecting filename");
    371                     exit(1);
    372                 }
    373                 if (include_count >= countof(include_list)) {
    374                     fprintf(stderr, "too many included files");
    375                     exit(1);
    376                 }
    377                 include_list[include_count++] = argv[optind++];
    378                 continue;
    379             }
    380             if (opt == 'i' || !strcmp(longopt, "interactive")) {
    381                 interactive++;
    382                 continue;
    383             }
    384             if (opt == 'm' || !strcmp(longopt, "module")) {
    385                 module = 1;
    386                 continue;
    387             }
    388             if (!strcmp(longopt, "script")) {
    389                 module = 0;
    390                 continue;
    391             }
    392             if (opt == 'd' || !strcmp(longopt, "dump")) {
    393                 dump_memory++;
    394                 continue;
    395             }
    396             if (opt == 'T' || !strcmp(longopt, "trace")) {
    397                 trace_memory++;
    398                 continue;
    399             }
    400             if (!strcmp(longopt, "std")) {
    401                 load_std = 1;
    402                 continue;
    403             }
    404             if (!strcmp(longopt, "unhandled-rejection")) {
    405                 dump_unhandled_promise_rejection = 1;
    406                 continue;
    407             }
    408 #ifdef CONFIG_BIGNUM
    409             if (!strcmp(longopt, "bignum")) {
    410                 bignum_ext = 1;
    411                 continue;
    412             }
    413             if (!strcmp(longopt, "qjscalc")) {
    414                 load_jscalc = 1;
    415                 continue;
    416             }
    417 #endif
    418             if (opt == 'q' || !strcmp(longopt, "quit")) {
    419                 empty_run++;
    420                 continue;
    421             }
    422             if (!strcmp(longopt, "memory-limit")) {
    423                 if (optind >= argc) {
    424                     fprintf(stderr, "expecting memory limit");
    425                     exit(1);
    426                 }
    427                 memory_limit = (size_t)strtod(argv[optind++], NULL);
    428                 continue;
    429             }
    430             if (!strcmp(longopt, "stack-size")) {
    431                 if (optind >= argc) {
    432                     fprintf(stderr, "expecting stack size");
    433                     exit(1);
    434                 }
    435                 stack_size = (size_t)strtod(argv[optind++], NULL);
    436                 continue;
    437             }
    438             if (opt) {
    439                 fprintf(stderr, "qjs: unknown option '-%c'\n", opt);
    440             } else {
    441                 fprintf(stderr, "qjs: unknown option '--%s'\n", longopt);
    442             }
    443             help();
    444         }
    445     }
    446 
    447 #ifdef CONFIG_BIGNUM
    448     if (load_jscalc)
    449         bignum_ext = 1;
    450 #endif
    451 
    452     if (trace_memory) {
    453         js_trace_malloc_init(&trace_data);
    454         rt = JS_NewRuntime2(&trace_mf, &trace_data);
    455     } else {
    456         rt = JS_NewRuntime();
    457     }
    458     if (!rt) {
    459         fprintf(stderr, "qjs: cannot allocate JS runtime\n");
    460         exit(2);
    461     }
    462     if (memory_limit != 0)
    463         JS_SetMemoryLimit(rt, memory_limit);
    464     if (stack_size != 0)
    465         JS_SetMaxStackSize(rt, stack_size);
    466     js_std_set_worker_new_context_func(JS_NewCustomContext);
    467     js_std_init_handlers(rt);
    468     ctx = JS_NewCustomContext(rt);
    469     if (!ctx) {
    470         fprintf(stderr, "qjs: cannot allocate JS context\n");
    471         exit(2);
    472     }
    473 
    474     /* loader for ES6 modules */
    475     JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
    476 
    477     if (dump_unhandled_promise_rejection) {
    478         JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker,
    479                                           NULL);
    480     }
    481 
    482     if (!empty_run) {
    483 #ifdef CONFIG_BIGNUM
    484         if (load_jscalc) {
    485             js_std_eval_binary(ctx, qjsc_qjscalc, qjsc_qjscalc_size, 0);
    486         }
    487 #endif
    488         js_std_add_helpers(ctx, argc - optind, argv + optind);
    489 
    490         /* make 'std' and 'os' visible to non module code */
    491         if (load_std) {
    492             const char *str = "import * as std from 'std';\n"
    493                 "import * as os from 'os';\n"
    494                 "globalThis.std = std;\n"
    495                 "globalThis.os = os;\n";
    496             eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
    497         }
    498 
    499         for(i = 0; i < include_count; i++) {
    500             if (eval_file(ctx, include_list[i], module))
    501                 goto fail;
    502         }
    503 
    504         if (expr) {
    505             if (eval_buf(ctx, expr, strlen(expr), "<cmdline>", 0))
    506                 goto fail;
    507         } else
    508         if (optind >= argc) {
    509             /* interactive mode */
    510             interactive = 1;
    511         } else {
    512             const char *filename;
    513             filename = argv[optind];
    514             if (eval_file(ctx, filename, module))
    515                 goto fail;
    516         }
    517         if (interactive) {
    518             js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0);
    519         }
    520         js_std_loop(ctx);
    521     }
    522 
    523     if (dump_memory) {
    524         JSMemoryUsage stats;
    525         JS_ComputeMemoryUsage(rt, &stats);
    526         JS_DumpMemoryUsage(stdout, &stats, rt);
    527     }
    528     js_std_free_handlers(rt);
    529     JS_FreeContext(ctx);
    530     JS_FreeRuntime(rt);
    531 
    532     if (empty_run && dump_memory) {
    533         clock_t t[5];
    534         double best[5];
    535         int i, j;
    536         for (i = 0; i < 100; i++) {
    537             t[0] = clock();
    538             rt = JS_NewRuntime();
    539             t[1] = clock();
    540             ctx = JS_NewContext(rt);
    541             t[2] = clock();
    542             JS_FreeContext(ctx);
    543             t[3] = clock();
    544             JS_FreeRuntime(rt);
    545             t[4] = clock();
    546             for (j = 4; j > 0; j--) {
    547                 double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC;
    548                 if (i == 0 || best[j] > ms)
    549                     best[j] = ms;
    550             }
    551         }
    552         printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n",
    553                best[1] + best[2] + best[3] + best[4],
    554                best[1], best[2], best[3], best[4]);
    555     }
    556     return 0;
    557  fail:
    558     js_std_free_handlers(rt);
    559     JS_FreeContext(ctx);
    560     JS_FreeRuntime(rt);
    561     return 1;
    562 }