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 }