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