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 }