1 /*
2 * Command line execution tool. Useful for test cases and manual testing.
3 * Also demonstrates some basic integration techniques.
4 *
5 * Optional features include:
6 *
7 * - To enable print()/alert() bindings, define DUK_CMDLINE_PRINTALERT_SUPPORT
8 * and add extras/print-alert/duk_print_alert.c to compilation.
9 *
10 * - To enable console.log() etc, define DUK_CMDLINE_CONSOLE_SUPPORT
11 * and add extras/console/duk_console.c to compilation.
12 *
13 * - To enable Duktape.Logger, define DUK_CMDLINE_LOGGING_SUPPORT
14 * and add extras/logging/duk_logging.c to compilation.
15 *
16 * - To enable Duktape 1.x module loading support (require(),
17 * Duktape.modSearch() etc), define DUK_CMDLINE_MODULE_SUPPORT and add
18 * extras/module-duktape/duk_module_duktape.c to compilation.
19 *
20 * - To enable linenoise and other fancy stuff, compile with -DDUK_CMDLINE_FANCY.
21 * It is not the default to maximize portability. You can also compile in
22 * support for example allocators, grep for DUK_CMDLINE_*.
23 */
24
25 /* Helper define to enable a feature set; can also use separate defines. */
26 #if defined(DUK_CMDLINE_FANCY)
27 #define DUK_CMDLINE_LINENOISE
28 #define DUK_CMDLINE_LINENOISE_COMPLETION /* Enables completion and hints. */
29 #define DUK_CMDLINE_RLIMIT
30 #define DUK_CMDLINE_SIGNAL
31 #endif
32
33 #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
34 defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
35 /* Suppress warnings about plain fopen() etc. */
36 #define _CRT_SECURE_NO_WARNINGS
37 #if defined(_MSC_VER) && (_MSC_VER < 1900)
38 /* Workaround for snprintf() missing in older MSVC versions.
39 * Note that _snprintf() may not NUL terminate the string, but
40 * this difference does not matter here as a NUL terminator is
41 * always explicitly added.
42 */
43 #define snprintf _snprintf
44 #endif
45 #endif
46
47 #if defined(DUK_CMDLINE_PTHREAD_STACK_CHECK)
48 #define _GNU_SOURCE
49 #include <pthread.h>
50 #endif
51
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #if defined(DUK_CMDLINE_SIGNAL)
56 #include <signal.h>
57 #endif
58 #if defined(DUK_CMDLINE_RLIMIT)
59 #include <sys/resource.h>
60 #endif
61 #if defined(DUK_CMDLINE_LINENOISE)
62 #include "linenoise.h"
63 #include <stdint.h> /* Assume C99/C++11 with linenoise. */
64 #endif
65 #if defined(DUK_CMDLINE_PRINTALERT_SUPPORT)
66 #include "duk_print_alert.h"
67 #endif
68 #if defined(DUK_CMDLINE_CONSOLE_SUPPORT)
69 #include "duk_console.h"
70 #endif
71 #if defined(DUK_CMDLINE_LOGGING_SUPPORT)
72 #include "duk_logging.h"
73 #endif
74 #if defined(DUK_CMDLINE_MODULE_SUPPORT)
75 #include "duk_module_duktape.h"
76 #endif
77 #if defined(DUK_CMDLINE_FILEIO)
78 #include <errno.h>
79 #endif
80 #if defined(EMSCRIPTEN)
81 #include <emscripten.h>
82 #endif
83 #if defined(DUK_CMDLINE_ALLOC_LOGGING)
84 #include "duk_alloc_logging.h"
85 #endif
86 #if defined(DUK_CMDLINE_ALLOC_TORTURE)
87 #include "duk_alloc_torture.h"
88 #endif
89 #if defined(DUK_CMDLINE_ALLOC_HYBRID)
90 #include "duk_alloc_hybrid.h"
91 #endif
92 #include "duktape.h"
93
94 #include "duk_cmdline.h"
95
96 #if defined(DUK_CMDLINE_LOWMEM)
97 #include "duk_alloc_pool.h"
98 #endif
99
100 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
101 #include "duk_trans_socket.h"
102 #endif
103
104 #define MEM_LIMIT_NORMAL (128*1024*1024) /* 128 MB */
105 #define MEM_LIMIT_HIGH (2047*1024*1024) /* ~2 GB */
106
107 static int main_argc = 0;
108 static char **main_argv = NULL;
109 static int interactive_mode = 0;
110 static int allow_bytecode = 0;
111 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
112 static int debugger_reattach = 0;
113 #endif
114 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
115 static int no_auto_complete = 0;
116 #endif
117
118 int duk_cmdline_stack_check(void);
119
120 /*
121 * Misc helpers
122 */
123
print_greet_line(void)124 static void print_greet_line(void) {
125 printf("((o) Duktape%s %d.%d.%d (%s)\n",
126 #if defined(DUK_CMDLINE_LINENOISE)
127 " [linenoise]",
128 #else
129 "",
130 #endif
131 (int) (DUK_VERSION / 10000),
132 (int) ((DUK_VERSION / 100) % 100),
133 (int) (DUK_VERSION % 100),
134 DUK_GIT_DESCRIBE);
135 }
136
137 #if defined(DUK_CMDLINE_RLIMIT)
set_resource_limits(rlim_t mem_limit_value)138 static void set_resource_limits(rlim_t mem_limit_value) {
139 int rc;
140 struct rlimit lim;
141
142 rc = getrlimit(RLIMIT_AS, &lim);
143 if (rc != 0) {
144 fprintf(stderr, "Warning: cannot read RLIMIT_AS\n");
145 return;
146 }
147
148 if (lim.rlim_max < mem_limit_value) {
149 fprintf(stderr, "Warning: rlim_max < mem_limit_value (%d < %d)\n", (int) lim.rlim_max, (int) mem_limit_value);
150 return;
151 }
152
153 lim.rlim_cur = mem_limit_value;
154 lim.rlim_max = mem_limit_value;
155
156 rc = setrlimit(RLIMIT_AS, &lim);
157 if (rc != 0) {
158 fprintf(stderr, "Warning: setrlimit failed\n");
159 return;
160 }
161
162 #if 0
163 fprintf(stderr, "Set RLIMIT_AS to %d\n", (int) mem_limit_value);
164 #endif
165 }
166 #endif /* DUK_CMDLINE_RLIMIT */
167
168 #if defined(DUK_CMDLINE_SIGNAL)
my_sighandler(int x)169 static void my_sighandler(int x) {
170 fprintf(stderr, "Got signal %d\n", x);
171 fflush(stderr);
172 }
set_sigint_handler(void)173 static void set_sigint_handler(void) {
174 (void) signal(SIGINT, my_sighandler);
175 (void) signal(SIGPIPE, SIG_IGN); /* avoid SIGPIPE killing process */
176 }
177 #endif /* DUK_CMDLINE_SIGNAL */
178
cmdline_fatal_handler(void * udata,const char * msg)179 static void cmdline_fatal_handler(void *udata, const char *msg) {
180 (void) udata;
181 fprintf(stderr, "*** FATAL ERROR: %s\n", msg ? msg : "no message");
182 fprintf(stderr, "Causing intentional segfault...\n");
183 fflush(stderr);
184 *((volatile unsigned int *) 0) = (unsigned int) 0xdeadbeefUL;
185 abort();
186 }
187
188 /* Print error to stderr and pop error. */
print_pop_error(duk_context * ctx,FILE * f)189 static void print_pop_error(duk_context *ctx, FILE *f) {
190 fprintf(f, "%s\n", duk_safe_to_stacktrace(ctx, -1));
191 fflush(f);
192 duk_pop(ctx);
193 }
194
wrapped_compile_execute(duk_context * ctx,void * udata)195 static duk_ret_t wrapped_compile_execute(duk_context *ctx, void *udata) {
196 const char *src_data;
197 duk_size_t src_len;
198 duk_uint_t comp_flags;
199
200 (void) udata;
201
202 /* XXX: Here it'd be nice to get some stats for the compilation result
203 * when a suitable command line is given (e.g. code size, constant
204 * count, function count. These are available internally but not through
205 * the public API.
206 */
207
208 /* Use duk_compile_lstring_filename() variant which avoids interning
209 * the source code. This only really matters for low memory environments.
210 */
211
212 /* [ ... bytecode_filename src_data src_len filename ] */
213
214 src_data = (const char *) duk_require_pointer(ctx, -3);
215 src_len = (duk_size_t) duk_require_uint(ctx, -2);
216
217 if (src_data != NULL && src_len >= 1 && src_data[0] == (char) 0xbf) {
218 /* Bytecode. */
219 if (allow_bytecode) {
220 void *buf;
221 buf = duk_push_fixed_buffer(ctx, src_len);
222 memcpy(buf, (const void *) src_data, src_len);
223 duk_load_function(ctx);
224 } else {
225 (void) duk_type_error(ctx, "bytecode input rejected (use -b to allow bytecode inputs)");
226 }
227 } else {
228 /* Source code. */
229 comp_flags = DUK_COMPILE_SHEBANG;
230 duk_compile_lstring_filename(ctx, comp_flags, src_data, src_len);
231 }
232
233 /* [ ... bytecode_filename src_data src_len function ] */
234
235 /* Optional bytecode dump. */
236 if (duk_is_string(ctx, -4)) {
237 FILE *f;
238 void *bc_ptr;
239 duk_size_t bc_len;
240 size_t wrote;
241 char fnbuf[256];
242 const char *filename;
243
244 duk_dup_top(ctx);
245 duk_dump_function(ctx);
246 bc_ptr = duk_require_buffer_data(ctx, -1, &bc_len);
247 filename = duk_require_string(ctx, -5);
248 #if defined(EMSCRIPTEN)
249 if (filename[0] == '/') {
250 snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
251 } else {
252 snprintf(fnbuf, sizeof(fnbuf), "/working/%s", filename);
253 }
254 #else
255 snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
256 #endif
257 fnbuf[sizeof(fnbuf) - 1] = (char) 0;
258
259 f = fopen(fnbuf, "wb");
260 if (!f) {
261 (void) duk_generic_error(ctx, "failed to open bytecode output file");
262 }
263 wrote = fwrite(bc_ptr, 1, (size_t) bc_len, f); /* XXX: handle partial writes */
264 (void) fclose(f);
265 if (wrote != bc_len) {
266 (void) duk_generic_error(ctx, "failed to write all bytecode");
267 }
268
269 return 0; /* duk_safe_call() cleans up */
270 }
271
272 #if 0
273 /* Manual test for bytecode dump/load cycle: dump and load before
274 * execution. Enable manually, then run "make ecmatest" for a
275 * reasonably good coverage of different functions and programs.
276 */
277 duk_dump_function(ctx);
278 duk_load_function(ctx);
279 #endif
280
281 #if defined(DUK_CMDLINE_LOWMEM)
282 lowmem_start_exec_timeout();
283 #endif
284
285 duk_push_global_object(ctx); /* 'this' binding */
286 duk_call_method(ctx, 0);
287
288 #if defined(DUK_CMDLINE_LOWMEM)
289 lowmem_clear_exec_timeout();
290 #endif
291
292 if (interactive_mode) {
293 /*
294 * In interactive mode, write to stdout so output won't
295 * interleave as easily.
296 *
297 * NOTE: the ToString() coercion may fail in some cases;
298 * for instance, if you evaluate:
299 *
300 * ( {valueOf: function() {return {}},
301 * toString: function() {return {}}});
302 *
303 * The error is:
304 *
305 * TypeError: coercion to primitive failed
306 * duk_api.c:1420
307 *
308 * These are handled now by the caller which also has stack
309 * trace printing support. User code can print out errors
310 * safely using duk_safe_to_string().
311 */
312
313 duk_push_global_stash(ctx);
314 duk_get_prop_string(ctx, -1, "dukFormat");
315 duk_dup(ctx, -3);
316 duk_call(ctx, 1); /* -> [ ... res stash formatted ] */
317
318 fprintf(stdout, "= %s\n", duk_to_string(ctx, -1));
319 fflush(stdout);
320 } else {
321 /* In non-interactive mode, success results are not written at all.
322 * It is important that the result value is not string coerced,
323 * as the string coercion may cause an error in some cases.
324 */
325 }
326
327 return 0; /* duk_safe_call() cleans up */
328 }
329
330 /*
331 * Minimal Linenoise completion support
332 */
333
334 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
335 static duk_context *completion_ctx;
336
337 static const char *linenoise_completion_script =
338 "(function linenoiseCompletion(input, addCompletion) {\n"
339 " // Find maximal trailing string which looks like a property\n"
340 " // access. Look up all the components (starting from the global\n"
341 " // object now) except the last; treat the last component as a\n"
342 " // partial name and use it as a filter for possible properties.\n"
343 " var match, propseq, obj, i, partial, names, name, sanity;\n"
344 "\n"
345 " if (!input) { return; }\n"
346 " match = /^.*?((?:\\w+\\.)*\\w*)$/.exec(input);\n"
347 " if (!match || !match[1]) { return; }\n"
348 " var propseq = match[1].split('.');\n"
349 "\n"
350 " obj = Function('return this')();\n"
351 " for (i = 0; i < propseq.length - 1; i++) {\n"
352 " if (obj === void 0 || obj === null) { return; }\n"
353 " obj = obj[propseq[i]];\n"
354 " }\n"
355 " if (obj === void 0 || obj === null) { return; }\n"
356 "\n"
357 " partial = propseq[propseq.length - 1];\n"
358 " sanity = 1000;\n"
359 " while (obj != null) {\n"
360 " if (--sanity < 0) { throw new Error('sanity'); }\n"
361 " names = Object.getOwnPropertyNames(Object(obj));\n"
362 " for (i = 0; i < names.length; i++) {\n"
363 " if (--sanity < 0) { throw new Error('sanity'); }\n"
364 " name = names[i];\n"
365 " if (Number(name) >= 0) { continue; } // ignore array keys\n"
366 " if (name.substring(0, partial.length) !== partial) { continue; }\n"
367 " if (name === partial) { addCompletion(input + '.'); continue; }\n"
368 " addCompletion(input + name.substring(partial.length));\n"
369 " }\n"
370 " obj = Object.getPrototypeOf(Object(obj));\n"
371 " }\n"
372 "})";
373
374 static const char *linenoise_hints_script =
375 "(function linenoiseHints(input) {\n"
376 " // Similar to completions but different handling for final results.\n"
377 " var match, propseq, obj, i, partial, names, name, res, found, first, sanity;\n"
378 "\n"
379 " if (!input) { return; }\n"
380 " match = /^.*?((?:\\w+\\.)*\\w*)$/.exec(input);\n"
381 " if (!match || !match[1]) { return; }\n"
382 " var propseq = match[1].split('.');\n"
383 "\n"
384 " obj = Function('return this')();\n"
385 " for (i = 0; i < propseq.length - 1; i++) {\n"
386 " if (obj === void 0 || obj === null) { return; }\n"
387 " obj = obj[propseq[i]];\n"
388 " }\n"
389 " if (obj === void 0 || obj === null) { return; }\n"
390 "\n"
391 " partial = propseq[propseq.length - 1];\n"
392 " res = [];\n"
393 " found = Object.create(null); // keys already handled\n"
394 " sanity = 1000;\n"
395 " while (obj != null) {\n"
396 " if (--sanity < 0) { throw new Error('sanity'); }\n"
397 " names = Object.getOwnPropertyNames(Object(obj));\n"
398 " first = true;\n"
399 " for (i = 0; i < names.length; i++) {\n"
400 " if (--sanity < 0) { throw new Error('sanity'); }\n"
401 " name = names[i];\n"
402 " if (Number(name) >= 0) { continue; } // ignore array keys\n"
403 " if (name.substring(0, partial.length) !== partial) { continue; }\n"
404 " if (name === partial) { continue; }\n"
405 " if (found[name]) { continue; }\n"
406 " found[name] = true;\n"
407 " res.push(res.length === 0 ? name.substring(partial.length) : (first ? ' || ' : ' | ') + name);\n"
408 " first = false;\n"
409 " }\n"
410 " obj = Object.getPrototypeOf(Object(obj));\n"
411 " }\n"
412 " return { hints: res.join(''), color: 35, bold: 1 };\n"
413 "})";
414
linenoise_add_completion(duk_context * ctx)415 static duk_ret_t linenoise_add_completion(duk_context *ctx) {
416 linenoiseCompletions *lc;
417
418 duk_push_current_function(ctx);
419 duk_get_prop_string(ctx, -1, "lc");
420 lc = duk_require_pointer(ctx, -1);
421
422 linenoiseAddCompletion(lc, duk_require_string(ctx, 0));
423 return 0;
424 }
425
linenoise_hints(const char * buf,int * color,int * bold)426 static char *linenoise_hints(const char *buf, int *color, int *bold) {
427 duk_context *ctx;
428 duk_int_t rc;
429
430 ctx = completion_ctx;
431 if (!ctx) {
432 return NULL;
433 }
434
435 duk_push_global_stash(ctx);
436 duk_get_prop_string(ctx, -1, "linenoiseHints");
437 if (!buf) {
438 duk_push_undefined(ctx);
439 } else {
440 duk_push_string(ctx, buf);
441 }
442
443 rc = duk_pcall(ctx, 1 /*nargs*/); /* [ stash func ] -> [ stash result ] */
444 if (rc != 0) {
445 const char *res;
446 res = strdup(duk_safe_to_string(ctx, -1));
447 *color = 31; /* red */
448 *bold = 1;
449 duk_pop_2(ctx);
450 return (char *) (uintptr_t) res; /* uintptr_t cast to avoid const discard warning. */
451 }
452
453 if (duk_is_object(ctx, -1)) {
454 const char *tmp;
455 const char *res = NULL;
456
457 duk_get_prop_string(ctx, -1, "hints");
458 tmp = duk_get_string(ctx, -1);
459 if (tmp) {
460 res = strdup(tmp);
461 }
462 duk_pop(ctx);
463
464 duk_get_prop_string(ctx, -1, "color");
465 *color = duk_to_int(ctx, -1);
466 duk_pop(ctx);
467
468 duk_get_prop_string(ctx, -1, "bold");
469 *bold = duk_to_int(ctx, -1);
470 duk_pop(ctx);
471
472 duk_pop_2(ctx);
473 return (char *) (uintptr_t) res; /* uintptr_t cast to avoid const discard warning. */
474 }
475
476 duk_pop_2(ctx);
477 return NULL;
478 }
479
linenoise_freehints(void * ptr)480 static void linenoise_freehints(void *ptr) {
481 #if 0
482 printf("free hint: %p\n", (void *) ptr);
483 #endif
484 free(ptr);
485 }
486
linenoise_completion(const char * buf,linenoiseCompletions * lc)487 static void linenoise_completion(const char *buf, linenoiseCompletions *lc) {
488 duk_context *ctx;
489 duk_int_t rc;
490
491 ctx = completion_ctx;
492 if (!ctx) {
493 return;
494 }
495
496 duk_push_global_stash(ctx);
497 duk_get_prop_string(ctx, -1, "linenoiseCompletion");
498
499 if (!buf) {
500 duk_push_undefined(ctx);
501 } else {
502 duk_push_string(ctx, buf);
503 }
504 duk_push_c_function(ctx, linenoise_add_completion, 2 /*nargs*/);
505 duk_push_pointer(ctx, (void *) lc);
506 duk_put_prop_string(ctx, -2, "lc");
507
508 rc = duk_pcall(ctx, 2 /*nargs*/); /* [ stash func callback ] -> [ stash result ] */
509 if (rc != 0) {
510 linenoiseAddCompletion(lc, duk_safe_to_string(ctx, -1));
511 }
512 duk_pop_2(ctx);
513 }
514 #endif /* DUK_CMDLINE_LINENOISE_COMPLETION */
515
516 /*
517 * Execute from file handle etc
518 */
519
handle_fh(duk_context * ctx,FILE * f,const char * filename,const char * bytecode_filename)520 static int handle_fh(duk_context *ctx, FILE *f, const char *filename, const char *bytecode_filename) {
521 char *buf = NULL;
522 size_t bufsz;
523 size_t bufoff;
524 size_t got;
525 int rc;
526 int retval = -1;
527
528 buf = (char *) malloc(1024);
529 if (!buf) {
530 goto error;
531 }
532 bufsz = 1024;
533 bufoff = 0;
534
535 /* Read until EOF, avoid fseek/stat because it won't work with stdin. */
536 for (;;) {
537 size_t avail;
538
539 avail = bufsz - bufoff;
540 if (avail < 1024) {
541 size_t newsz;
542 char *buf_new;
543 #if 0
544 fprintf(stderr, "resizing read buffer: %ld -> %ld\n", (long) bufsz, (long) (bufsz * 2));
545 #endif
546 newsz = bufsz + (bufsz >> 2) + 1024; /* +25% and some extra */
547 if (newsz < bufsz) {
548 goto error;
549 }
550 buf_new = (char *) realloc(buf, newsz);
551 if (!buf_new) {
552 goto error;
553 }
554 buf = buf_new;
555 bufsz = newsz;
556 }
557
558 avail = bufsz - bufoff;
559 #if 0
560 fprintf(stderr, "reading input: buf=%p bufsz=%ld bufoff=%ld avail=%ld\n",
561 (void *) buf, (long) bufsz, (long) bufoff, (long) avail);
562 #endif
563
564 got = fread((void *) (buf + bufoff), (size_t) 1, avail, f);
565 #if 0
566 fprintf(stderr, "got=%ld\n", (long) got);
567 #endif
568 if (got == 0) {
569 break;
570 }
571 bufoff += got;
572
573 /* Emscripten specific: stdin EOF doesn't work as expected.
574 * Instead, when 'emduk' is executed using Node.js, a file
575 * piped to stdin repeats (!). Detect that repeat and cut off
576 * the stdin read. Ensure the loop repeats enough times to
577 * avoid detecting spurious loops.
578 *
579 * This only seems to work for inputs up to 256 bytes long.
580 */
581 #if defined(EMSCRIPTEN)
582 if (bufoff >= 16384) {
583 size_t i, j, nloops;
584 int looped = 0;
585
586 for (i = 16; i < bufoff / 8; i++) {
587 int ok;
588
589 nloops = bufoff / i;
590 ok = 1;
591 for (j = 1; j < nloops; j++) {
592 if (memcmp((void *) buf, (void *) (buf + i * j), i) != 0) {
593 ok = 0;
594 break;
595 }
596 }
597 if (ok) {
598 fprintf(stderr, "emscripten workaround: detect looping at index %ld, verified with %ld loops\n", (long) i, (long) (nloops - 1));
599 bufoff = i;
600 looped = 1;
601 break;
602 }
603 }
604
605 if (looped) {
606 break;
607 }
608 }
609 #endif
610 }
611
612 duk_push_string(ctx, bytecode_filename);
613 duk_push_pointer(ctx, (void *) buf);
614 duk_push_uint(ctx, (duk_uint_t) bufoff);
615 duk_push_string(ctx, filename);
616
617 interactive_mode = 0; /* global */
618
619 rc = duk_safe_call(ctx, wrapped_compile_execute, NULL /*udata*/, 4 /*nargs*/, 1 /*nret*/);
620
621 #if defined(DUK_CMDLINE_LOWMEM)
622 lowmem_clear_exec_timeout();
623 #endif
624
625 free(buf);
626 buf = NULL;
627
628 if (rc != DUK_EXEC_SUCCESS) {
629 print_pop_error(ctx, stderr);
630 goto error;
631 } else {
632 duk_pop(ctx);
633 retval = 0;
634 }
635 /* fall thru */
636
637 cleanup:
638 if (buf) {
639 free(buf);
640 buf = NULL;
641 }
642 return retval;
643
644 error:
645 fprintf(stderr, "error in executing file %s\n", filename);
646 fflush(stderr);
647 goto cleanup;
648 }
649
handle_file(duk_context * ctx,const char * filename,const char * bytecode_filename)650 static int handle_file(duk_context *ctx, const char *filename, const char *bytecode_filename) {
651 FILE *f = NULL;
652 int retval;
653 char fnbuf[256];
654
655 /* Example of sending an application specific debugger notification. */
656 duk_push_string(ctx, "DebuggerHandleFile");
657 duk_push_string(ctx, filename);
658 duk_debugger_notify(ctx, 2);
659
660 #if defined(EMSCRIPTEN)
661 if (filename[0] == '/') {
662 snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
663 } else {
664 snprintf(fnbuf, sizeof(fnbuf), "/working/%s", filename);
665 }
666 #else
667 snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
668 #endif
669 fnbuf[sizeof(fnbuf) - 1] = (char) 0;
670
671 f = fopen(fnbuf, "rb");
672 if (!f) {
673 fprintf(stderr, "failed to open source file: %s\n", filename);
674 fflush(stderr);
675 goto error;
676 }
677
678 retval = handle_fh(ctx, f, filename, bytecode_filename);
679
680 fclose(f);
681 return retval;
682
683 error:
684 return -1;
685 }
686
handle_eval(duk_context * ctx,char * code)687 static int handle_eval(duk_context *ctx, char *code) {
688 int rc;
689 int retval = -1;
690
691 duk_push_pointer(ctx, (void *) code);
692 duk_push_uint(ctx, (duk_uint_t) strlen(code));
693 duk_push_string(ctx, "eval");
694
695 interactive_mode = 0; /* global */
696
697 rc = duk_safe_call(ctx, wrapped_compile_execute, NULL /*udata*/, 3 /*nargs*/, 1 /*nret*/);
698
699 #if defined(DUK_CMDLINE_LOWMEM)
700 lowmem_clear_exec_timeout();
701 #endif
702
703 if (rc != DUK_EXEC_SUCCESS) {
704 print_pop_error(ctx, stderr);
705 } else {
706 duk_pop(ctx);
707 retval = 0;
708 }
709
710 return retval;
711 }
712
713 #if defined(DUK_CMDLINE_LINENOISE)
handle_interactive(duk_context * ctx)714 static int handle_interactive(duk_context *ctx) {
715 const char *prompt = "duk> ";
716 char *buffer = NULL;
717 int retval = 0;
718 int rc;
719
720 linenoiseSetMultiLine(1);
721 linenoiseHistorySetMaxLen(64);
722 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
723 if (!no_auto_complete) {
724 linenoiseSetCompletionCallback(linenoise_completion);
725 linenoiseSetHintsCallback(linenoise_hints);
726 linenoiseSetFreeHintsCallback(linenoise_freehints);
727 duk_push_global_stash(ctx);
728 duk_eval_string(ctx, linenoise_completion_script);
729 duk_put_prop_string(ctx, -2, "linenoiseCompletion");
730 duk_eval_string(ctx, linenoise_hints_script);
731 duk_put_prop_string(ctx, -2, "linenoiseHints");
732 duk_pop(ctx);
733 }
734 #endif
735
736 for (;;) {
737 if (buffer) {
738 linenoiseFree(buffer);
739 buffer = NULL;
740 }
741
742 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
743 completion_ctx = ctx;
744 #endif
745 buffer = linenoise(prompt);
746 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
747 completion_ctx = NULL;
748 #endif
749
750 if (!buffer) {
751 break;
752 }
753
754 if (buffer && buffer[0] != (char) 0) {
755 linenoiseHistoryAdd(buffer);
756 }
757
758 duk_push_pointer(ctx, (void *) buffer);
759 duk_push_uint(ctx, (duk_uint_t) strlen(buffer));
760 duk_push_string(ctx, "input");
761
762 interactive_mode = 1; /* global */
763
764 rc = duk_safe_call(ctx, wrapped_compile_execute, NULL /*udata*/, 3 /*nargs*/, 1 /*nret*/);
765
766 #if defined(DUK_CMDLINE_LOWMEM)
767 lowmem_clear_exec_timeout();
768 #endif
769
770 if (buffer) {
771 linenoiseFree(buffer);
772 buffer = NULL;
773 }
774
775 if (rc != DUK_EXEC_SUCCESS) {
776 /* in interactive mode, write to stdout */
777 print_pop_error(ctx, stdout);
778 retval = -1; /* an error 'taints' the execution */
779 } else {
780 duk_pop(ctx);
781 }
782 }
783
784 if (buffer) {
785 linenoiseFree(buffer);
786 buffer = NULL;
787 }
788
789 return retval;
790 }
791 #else /* DUK_CMDLINE_LINENOISE */
handle_interactive(duk_context * ctx)792 static int handle_interactive(duk_context *ctx) {
793 const char *prompt = "duk> ";
794 size_t bufsize = 0;
795 char *buffer = NULL;
796 int retval = 0;
797 int rc;
798 int got_eof = 0;
799
800 while (!got_eof) {
801 size_t idx = 0;
802
803 fwrite(prompt, 1, strlen(prompt), stdout);
804 fflush(stdout);
805
806 for (;;) {
807 int c;
808
809 if (idx >= bufsize) {
810 size_t newsize = bufsize + (bufsize >> 2) + 1024; /* +25% and some extra */
811 char *newptr;
812
813 if (newsize < bufsize) {
814 goto fail_realloc;
815 }
816 newptr = (char *) realloc(buffer, newsize);
817 if (!newptr) {
818 goto fail_realloc;
819 }
820 buffer = newptr;
821 bufsize = newsize;
822 }
823
824 c = fgetc(stdin);
825 if (c == EOF) {
826 got_eof = 1;
827 break;
828 } else if (c == '\n') {
829 break;
830 } else {
831 buffer[idx++] = (char) c;
832 }
833 }
834
835 duk_push_pointer(ctx, (void *) buffer);
836 duk_push_uint(ctx, (duk_uint_t) idx);
837 duk_push_string(ctx, "input");
838
839 interactive_mode = 1; /* global */
840
841 rc = duk_safe_call(ctx, wrapped_compile_execute, NULL /*udata*/, 3 /*nargs*/, 1 /*nret*/);
842
843 #if defined(DUK_CMDLINE_LOWMEM)
844 lowmem_clear_exec_timeout();
845 #endif
846
847 if (rc != DUK_EXEC_SUCCESS) {
848 /* in interactive mode, write to stdout */
849 print_pop_error(ctx, stdout);
850 retval = -1; /* an error 'taints' the execution */
851 } else {
852 duk_pop(ctx);
853 }
854 }
855
856 done:
857 if (buffer) {
858 free(buffer);
859 buffer = NULL;
860 }
861
862 return retval;
863
864 fail_realloc:
865 fprintf(stderr, "failed to extend line buffer\n");
866 fflush(stderr);
867 retval = -1;
868 goto done;
869 }
870 #endif /* DUK_CMDLINE_LINENOISE */
871
872 /*
873 * Simple file read/write bindings
874 */
875
876 #if defined(DUK_CMDLINE_FILEIO)
fileio_read_file(duk_context * ctx)877 static duk_ret_t fileio_read_file(duk_context *ctx) {
878 const char *fn;
879 char *buf;
880 size_t len;
881 size_t off;
882 int rc;
883 FILE *f;
884
885 fn = duk_require_string(ctx, 0);
886 f = fopen(fn, "rb");
887 if (!f) {
888 (void) duk_type_error(ctx, "cannot open file %s for reading, errno %ld: %s",
889 fn, (long) errno, strerror(errno));
890 }
891
892 rc = fseek(f, 0, SEEK_END);
893 if (rc < 0) {
894 (void) fclose(f);
895 (void) duk_type_error(ctx, "fseek() failed for %s, errno %ld: %s",
896 fn, (long) errno, strerror(errno));
897 }
898 len = (size_t) ftell(f);
899 rc = fseek(f, 0, SEEK_SET);
900 if (rc < 0) {
901 (void) fclose(f);
902 (void) duk_type_error(ctx, "fseek() failed for %s, errno %ld: %s",
903 fn, (long) errno, strerror(errno));
904 }
905
906 buf = (char *) duk_push_fixed_buffer(ctx, (duk_size_t) len);
907 for (off = 0; off < len;) {
908 size_t got;
909 got = fread((void *) (buf + off), 1, len - off, f);
910 if (ferror(f)) {
911 (void) fclose(f);
912 (void) duk_type_error(ctx, "error while reading %s", fn);
913 }
914 if (got == 0) {
915 if (feof(f)) {
916 break;
917 } else {
918 (void) fclose(f);
919 (void) duk_type_error(ctx, "error while reading %s", fn);
920 }
921 }
922 off += got;
923 }
924
925 if (f) {
926 (void) fclose(f);
927 }
928
929 return 1;
930 }
931
fileio_write_file(duk_context * ctx)932 static duk_ret_t fileio_write_file(duk_context *ctx) {
933 const char *fn;
934 const char *buf;
935 size_t len;
936 size_t off;
937 FILE *f;
938
939 fn = duk_require_string(ctx, 0);
940 f = fopen(fn, "wb");
941 if (!f) {
942 (void) duk_type_error(ctx, "cannot open file %s for writing, errno %ld: %s",
943 fn, (long) errno, strerror(errno));
944 }
945
946 len = 0;
947 buf = (char *) duk_require_buffer_data(ctx, 1, &len);
948 for (off = 0; off < len;) {
949 size_t got;
950 got = fwrite((const void *) (buf + off), 1, len - off, f);
951 if (ferror(f)) {
952 (void) fclose(f);
953 (void) duk_type_error(ctx, "error while writing %s", fn);
954 }
955 if (got == 0) {
956 (void) fclose(f);
957 (void) duk_type_error(ctx, "error while writing %s", fn);
958 }
959 off += got;
960 }
961
962 if (f) {
963 (void) fclose(f);
964 }
965
966 return 0;
967 }
968 #endif /* DUK_CMDLINE_FILEIO */
969
970 /*
971 * String.fromBufferRaw()
972 */
973
string_frombufferraw(duk_context * ctx)974 static duk_ret_t string_frombufferraw(duk_context *ctx) {
975 duk_buffer_to_string(ctx, 0);
976 return 1;
977 }
978
979 /*
980 * Duktape heap lifecycle
981 */
982
983 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
debugger_request(duk_context * ctx,void * udata,duk_idx_t nvalues)984 static duk_idx_t debugger_request(duk_context *ctx, void *udata, duk_idx_t nvalues) {
985 const char *cmd;
986 int i;
987
988 (void) udata;
989
990 if (nvalues < 1) {
991 duk_push_string(ctx, "missing AppRequest argument(s)");
992 return -1;
993 }
994
995 cmd = duk_get_string(ctx, -nvalues + 0);
996
997 if (cmd && strcmp(cmd, "CommandLine") == 0) {
998 if (!duk_check_stack(ctx, main_argc)) {
999 /* Callback should avoid errors for now, so use
1000 * duk_check_stack() rather than duk_require_stack().
1001 */
1002 duk_push_string(ctx, "failed to extend stack");
1003 return -1;
1004 }
1005 for (i = 0; i < main_argc; i++) {
1006 duk_push_string(ctx, main_argv[i]);
1007 }
1008 return main_argc;
1009 }
1010 duk_push_sprintf(ctx, "command not supported");
1011 return -1;
1012 }
1013
debugger_detached(duk_context * ctx,void * udata)1014 static void debugger_detached(duk_context *ctx, void *udata) {
1015 fprintf(stderr, "Debugger detached, udata: %p\n", (void *) udata);
1016 fflush(stderr);
1017
1018 /* Ensure socket is closed even when detach is initiated by Duktape
1019 * rather than debug client.
1020 */
1021 duk_trans_socket_finish();
1022
1023 if (debugger_reattach) {
1024 /* For automatic reattach testing. */
1025 duk_trans_socket_init();
1026 duk_trans_socket_waitconn();
1027 fprintf(stderr, "Debugger reconnected, call duk_debugger_attach()\n");
1028 fflush(stderr);
1029 #if 0
1030 /* This is not necessary but should be harmless. */
1031 duk_debugger_detach(ctx);
1032 #endif
1033 duk_debugger_attach(ctx,
1034 duk_trans_socket_read_cb,
1035 duk_trans_socket_write_cb,
1036 duk_trans_socket_peek_cb,
1037 duk_trans_socket_read_flush_cb,
1038 duk_trans_socket_write_flush_cb,
1039 debugger_request,
1040 debugger_detached,
1041 NULL);
1042 }
1043 }
1044 #endif
1045
1046 #define ALLOC_DEFAULT 0
1047 #define ALLOC_LOGGING 1
1048 #define ALLOC_TORTURE 2
1049 #define ALLOC_HYBRID 3
1050 #define ALLOC_LOWMEM 4
1051
create_duktape_heap(int alloc_provider,int debugger,int lowmem_log)1052 static duk_context *create_duktape_heap(int alloc_provider, int debugger, int lowmem_log) {
1053 duk_context *ctx;
1054
1055 (void) lowmem_log; /* suppress warning */
1056
1057 ctx = NULL;
1058 if (!ctx && alloc_provider == ALLOC_LOGGING) {
1059 #if defined(DUK_CMDLINE_ALLOC_LOGGING)
1060 ctx = duk_create_heap(duk_alloc_logging,
1061 duk_realloc_logging,
1062 duk_free_logging,
1063 (void *) 0xdeadbeef,
1064 cmdline_fatal_handler);
1065 #else
1066 fprintf(stderr, "Warning: option --alloc-logging ignored, no logging allocator support\n");
1067 fflush(stderr);
1068 #endif
1069 }
1070 if (!ctx && alloc_provider == ALLOC_TORTURE) {
1071 #if defined(DUK_CMDLINE_ALLOC_TORTURE)
1072 ctx = duk_create_heap(duk_alloc_torture,
1073 duk_realloc_torture,
1074 duk_free_torture,
1075 (void *) 0xdeadbeef,
1076 cmdline_fatal_handler);
1077 #else
1078 fprintf(stderr, "Warning: option --alloc-torture ignored, no torture allocator support\n");
1079 fflush(stderr);
1080 #endif
1081 }
1082 if (!ctx && alloc_provider == ALLOC_HYBRID) {
1083 #if defined(DUK_CMDLINE_ALLOC_HYBRID)
1084 void *udata = duk_alloc_hybrid_init();
1085 if (!udata) {
1086 fprintf(stderr, "Failed to init hybrid allocator\n");
1087 fflush(stderr);
1088 } else {
1089 ctx = duk_create_heap(duk_alloc_hybrid,
1090 duk_realloc_hybrid,
1091 duk_free_hybrid,
1092 udata,
1093 cmdline_fatal_handler);
1094 }
1095 #else
1096 fprintf(stderr, "Warning: option --alloc-hybrid ignored, no hybrid allocator support\n");
1097 fflush(stderr);
1098 #endif
1099 }
1100 if (!ctx && alloc_provider == ALLOC_LOWMEM) {
1101 #if defined(DUK_CMDLINE_LOWMEM)
1102 lowmem_init();
1103
1104 ctx = duk_create_heap(
1105 lowmem_log ? lowmem_alloc_wrapped : duk_alloc_pool,
1106 lowmem_log ? lowmem_realloc_wrapped : duk_realloc_pool,
1107 lowmem_log ? lowmem_free_wrapped : duk_free_pool,
1108 (void *) lowmem_pool_ptr,
1109 cmdline_fatal_handler);
1110 #else
1111 fprintf(stderr, "Warning: option --alloc-ajsheap ignored, no ajsheap allocator support\n");
1112 fflush(stderr);
1113 #endif
1114 }
1115 if (!ctx && alloc_provider == ALLOC_DEFAULT) {
1116 ctx = duk_create_heap(NULL, NULL, NULL, NULL, cmdline_fatal_handler);
1117 }
1118
1119 if (!ctx) {
1120 fprintf(stderr, "Failed to create Duktape heap\n");
1121 fflush(stderr);
1122 exit(1);
1123 }
1124
1125 #if defined(DUK_CMDLINE_LOWMEM)
1126 if (alloc_provider == ALLOC_LOWMEM) {
1127 fprintf(stderr, "*** pool dump after heap creation ***\n");
1128 lowmem_dump();
1129 }
1130 #endif
1131
1132 #if defined(DUK_CMDLINE_LOWMEM)
1133 if (alloc_provider == ALLOC_LOWMEM) {
1134 lowmem_register(ctx);
1135 }
1136 #endif
1137
1138 /* Register print() and alert() (removed in Duktape 2.x). */
1139 #if defined(DUK_CMDLINE_PRINTALERT_SUPPORT)
1140 duk_print_alert_init(ctx, 0 /*flags*/);
1141 #endif
1142
1143 /* Register String.fromBufferRaw() which does a 1:1 buffer-to-string
1144 * coercion needed by testcases. String.fromBufferRaw() is -not- a
1145 * default built-in! For stripped builds the 'String' built-in
1146 * doesn't exist and we create it here; for ROM builds it may be
1147 * present but unwritable (which is ignored).
1148 */
1149 duk_eval_string(ctx,
1150 "(function(v){"
1151 "if (typeof String === 'undefined') { String = {}; }"
1152 "Object.defineProperty(String, 'fromBufferRaw', {value:v, configurable:true});"
1153 "})");
1154 duk_push_c_function(ctx, string_frombufferraw, 1 /*nargs*/);
1155 (void) duk_pcall(ctx, 1);
1156 duk_pop(ctx);
1157
1158 /* Register console object. */
1159 #if defined(DUK_CMDLINE_CONSOLE_SUPPORT)
1160 duk_console_init(ctx, DUK_CONSOLE_FLUSH /*flags*/);
1161 #endif
1162
1163 /* Register Duktape.Logger (removed in Duktape 2.x). */
1164 #if defined(DUK_CMDLINE_LOGGING_SUPPORT)
1165 duk_logging_init(ctx, 0 /*flags*/);
1166 #endif
1167
1168 /* Register require() (removed in Duktape 2.x). */
1169 #if defined(DUK_CMDLINE_MODULE_SUPPORT)
1170 duk_module_duktape_init(ctx);
1171 #endif
1172
1173 /* Trivial readFile/writeFile bindings for testing. */
1174 #if defined(DUK_CMDLINE_FILEIO)
1175 duk_push_c_function(ctx, fileio_read_file, 1 /*nargs*/);
1176 duk_put_global_string(ctx, "readFile");
1177 duk_push_c_function(ctx, fileio_write_file, 2 /*nargs*/);
1178 duk_put_global_string(ctx, "writeFile");
1179 #endif
1180
1181 /* Stash a formatting function for evaluation results. */
1182 duk_push_global_stash(ctx);
1183 duk_eval_string(ctx,
1184 "(function (E) {"
1185 "return function format(v){"
1186 "try{"
1187 "return E('jx',v);"
1188 "}catch(e){"
1189 "return ''+v;"
1190 "}"
1191 "};"
1192 "})(Duktape.enc)");
1193 duk_put_prop_string(ctx, -2, "dukFormat");
1194 duk_pop(ctx);
1195
1196 if (debugger) {
1197 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
1198 fprintf(stderr, "Debugger enabled, create socket and wait for connection\n");
1199 fflush(stderr);
1200 duk_trans_socket_init();
1201 duk_trans_socket_waitconn();
1202 fprintf(stderr, "Debugger connected, call duk_debugger_attach() and then execute requested file(s)/eval\n");
1203 fflush(stderr);
1204 duk_debugger_attach(ctx,
1205 duk_trans_socket_read_cb,
1206 duk_trans_socket_write_cb,
1207 duk_trans_socket_peek_cb,
1208 duk_trans_socket_read_flush_cb,
1209 duk_trans_socket_write_flush_cb,
1210 debugger_request,
1211 debugger_detached,
1212 NULL);
1213 #else
1214 fprintf(stderr, "Warning: option --debugger ignored, no debugger support\n");
1215 fflush(stderr);
1216 #endif
1217 }
1218
1219 #if 0
1220 /* Manual test for duk_debugger_cooperate() */
1221 {
1222 for (i = 0; i < 60; i++) {
1223 printf("cooperate: %d\n", i);
1224 usleep(1000000);
1225 duk_debugger_cooperate(ctx);
1226 }
1227 }
1228 #endif
1229
1230 return ctx;
1231 }
1232
destroy_duktape_heap(duk_context * ctx,int alloc_provider)1233 static void destroy_duktape_heap(duk_context *ctx, int alloc_provider) {
1234 (void) alloc_provider;
1235
1236 #if defined(DUK_CMDLINE_LOWMEM)
1237 if (alloc_provider == ALLOC_LOWMEM) {
1238 fprintf(stderr, "*** pool dump before duk_destroy_heap(), before forced gc ***\n");
1239 lowmem_dump();
1240
1241 duk_gc(ctx, 0);
1242
1243 fprintf(stderr, "*** pool dump before duk_destroy_heap(), after forced gc ***\n");
1244 lowmem_dump();
1245 }
1246 #endif
1247
1248 if (ctx) {
1249 duk_destroy_heap(ctx);
1250 }
1251
1252 #if defined(DUK_CMDLINE_LOWMEM)
1253 if (alloc_provider == ALLOC_LOWMEM) {
1254 fprintf(stderr, "*** pool dump after duk_destroy_heap() (should have zero allocs) ***\n");
1255 lowmem_dump();
1256 }
1257 lowmem_free();
1258 #endif
1259 }
1260
1261 /*
1262 * Main
1263 */
1264
main(int argc,char * argv[])1265 int main(int argc, char *argv[]) {
1266 duk_context *ctx = NULL;
1267 int retval = 0;
1268 int have_files = 0;
1269 int have_eval = 0;
1270 int interactive = 0;
1271 int memlimit_high = 1;
1272 int alloc_provider = ALLOC_DEFAULT;
1273 int lowmem_log = 0;
1274 int debugger = 0;
1275 int recreate_heap = 0;
1276 int no_heap_destroy = 0;
1277 int verbose = 0;
1278 int run_stdin = 0;
1279 const char *compile_filename = NULL;
1280 int i;
1281
1282 main_argc = argc;
1283 main_argv = (char **) argv;
1284
1285 #if defined(EMSCRIPTEN)
1286 /* Try to use NODEFS to provide access to local files. Mount the
1287 * CWD as /working, and then prepend "/working/" to relative native
1288 * paths in file calls to get something that works reasonably for
1289 * relative paths. Emscripten doesn't support replacing virtual
1290 * "/" with host "/" (the default MEMFS at "/" can't be unmounted)
1291 * but we can mount "/tmp" as host "/tmp" to allow testcase runs.
1292 *
1293 * https://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html#filesystem-api-nodefs
1294 * https://github.com/kripken/emscripten/blob/master/tests/fs/test_nodefs_rw.c
1295 */
1296 EM_ASM(
1297 /* At the moment it's not possible to replace the default MEMFS mounted at '/':
1298 * https://github.com/kripken/emscripten/issues/2040
1299 * https://github.com/kripken/emscripten/blob/incoming/src/library_fs.js#L1341-L1358
1300 */
1301 /*
1302 try {
1303 FS.unmount("/");
1304 } catch (e) {
1305 console.log("Failed to unmount default '/' MEMFS mount: " + e);
1306 }
1307 */
1308 try {
1309 FS.mkdir("/working");
1310 FS.mount(NODEFS, { root: "." }, "/working");
1311 } catch (e) {
1312 console.log("Failed to mount NODEFS /working: " + e);
1313 }
1314 /* A virtual '/tmp' exists by default:
1315 * https://gist.github.com/evanw/e6be28094f34451bd5bd#file-temp-js-L3806-L3809
1316 */
1317 /*
1318 try {
1319 FS.mkdir("/tmp");
1320 } catch (e) {
1321 console.log("Failed to create virtual /tmp: " + e);
1322 }
1323 */
1324 try {
1325 FS.mount(NODEFS, { root: "/tmp" }, "/tmp");
1326 } catch (e) {
1327 console.log("Failed to mount NODEFS /tmp: " + e);
1328 }
1329 );
1330 #endif /* EMSCRIPTEN */
1331
1332 #if defined(DUK_CMDLINE_LOWMEM)
1333 alloc_provider = ALLOC_LOWMEM;
1334 #endif
1335 (void) lowmem_log;
1336
1337 /*
1338 * Signal handling setup
1339 */
1340
1341 #if defined(DUK_CMDLINE_SIGNAL)
1342 set_sigint_handler();
1343
1344 /* This is useful at the global level; libraries should avoid SIGPIPE though */
1345 /*signal(SIGPIPE, SIG_IGN);*/
1346 #endif
1347
1348 /*
1349 * Parse options
1350 */
1351
1352 for (i = 1; i < argc; i++) {
1353 char *arg = argv[i];
1354 if (!arg) {
1355 goto usage;
1356 }
1357 if (strcmp(arg, "--restrict-memory") == 0) {
1358 memlimit_high = 0;
1359 } else if (strcmp(arg, "-i") == 0) {
1360 interactive = 1;
1361 } else if (strcmp(arg, "-b") == 0) {
1362 allow_bytecode = 1;
1363 } else if (strcmp(arg, "-c") == 0) {
1364 if (i == argc - 1) {
1365 goto usage;
1366 }
1367 i++;
1368 compile_filename = argv[i];
1369 } else if (strcmp(arg, "-e") == 0) {
1370 have_eval = 1;
1371 if (i == argc - 1) {
1372 goto usage;
1373 }
1374 i++; /* skip code */
1375 } else if (strcmp(arg, "--alloc-default") == 0) {
1376 alloc_provider = ALLOC_DEFAULT;
1377 } else if (strcmp(arg, "--alloc-logging") == 0) {
1378 alloc_provider = ALLOC_LOGGING;
1379 } else if (strcmp(arg, "--alloc-torture") == 0) {
1380 alloc_provider = ALLOC_TORTURE;
1381 } else if (strcmp(arg, "--alloc-hybrid") == 0) {
1382 alloc_provider = ALLOC_HYBRID;
1383 } else if (strcmp(arg, "--alloc-lowmem") == 0) {
1384 alloc_provider = ALLOC_LOWMEM;
1385 } else if (strcmp(arg, "--lowmem-log") == 0) {
1386 lowmem_log = 1;
1387 } else if (strcmp(arg, "--debugger") == 0) {
1388 debugger = 1;
1389 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
1390 } else if (strcmp(arg, "--reattach") == 0) {
1391 debugger_reattach = 1;
1392 #endif
1393 } else if (strcmp(arg, "--recreate-heap") == 0) {
1394 recreate_heap = 1;
1395 } else if (strcmp(arg, "--no-heap-destroy") == 0) {
1396 no_heap_destroy = 1;
1397 } else if (strcmp(arg, "--no-auto-complete") == 0) {
1398 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
1399 no_auto_complete = 1;
1400 #endif
1401 } else if (strcmp(arg, "--verbose") == 0) {
1402 verbose = 1;
1403 } else if (strcmp(arg, "--run-stdin") == 0) {
1404 run_stdin = 1;
1405 } else if (strlen(arg) >= 1 && arg[0] == '-') {
1406 goto usage;
1407 } else {
1408 have_files = 1;
1409 }
1410 }
1411 if (!have_files && !have_eval && !run_stdin) {
1412 interactive = 1;
1413 }
1414
1415 /*
1416 * Memory limit
1417 */
1418
1419 #if defined(DUK_CMDLINE_RLIMIT)
1420 set_resource_limits(memlimit_high ? MEM_LIMIT_HIGH : MEM_LIMIT_NORMAL);
1421 #else
1422 if (memlimit_high == 0) {
1423 fprintf(stderr, "Warning: option --restrict-memory ignored, no rlimit support\n");
1424 fflush(stderr);
1425 }
1426 #endif
1427
1428 /*
1429 * Create heap
1430 */
1431
1432 ctx = create_duktape_heap(alloc_provider, debugger, lowmem_log);
1433
1434 /*
1435 * Execute any argument file(s)
1436 */
1437
1438 for (i = 1; i < argc; i++) {
1439 char *arg = argv[i];
1440 if (!arg) {
1441 continue;
1442 } else if (strlen(arg) == 2 && strcmp(arg, "-e") == 0) {
1443 /* Here we know the eval arg exists but check anyway */
1444 if (i == argc - 1) {
1445 retval = 1;
1446 goto cleanup;
1447 }
1448 if (handle_eval(ctx, argv[i + 1]) != 0) {
1449 retval = 1;
1450 goto cleanup;
1451 }
1452 i++; /* skip code */
1453 continue;
1454 } else if (strlen(arg) == 2 && strcmp(arg, "-c") == 0) {
1455 i++; /* skip filename */
1456 continue;
1457 } else if (strlen(arg) >= 1 && arg[0] == '-') {
1458 continue;
1459 }
1460
1461 if (verbose) {
1462 fprintf(stderr, "*** Executing file: %s\n", arg);
1463 fflush(stderr);
1464 }
1465
1466 if (handle_file(ctx, arg, compile_filename) != 0) {
1467 retval = 1;
1468 goto cleanup;
1469 }
1470
1471 if (recreate_heap) {
1472 if (verbose) {
1473 fprintf(stderr, "*** Recreating heap...\n");
1474 fflush(stderr);
1475 }
1476
1477 destroy_duktape_heap(ctx, alloc_provider);
1478 ctx = create_duktape_heap(alloc_provider, debugger, lowmem_log);
1479 }
1480 }
1481
1482 if (run_stdin) {
1483 /* Running stdin like a full file (reading all lines before
1484 * compiling) is useful with emduk:
1485 * cat test.js | ./emduk --run-stdin
1486 */
1487 if (handle_fh(ctx, stdin, "stdin", compile_filename) != 0) {
1488 retval = 1;
1489 goto cleanup;
1490 }
1491
1492 if (recreate_heap) {
1493 if (verbose) {
1494 fprintf(stderr, "*** Recreating heap...\n");
1495 fflush(stderr);
1496 }
1497
1498 destroy_duktape_heap(ctx, alloc_provider);
1499 ctx = create_duktape_heap(alloc_provider, debugger, lowmem_log);
1500 }
1501 }
1502
1503 /*
1504 * Enter interactive mode if options indicate it
1505 */
1506
1507 if (interactive) {
1508 print_greet_line();
1509 if (handle_interactive(ctx) != 0) {
1510 retval = 1;
1511 goto cleanup;
1512 }
1513 }
1514
1515 /*
1516 * Cleanup and exit
1517 */
1518
1519 cleanup:
1520 if (interactive) {
1521 fprintf(stderr, "Cleaning up...\n");
1522 fflush(stderr);
1523 }
1524
1525 if (ctx && no_heap_destroy) {
1526 duk_gc(ctx, 0);
1527 }
1528 if (ctx && !no_heap_destroy) {
1529 destroy_duktape_heap(ctx, alloc_provider);
1530 }
1531 ctx = NULL;
1532
1533 return retval;
1534
1535 /*
1536 * Usage
1537 */
1538
1539 usage:
1540 fprintf(stderr, "Usage: duk [options] [<filenames>]\n"
1541 "\n"
1542 " -i enter interactive mode after executing argument file(s) / eval code\n"
1543 " -e CODE evaluate code\n"
1544 " -c FILE compile into bytecode and write to FILE (use with only one file argument)\n"
1545 " -b allow bytecode input files (memory unsafe for invalid bytecode)\n"
1546 " --run-stdin treat stdin like a file, i.e. compile full input (not line by line)\n"
1547 " --verbose verbose messages to stderr\n"
1548 " --restrict-memory use lower memory limit (used by test runner)\n"
1549 " --alloc-default use Duktape default allocator\n"
1550 #if defined(DUK_CMDLINE_ALLOC_LOGGING)
1551 " --alloc-logging use logging allocator, write alloc log to /tmp/duk-alloc-log.txt\n"
1552 #endif
1553 #if defined(DUK_CMDLINE_ALLOC_TORTURE)
1554 " --alloc-torture use torture allocator\n"
1555 #endif
1556 #if defined(DUK_CMDLINE_ALLOC_HYBRID)
1557 " --alloc-hybrid use hybrid allocator\n"
1558 #endif
1559 #if defined(DUK_CMDLINE_LOWMEM)
1560 " --alloc-lowmem use pooled allocator (enabled by default for duk-low)\n"
1561 " --lowmem-log write alloc log to /tmp/lowmem-alloc-log.txt\n"
1562 #endif
1563 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
1564 " --debugger start example debugger\n"
1565 " --reattach automatically reattach debugger on detach\n"
1566 #endif
1567 " --recreate-heap recreate heap after every file\n"
1568 " --no-heap-destroy force GC, but don't destroy heap at end (leak testing)\n"
1569 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
1570 " --no-auto-complete disable linenoise auto completion\n"
1571 #else
1572 " --no-auto-complete disable linenoise auto completion [ignored, not supported]\n"
1573 #endif
1574 "\n"
1575 "If <filename> is omitted, interactive mode is started automatically.\n"
1576 "\n"
1577 "Input files can be either ECMAScript source files or bytecode files\n"
1578 "(if -b is given). Bytecode files are not validated prior to loading,\n"
1579 "so that incompatible or crafted files can cause memory unsafe behavior.\n"
1580 "See discussion in\n"
1581 "https://github.com/svaarala/duktape/blob/master/doc/bytecode.rst#memory-safety-and-bytecode-validation.\n");
1582 fflush(stderr);
1583 exit(1);
1584 }
1585
1586 /* Example of how a native stack check can be implemented in a platform
1587 * specific manner for DUK_USE_NATIVE_STACK_CHECK(). This example is for
1588 * (Linux) pthreads, and rejects further native recursion if less than
1589 * 16kB stack is left (conservative).
1590 */
1591 #if defined(DUK_CMDLINE_PTHREAD_STACK_CHECK)
duk_cmdline_stack_check(void)1592 int duk_cmdline_stack_check(void) {
1593 pthread_attr_t attr;
1594 void *stackaddr;
1595 size_t stacksize;
1596 char *ptr;
1597 char *ptr_base;
1598 ptrdiff_t remain;
1599
1600 (void) pthread_getattr_np(pthread_self(), &attr);
1601 (void) pthread_attr_getstack(&attr, &stackaddr, &stacksize);
1602 ptr = (char *) &stacksize; /* Rough estimate of current stack pointer. */
1603 ptr_base = (char *) stackaddr;
1604 remain = ptr - ptr_base;
1605
1606 /* HIGH ADDR ----- stackaddr + stacksize
1607 * |
1608 * | stack growth direction
1609 * v -- ptr, approximate used size
1610 *
1611 * LOW ADDR ----- ptr_base, end of stack (lowest address)
1612 */
1613
1614 #if 0
1615 fprintf(stderr, "STACK CHECK: stackaddr=%p, stacksize=%ld, ptr=%p, remain=%ld\n",
1616 stackaddr, (long) stacksize, (void *) ptr, (long) remain);
1617 fflush(stderr);
1618 #endif
1619 if (remain < 16384) {
1620 return 1;
1621 }
1622 return 0; /* 0: no error, != 0: throw RangeError */
1623 }
1624 #else
duk_cmdline_stack_check(void)1625 int duk_cmdline_stack_check(void) {
1626 return 0;
1627 }
1628 #endif
1629