qjs.c 17 KB

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