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