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) |
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 */ |
104 | static 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 | |
122 | struct trace_malloc_data { |
123 | uint8_t *base; |
124 | }; |
125 | |
126 | static 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 */ |
133 | static 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 | |
149 | static 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 | |
189 | static void js_trace_malloc_init(struct trace_malloc_data *s) |
190 | { |
191 | free(s->base = malloc(8)); |
192 | } |
193 | |
194 | static 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 | |
212 | static 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 | |
223 | static 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 | |
253 | static const JSMallocFunctions trace_mf = { |
254 | js_trace_malloc, |
255 | js_trace_free, |
256 | js_trace_realloc, |
257 | js_trace_malloc_usable_size, |
258 | }; |
259 | |
260 | static 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 | |
288 | void 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 | |
310 | int 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 | |