2 * Command line execution tool. Useful for test cases and manual testing.
4 * To enable linenoise and other fancy stuff, compile with -DDUK_CMDLINE_FANCY.
5 * It is not the default to maximize portability. You can also compile in
6 * support for example allocators, grep for DUK_CMDLINE_*.
9 /* Helper define to enable a feature set; can also use separate defines. */
10 #if defined(DUK_CMDLINE_FANCY)
11 #define DUK_CMDLINE_LINENOISE
12 #define DUK_CMDLINE_LINENOISE_COMPLETION
13 #define DUK_CMDLINE_RLIMIT
14 #define DUK_CMDLINE_SIGNAL
17 #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
18 defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
19 /* Suppress warnings about plain fopen() etc. */
20 #define _CRT_SECURE_NO_WARNINGS
21 #if defined(_MSC_VER) && (_MSC_VER < 1900)
22 /* Workaround for snprintf() missing in older MSVC versions.
23 * Note that _snprintf() may not NUL terminate the string, but
24 * this difference does not matter here as a NUL terminator is
25 * always explicitly added.
27 #define snprintf _snprintf
31 #define GREET_CODE(variant) \
32 "print('((o) Duktape" variant " ' + " \
33 "Math.floor(Duktape.version / 10000) + '.' + " \
34 "Math.floor(Duktape.version / 100) % 100 + '.' + " \
35 "Duktape.version % 100" \
36 ", '(" DUK_GIT_DESCRIBE ")');"
41 #if defined(DUK_CMDLINE_SIGNAL)
44 #if defined(DUK_CMDLINE_RLIMIT)
45 #include <sys/resource.h>
47 #if defined(DUK_CMDLINE_LINENOISE)
48 #include "linenoise.h"
50 #if defined(DUK_CMDLINE_FILEIO)
53 #if defined(EMSCRIPTEN)
54 #include <emscripten.h>
56 #if defined(DUK_CMDLINE_ALLOC_LOGGING)
57 #include "duk_alloc_logging.h"
59 #if defined(DUK_CMDLINE_ALLOC_TORTURE)
60 #include "duk_alloc_torture.h"
62 #if defined(DUK_CMDLINE_ALLOC_HYBRID)
63 #include "duk_alloc_hybrid.h"
67 #if defined(DUK_CMDLINE_AJSHEAP)
68 /* Defined in duk_cmdline_ajduk.c or alljoyn.js headers. */
69 void ajsheap_init(void);
70 void ajsheap_free(void);
71 void ajsheap_dump(void);
72 void ajsheap_register(duk_context
*ctx
);
73 void ajsheap_start_exec_timeout(void);
74 void ajsheap_clear_exec_timeout(void);
75 void *ajsheap_alloc_wrapped(void *udata
, duk_size_t size
);
76 void *ajsheap_realloc_wrapped(void *udata
, void *ptr
, duk_size_t size
);
77 void ajsheap_free_wrapped(void *udata
, void *ptr
);
78 void *AJS_Alloc(void *udata
, duk_size_t size
);
79 void *AJS_Realloc(void *udata
, void *ptr
, duk_size_t size
);
80 void AJS_Free(void *udata
, void *ptr
);
83 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
84 #include "duk_trans_socket.h"
87 #define MEM_LIMIT_NORMAL (128*1024*1024) /* 128 MB */
88 #define MEM_LIMIT_HIGH (2047*1024*1024) /* ~2 GB */
89 #define LINEBUF_SIZE 65536
91 static int main_argc
= 0;
92 static char **main_argv
= NULL
;
93 static int interactive_mode
= 0;
94 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
95 static int debugger_reattach
= 0;
102 #if defined(DUK_CMDLINE_RLIMIT)
103 static void set_resource_limits(rlim_t mem_limit_value
) {
107 rc
= getrlimit(RLIMIT_AS
, &lim
);
109 fprintf(stderr
, "Warning: cannot read RLIMIT_AS\n");
113 if (lim
.rlim_max
< mem_limit_value
) {
114 fprintf(stderr
, "Warning: rlim_max < mem_limit_value (%d < %d)\n", (int) lim
.rlim_max
, (int) mem_limit_value
);
118 lim
.rlim_cur
= mem_limit_value
;
119 lim
.rlim_max
= mem_limit_value
;
121 rc
= setrlimit(RLIMIT_AS
, &lim
);
123 fprintf(stderr
, "Warning: setrlimit failed\n");
128 fprintf(stderr
, "Set RLIMIT_AS to %d\n", (int) mem_limit_value
);
131 #endif /* DUK_CMDLINE_RLIMIT */
133 #if defined(DUK_CMDLINE_SIGNAL)
134 static void my_sighandler(int x
) {
135 fprintf(stderr
, "Got signal %d\n", x
);
138 static void set_sigint_handler(void) {
139 (void) signal(SIGINT
, my_sighandler
);
140 (void) signal(SIGPIPE
, SIG_IGN
); /* avoid SIGPIPE killing process */
142 #endif /* DUK_CMDLINE_SIGNAL */
144 static int get_stack_raw(duk_context
*ctx
) {
145 if (!duk_is_object(ctx
, -1)) {
148 if (!duk_has_prop_string(ctx
, -1, "stack")) {
151 if (!duk_is_error(ctx
, -1)) {
152 /* Not an Error instance, don't read "stack". */
156 duk_get_prop_string(ctx
, -1, "stack"); /* caller coerces */
161 /* Print error to stderr and pop error. */
162 static void print_pop_error(duk_context
*ctx
, FILE *f
) {
163 /* Print error objects with a stack trace specially.
164 * Note that getting the stack trace may throw an error
165 * so this also needs to be safe call wrapped.
167 (void) duk_safe_call(ctx
, get_stack_raw
, 1 /*nargs*/, 1 /*nrets*/);
168 fprintf(f
, "%s\n", duk_safe_to_string(ctx
, -1));
173 static int wrapped_compile_execute(duk_context
*ctx
) {
174 const char *src_data
;
178 /* XXX: Here it'd be nice to get some stats for the compilation result
179 * when a suitable command line is given (e.g. code size, constant
180 * count, function count. These are available internally but not through
184 /* Use duk_compile_lstring_filename() variant which avoids interning
185 * the source code. This only really matters for low memory environments.
188 /* [ ... bytecode_filename src_data src_len filename ] */
190 src_data
= (const char *) duk_require_pointer(ctx
, -3);
191 src_len
= (duk_size_t
) duk_require_uint(ctx
, -2);
193 if (src_data
!= NULL
&& src_len
>= 2 && src_data
[0] == (char) 0xff) {
195 duk_push_lstring(ctx
, src_data
, src_len
);
196 duk_to_buffer(ctx
, -1, NULL
);
197 duk_load_function(ctx
);
201 duk_compile_lstring_filename(ctx
, comp_flags
, src_data
, src_len
);
204 /* [ ... bytecode_filename src_data src_len function ] */
206 /* Optional bytecode dump. */
207 if (duk_is_string(ctx
, -4)) {
213 const char *filename
;
216 duk_dump_function(ctx
);
217 bc_ptr
= duk_require_buffer(ctx
, -1, &bc_len
);
218 filename
= duk_require_string(ctx
, -5);
219 #if defined(EMSCRIPTEN)
220 if (filename
[0] == '/') {
221 snprintf(fnbuf
, sizeof(fnbuf
), "%s", filename
);
223 snprintf(fnbuf
, sizeof(fnbuf
), "/working/%s", filename
);
226 snprintf(fnbuf
, sizeof(fnbuf
), "%s", filename
);
228 fnbuf
[sizeof(fnbuf
) - 1] = (char) 0;
230 f
= fopen(fnbuf
, "wb");
232 duk_error(ctx
, DUK_ERR_ERROR
, "failed to open bytecode output file");
234 wrote
= fwrite(bc_ptr
, 1, (size_t) bc_len
, f
); /* XXX: handle partial writes */
236 if (wrote
!= bc_len
) {
237 duk_error(ctx
, DUK_ERR_ERROR
, "failed to write all bytecode");
240 return 0; /* duk_safe_call() cleans up */
244 /* Manual test for bytecode dump/load cycle: dump and load before
245 * execution. Enable manually, then run "make qecmatest" for a
246 * reasonably good coverage of different functions and programs.
248 duk_dump_function(ctx
);
249 duk_load_function(ctx
);
252 #if defined(DUK_CMDLINE_AJSHEAP)
253 ajsheap_start_exec_timeout();
256 duk_push_global_object(ctx
); /* 'this' binding */
257 duk_call_method(ctx
, 0);
259 #if defined(DUK_CMDLINE_AJSHEAP)
260 ajsheap_clear_exec_timeout();
263 if (interactive_mode
) {
265 * In interactive mode, write to stdout so output won't
266 * interleave as easily.
268 * NOTE: the ToString() coercion may fail in some cases;
269 * for instance, if you evaluate:
271 * ( {valueOf: function() {return {}},
272 * toString: function() {return {}}});
276 * TypeError: failed to coerce with [[DefaultValue]]
279 * These are handled now by the caller which also has stack
280 * trace printing support. User code can print out errors
281 * safely using duk_safe_to_string().
284 fprintf(stdout
, "= %s\n", duk_to_string(ctx
, -1));
287 /* In non-interactive mode, success results are not written at all.
288 * It is important that the result value is not string coerced,
289 * as the string coercion may cause an error in some cases.
293 return 0; /* duk_safe_call() cleans up */
297 * Minimal Linenoise completion support
300 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
301 static duk_context
*completion_ctx
;
303 static int completion_idpart(unsigned char c
) {
304 /* Very simplified "is identifier part" check. */
305 if ((c
>= (unsigned char) 'a' && c
<= (unsigned char) 'z') ||
306 (c
>= (unsigned char) 'A' && c
<= (unsigned char) 'Z') ||
307 (c
>= (unsigned char) '0' && c
<= (unsigned char) '9') ||
308 c
== (unsigned char) '$' || c
== (unsigned char) '_') {
314 static int completion_digit(unsigned char c
) {
315 return (c
>= (unsigned char) '0' && c
<= (unsigned char) '9');
318 static duk_ret_t
linenoise_completion_lookup(duk_context
*ctx
) {
321 const unsigned char *p
;
322 const unsigned char *p_curr
;
323 const unsigned char *p_end
;
326 linenoiseCompletions
*lc
;
329 orig
= duk_require_string(ctx
, -3);
330 p_curr
= (const unsigned char *) duk_require_lstring(ctx
, -2, &len
);
331 p_end
= p_curr
+ len
;
332 lc
= duk_require_pointer(ctx
, -1);
334 duk_push_global_object(ctx
);
335 idx_obj
= duk_require_top_index(ctx
);
337 while (p_curr
<= p_end
) {
338 /* p_curr == p_end allowed on purpose, to handle 'Math.' for example. */
340 while (p
< p_end
&& p
[0] != (unsigned char) '.') {
343 /* 'p' points to a NUL (p == p_end) or a period. */
344 prefix
= duk_push_lstring(ctx
, (const char *) p_curr
, (duk_size_t
) (p
- p_curr
));
347 fprintf(stderr
, "Completion check: '%s'\n", prefix
);
352 /* 'idx_obj' points to the object matching the last
353 * full component, use [p_curr,p[ as a filter for
357 duk_enum(ctx
, idx_obj
, DUK_ENUM_INCLUDE_NONENUMERABLE
);
358 while (duk_next(ctx
, -1, 0 /*get_value*/)) {
359 key
= duk_get_string(ctx
, -1);
361 fprintf(stderr
, "Key: %s\n", key
? key
: "");
365 /* Should never happen, just in case. */
369 /* Ignore array index keys: usually not desirable, and would
370 * also require ['0'] quoting.
372 if (completion_digit(key
[0])) {
376 /* XXX: There's no key quoting now, it would require replacing the
377 * last component with a ['foo\nbar'] style lookup when appropriate.
380 if (strlen(prefix
) == 0) {
381 /* Partial ends in a period, e.g. 'Math.' -> complete all Math properties. */
382 duk_push_string(ctx
, orig
); /* original, e.g. 'Math.' */
383 duk_push_string(ctx
, key
);
385 linenoiseAddCompletion(lc
, duk_require_string(ctx
, -1));
387 } else if (prefix
&& strcmp(key
, prefix
) == 0) {
388 /* Full completion, add a period, e.g. input 'Math' -> 'Math.'. */
389 duk_push_string(ctx
, orig
); /* original, including partial last component */
390 duk_push_string(ctx
, ".");
392 linenoiseAddCompletion(lc
, duk_require_string(ctx
, -1));
394 } else if (prefix
&& strncmp(key
, prefix
, strlen(prefix
)) == 0) {
395 /* Last component is partial, complete. */
396 duk_push_string(ctx
, orig
); /* original, including partial last component */
397 duk_push_string(ctx
, key
+ strlen(prefix
)); /* completion to last component */
399 linenoiseAddCompletion(lc
, duk_require_string(ctx
, -1));
408 if (duk_get_prop(ctx
, idx_obj
)) {
409 duk_to_object(ctx
, -1); /* for properties of plain strings etc */
410 duk_replace(ctx
, idx_obj
);
422 static void linenoise_completion(const char *buf
, linenoiseCompletions
*lc
) {
424 const unsigned char *p_start
;
425 const unsigned char *p_end
;
426 const unsigned char *p
;
432 ctx
= completion_ctx
;
437 p_start
= (const unsigned char *) buf
;
438 p_end
= (const unsigned char *) (buf
+ strlen(buf
));
441 /* Scan backwards for a maximal string which looks like a property
442 * chain (e.g. foo.bar.quux).
445 while (--p
>= p_start
) {
446 if (p
[0] == (unsigned char) '.') {
450 if (!completion_idpart(p
[-1])) {
451 /* Catches e.g. 'foo..bar' -> we want 'bar' only. */
454 } else if (!completion_idpart(p
[0])) {
458 /* 'p' will either be p_start - 1 (ran out of buffer) or point to
459 * the first offending character.
462 if (p
< p_start
|| p
>= p_end
) {
463 return; /* should never happen, but just in case */
466 /* 'p' now points to a string of the form 'foo.bar.quux'. Look up
467 * all the components except the last; treat the last component as
468 * a partial name which is used as a filter for the previous full
469 * component. All lookups are from the global object now.
473 fprintf(stderr
, "Completion starting point: '%s'\n", p
);
477 duk_push_string(ctx
, (const char *) buf
);
478 duk_push_lstring(ctx
, (const char *) p
, (duk_size_t
) (p_end
- p
));
479 duk_push_pointer(ctx
, (void *) lc
);
481 rc
= duk_safe_call(ctx
, linenoise_completion_lookup
, 3 /*nargs*/, 1 /*nrets*/);
482 if (rc
!= DUK_EXEC_SUCCESS
) {
483 fprintf(stderr
, "Completion handling failure: %s\n", duk_safe_to_string(ctx
, -1));
487 #endif /* DUK_CMDLINE_LINENOISE_COMPLETION */
490 * Execute from file handle etc
493 static int handle_fh(duk_context
*ctx
, FILE *f
, const char *filename
, const char *bytecode_filename
) {
501 buf
= (char *) malloc(1024);
508 /* Read until EOF, avoid fseek/stat because it won't work with stdin. */
512 avail
= bufsz
- bufoff
;
517 fprintf(stderr
, "resizing read buffer: %ld -> %ld\n", (long) bufsz
, (long) (bufsz
* 2));
519 newsz
= bufsz
+ (bufsz
>> 2) + 1024; /* +25% and some extra */
520 buf_new
= (char *) realloc(buf
, newsz
);
528 avail
= bufsz
- bufoff
;
530 fprintf(stderr
, "reading input: buf=%p bufsz=%ld bufoff=%ld avail=%ld\n",
531 (void *) buf
, (long) bufsz
, (long) bufoff
, (long) avail
);
534 got
= fread((void *) (buf
+ bufoff
), (size_t) 1, avail
, f
);
536 fprintf(stderr
, "got=%ld\n", (long) got
);
543 /* Emscripten specific: stdin EOF doesn't work as expected.
544 * Instead, when 'emduk' is executed using Node.js, a file
545 * piped to stdin repeats (!). Detect that repeat and cut off
546 * the stdin read. Ensure the loop repeats enough times to
547 * avoid detecting spurious loops.
549 * This only seems to work for inputs up to 256 bytes long.
551 #if defined(EMSCRIPTEN)
552 if (bufoff
>= 16384) {
556 for (i
= 16; i
< bufoff
/ 8; i
++) {
561 for (j
= 1; j
< nloops
; j
++) {
562 if (memcmp((void *) buf
, (void *) (buf
+ i
* j
), i
) != 0) {
568 fprintf(stderr
, "emscripten workaround: detect looping at index %ld, verified with %ld loops\n", (long) i
, (long) (nloops
- 1));
582 duk_push_string(ctx
, bytecode_filename
);
583 duk_push_pointer(ctx
, (void *) buf
);
584 duk_push_uint(ctx
, (duk_uint_t
) bufoff
);
585 duk_push_string(ctx
, filename
);
587 interactive_mode
= 0; /* global */
589 rc
= duk_safe_call(ctx
, wrapped_compile_execute
, 4 /*nargs*/, 1 /*nret*/);
591 #if defined(DUK_CMDLINE_AJSHEAP)
592 ajsheap_clear_exec_timeout();
598 if (rc
!= DUK_EXEC_SUCCESS
) {
599 print_pop_error(ctx
, stderr
);
615 fprintf(stderr
, "error in executing file %s\n", filename
);
620 static int handle_file(duk_context
*ctx
, const char *filename
, const char *bytecode_filename
) {
625 /* Example of sending an application specific debugger notification. */
626 duk_push_string(ctx
, "DebuggerHandleFile");
627 duk_push_string(ctx
, filename
);
628 duk_debugger_notify(ctx
, 2);
630 #if defined(EMSCRIPTEN)
631 if (filename
[0] == '/') {
632 snprintf(fnbuf
, sizeof(fnbuf
), "%s", filename
);
634 snprintf(fnbuf
, sizeof(fnbuf
), "/working/%s", filename
);
637 snprintf(fnbuf
, sizeof(fnbuf
), "%s", filename
);
639 fnbuf
[sizeof(fnbuf
) - 1] = (char) 0;
641 f
= fopen(fnbuf
, "rb");
643 fprintf(stderr
, "failed to open source file: %s\n", filename
);
648 retval
= handle_fh(ctx
, f
, filename
, bytecode_filename
);
657 static int handle_eval(duk_context
*ctx
, char *code
) {
661 duk_push_pointer(ctx
, (void *) code
);
662 duk_push_uint(ctx
, (duk_uint_t
) strlen(code
));
663 duk_push_string(ctx
, "eval");
665 interactive_mode
= 0; /* global */
667 rc
= duk_safe_call(ctx
, wrapped_compile_execute
, 3 /*nargs*/, 1 /*nret*/);
669 #if defined(DUK_CMDLINE_AJSHEAP)
670 ajsheap_clear_exec_timeout();
673 if (rc
!= DUK_EXEC_SUCCESS
) {
674 print_pop_error(ctx
, stderr
);
683 #if defined(DUK_CMDLINE_LINENOISE)
684 static int handle_interactive(duk_context
*ctx
) {
685 const char *prompt
= "duk> ";
690 duk_eval_string(ctx
, GREET_CODE(" [linenoise]"));
693 linenoiseSetMultiLine(1);
694 linenoiseHistorySetMaxLen(64);
695 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
696 linenoiseSetCompletionCallback(linenoise_completion
);
701 linenoiseFree(buffer
);
705 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
706 completion_ctx
= ctx
;
708 buffer
= linenoise(prompt
);
709 #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
710 completion_ctx
= NULL
;
717 if (buffer
&& buffer
[0] != (char) 0) {
718 linenoiseHistoryAdd(buffer
);
721 duk_push_pointer(ctx
, (void *) buffer
);
722 duk_push_uint(ctx
, (duk_uint_t
) strlen(buffer
));
723 duk_push_string(ctx
, "input");
725 interactive_mode
= 1; /* global */
727 rc
= duk_safe_call(ctx
, wrapped_compile_execute
, 3 /*nargs*/, 1 /*nret*/);
729 #if defined(DUK_CMDLINE_AJSHEAP)
730 ajsheap_clear_exec_timeout();
734 linenoiseFree(buffer
);
738 if (rc
!= DUK_EXEC_SUCCESS
) {
739 /* in interactive mode, write to stdout */
740 print_pop_error(ctx
, stdout
);
741 retval
= -1; /* an error 'taints' the execution */
748 linenoiseFree(buffer
);
754 #else /* DUK_CMDLINE_LINENOISE */
755 static int handle_interactive(duk_context
*ctx
) {
756 const char *prompt
= "duk> ";
762 duk_eval_string(ctx
, GREET_CODE(""));
765 buffer
= (char *) malloc(LINEBUF_SIZE
);
767 fprintf(stderr
, "failed to allocated a line buffer\n");
776 fwrite(prompt
, 1, strlen(prompt
), stdout
);
780 int c
= fgetc(stdin
);
784 } else if (c
== '\n') {
786 } else if (idx
>= LINEBUF_SIZE
) {
787 fprintf(stderr
, "line too long\n");
792 buffer
[idx
++] = (char) c
;
796 duk_push_pointer(ctx
, (void *) buffer
);
797 duk_push_uint(ctx
, (duk_uint_t
) idx
);
798 duk_push_string(ctx
, "input");
800 interactive_mode
= 1; /* global */
802 rc
= duk_safe_call(ctx
, wrapped_compile_execute
, 3 /*nargs*/, 1 /*nret*/);
804 #if defined(DUK_CMDLINE_AJSHEAP)
805 ajsheap_clear_exec_timeout();
808 if (rc
!= DUK_EXEC_SUCCESS
) {
809 /* in interactive mode, write to stdout */
810 print_pop_error(ctx
, stdout
);
811 retval
= -1; /* an error 'taints' the execution */
825 #endif /* DUK_CMDLINE_LINENOISE */
828 * Simple file read/write bindings
831 #if defined(DUK_CMDLINE_FILEIO)
832 static duk_ret_t
fileio_read_file(duk_context
*ctx
) {
840 fn
= duk_require_string(ctx
, 0);
843 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "cannot open file %s for reading, errno %ld: %s",
844 fn
, (long) errno
, strerror(errno
));
847 rc
= fseek(f
, 0, SEEK_END
);
850 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "fseek() failed for %s, errno %ld: %s",
851 fn
, (long) errno
, strerror(errno
));
853 len
= (size_t) ftell(f
);
854 rc
= fseek(f
, 0, SEEK_SET
);
857 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "fseek() failed for %s, errno %ld: %s",
858 fn
, (long) errno
, strerror(errno
));
861 buf
= (char *) duk_push_fixed_buffer(ctx
, (duk_size_t
) len
);
862 for (off
= 0; off
< len
;) {
864 got
= fread((void *) (buf
+ off
), 1, len
- off
, f
);
867 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "error while reading %s", fn
);
874 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "error while reading %s", fn
);
887 static duk_ret_t
fileio_write_file(duk_context
*ctx
) {
894 fn
= duk_require_string(ctx
, 0);
897 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "cannot open file %s for writing, errno %ld: %s",
898 fn
, (long) errno
, strerror(errno
));
902 buf
= (char *) duk_to_buffer(ctx
, 1, &len
);
903 for (off
= 0; off
< len
;) {
905 got
= fwrite((const void *) (buf
+ off
), 1, len
- off
, f
);
908 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "error while writing %s", fn
);
912 duk_error(ctx
, DUK_ERR_TYPE_ERROR
, "error while writing %s", fn
);
923 #endif /* DUK_CMDLINE_FILEIO */
926 * Duktape heap lifecycle
929 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
930 static duk_idx_t
debugger_request(duk_context
*ctx
, void *udata
, duk_idx_t nvalues
) {
937 duk_push_string(ctx
, "missing AppRequest argument(s)");
941 cmd
= duk_get_string(ctx
, -nvalues
+ 0);
943 if (cmd
&& strcmp(cmd
, "CommandLine") == 0) {
944 if (!duk_check_stack(ctx
, main_argc
)) {
945 /* Callback should avoid errors for now, so use
946 * duk_check_stack() rather than duk_require_stack().
948 duk_push_string(ctx
, "failed to extend stack");
951 for (i
= 0; i
< main_argc
; i
++) {
952 duk_push_string(ctx
, main_argv
[i
]);
956 duk_push_sprintf(ctx
, "command not supported");
960 static void debugger_detached(void *udata
) {
961 duk_context
*ctx
= (duk_context
*) udata
;
963 fprintf(stderr
, "Debugger detached, udata: %p\n", (void *) udata
);
966 /* Ensure socket is closed even when detach is initiated by Duktape
967 * rather than debug client.
969 duk_trans_socket_finish();
971 if (debugger_reattach
) {
972 /* For automatic reattach testing. */
973 duk_trans_socket_init();
974 duk_trans_socket_waitconn();
975 fprintf(stderr
, "Debugger reconnected, call duk_debugger_attach()\n");
978 /* This is not necessary but should be harmless. */
979 duk_debugger_detach(ctx
);
981 duk_debugger_attach_custom(ctx
,
982 duk_trans_socket_read_cb
,
983 duk_trans_socket_write_cb
,
984 duk_trans_socket_peek_cb
,
985 duk_trans_socket_read_flush_cb
,
986 duk_trans_socket_write_flush_cb
,
994 #define ALLOC_DEFAULT 0
995 #define ALLOC_LOGGING 1
996 #define ALLOC_TORTURE 2
997 #define ALLOC_HYBRID 3
998 #define ALLOC_AJSHEAP 4
1000 static duk_context
*create_duktape_heap(int alloc_provider
, int debugger
, int ajsheap_log
) {
1003 (void) ajsheap_log
; /* suppress warning */
1006 if (!ctx
&& alloc_provider
== ALLOC_LOGGING
) {
1007 #if defined(DUK_CMDLINE_ALLOC_LOGGING)
1008 ctx
= duk_create_heap(duk_alloc_logging
,
1009 duk_realloc_logging
,
1011 (void *) 0xdeadbeef,
1014 fprintf(stderr
, "Warning: option --alloc-logging ignored, no logging allocator support\n");
1018 if (!ctx
&& alloc_provider
== ALLOC_TORTURE
) {
1019 #if defined(DUK_CMDLINE_ALLOC_TORTURE)
1020 ctx
= duk_create_heap(duk_alloc_torture
,
1021 duk_realloc_torture
,
1023 (void *) 0xdeadbeef,
1026 fprintf(stderr
, "Warning: option --alloc-torture ignored, no torture allocator support\n");
1030 if (!ctx
&& alloc_provider
== ALLOC_HYBRID
) {
1031 #if defined(DUK_CMDLINE_ALLOC_HYBRID)
1032 void *udata
= duk_alloc_hybrid_init();
1034 fprintf(stderr
, "Failed to init hybrid allocator\n");
1037 ctx
= duk_create_heap(duk_alloc_hybrid
,
1044 fprintf(stderr
, "Warning: option --alloc-hybrid ignored, no hybrid allocator support\n");
1048 if (!ctx
&& alloc_provider
== ALLOC_AJSHEAP
) {
1049 #if defined(DUK_CMDLINE_AJSHEAP)
1052 ctx
= duk_create_heap(
1053 ajsheap_log
? ajsheap_alloc_wrapped
: AJS_Alloc
,
1054 ajsheap_log
? ajsheap_realloc_wrapped
: AJS_Realloc
,
1055 ajsheap_log
? ajsheap_free_wrapped
: AJS_Free
,
1056 (void *) 0xdeadbeef, /* heap_udata: ignored by AjsHeap, use as marker */
1058 ); /* fatal_handler */
1060 fprintf(stderr
, "Warning: option --alloc-ajsheap ignored, no ajsheap allocator support\n");
1064 if (!ctx
&& alloc_provider
== ALLOC_DEFAULT
) {
1065 ctx
= duk_create_heap_default();
1069 fprintf(stderr
, "Failed to create Duktape heap\n");
1074 #if defined(DUK_CMDLINE_AJSHEAP)
1075 if (alloc_provider
== ALLOC_AJSHEAP
) {
1076 fprintf(stdout
, "Pool dump after heap creation\n");
1081 #if defined(DUK_CMDLINE_AJSHEAP)
1082 if (alloc_provider
== ALLOC_AJSHEAP
) {
1083 ajsheap_register(ctx
);
1087 #if defined(DUK_CMDLINE_FILEIO)
1088 duk_push_c_function(ctx
, fileio_read_file
, 1 /*nargs*/);
1089 duk_put_global_string(ctx
, "readFile");
1090 duk_push_c_function(ctx
, fileio_write_file
, 2 /*nargs*/);
1091 duk_put_global_string(ctx
, "writeFile");
1095 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
1096 fprintf(stderr
, "Debugger enabled, create socket and wait for connection\n");
1098 duk_trans_socket_init();
1099 duk_trans_socket_waitconn();
1100 fprintf(stderr
, "Debugger connected, call duk_debugger_attach() and then execute requested file(s)/eval\n");
1102 duk_debugger_attach_custom(ctx
,
1103 duk_trans_socket_read_cb
,
1104 duk_trans_socket_write_cb
,
1105 duk_trans_socket_peek_cb
,
1106 duk_trans_socket_read_flush_cb
,
1107 duk_trans_socket_write_flush_cb
,
1112 fprintf(stderr
, "Warning: option --debugger ignored, no debugger support\n");
1118 /* Manual test for duk_debugger_cooperate() */
1120 for (i
= 0; i
< 60; i
++) {
1121 printf("cooperate: %d\n", i
);
1123 duk_debugger_cooperate(ctx
);
1131 static void destroy_duktape_heap(duk_context
*ctx
, int alloc_provider
) {
1132 (void) alloc_provider
;
1134 #if defined(DUK_CMDLINE_AJSHEAP)
1135 if (alloc_provider
== ALLOC_AJSHEAP
) {
1136 fprintf(stdout
, "Pool dump before duk_destroy_heap(), before forced gc\n");
1141 fprintf(stdout
, "Pool dump before duk_destroy_heap(), after forced gc\n");
1147 duk_destroy_heap(ctx
);
1150 #if defined(DUK_CMDLINE_AJSHEAP)
1151 if (alloc_provider
== ALLOC_AJSHEAP
) {
1152 fprintf(stdout
, "Pool dump after duk_destroy_heap() (should have zero allocs)\n");
1163 int main(int argc
, char *argv
[]) {
1164 duk_context
*ctx
= NULL
;
1168 int interactive
= 0;
1169 int memlimit_high
= 1;
1170 int alloc_provider
= ALLOC_DEFAULT
;
1171 int ajsheap_log
= 0;
1173 int recreate_heap
= 0;
1174 int no_heap_destroy
= 0;
1177 const char *compile_filename
= NULL
;
1181 main_argv
= (char **) argv
;
1183 #if defined(EMSCRIPTEN)
1184 /* Try to use NODEFS to provide access to local files. Mount the
1185 * CWD as /working, and then prepend "/working/" to relative native
1186 * paths in file calls to get something that works reasonably for
1187 * relative paths. Emscripten doesn't support replacing virtual
1188 * "/" with host "/" (the default MEMFS at "/" can't be unmounted)
1189 * but we can mount "/tmp" as host "/tmp" to allow testcase runs.
1191 * https://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html#filesystem-api-nodefs
1192 * https://github.com/kripken/emscripten/blob/master/tests/fs/test_nodefs_rw.c
1195 /* At the moment it's not possible to replace the default MEMFS mounted at '/':
1196 * https://github.com/kripken/emscripten/issues/2040
1197 * https://github.com/kripken/emscripten/blob/incoming/src/library_fs.js#L1341-L1358
1203 console.log("Failed to unmount default '/' MEMFS mount: " + e);
1207 FS
.mkdir("/working");
1208 FS
.mount(NODEFS
, { root
: "." }, "/working");
1210 console
.log("Failed to mount NODEFS /working: " + e
);
1212 /* A virtual '/tmp' exists by default:
1213 * https://gist.github.com/evanw/e6be28094f34451bd5bd#file-temp-js-L3806-L3809
1219 console.log("Failed to create virtual /tmp: " + e);
1223 FS
.mount(NODEFS
, { root
: "/tmp" }, "/tmp");
1225 console
.log("Failed to mount NODEFS /tmp: " + e
);
1228 #endif /* EMSCRIPTEN */
1230 #if defined(DUK_CMDLINE_AJSHEAP)
1231 alloc_provider
= ALLOC_AJSHEAP
;
1236 * Signal handling setup
1239 #if defined(DUK_CMDLINE_SIGNAL)
1240 set_sigint_handler();
1242 /* This is useful at the global level; libraries should avoid SIGPIPE though */
1243 /*signal(SIGPIPE, SIG_IGN);*/
1250 for (i
= 1; i
< argc
; i
++) {
1251 char *arg
= argv
[i
];
1255 if (strcmp(arg
, "--restrict-memory") == 0) {
1257 } else if (strcmp(arg
, "-i") == 0) {
1259 } else if (strcmp(arg
, "-c") == 0) {
1260 if (i
== argc
- 1) {
1264 compile_filename
= argv
[i
];
1265 } else if (strcmp(arg
, "-e") == 0) {
1267 if (i
== argc
- 1) {
1270 i
++; /* skip code */
1271 } else if (strcmp(arg
, "--alloc-default") == 0) {
1272 alloc_provider
= ALLOC_DEFAULT
;
1273 } else if (strcmp(arg
, "--alloc-logging") == 0) {
1274 alloc_provider
= ALLOC_LOGGING
;
1275 } else if (strcmp(arg
, "--alloc-torture") == 0) {
1276 alloc_provider
= ALLOC_TORTURE
;
1277 } else if (strcmp(arg
, "--alloc-hybrid") == 0) {
1278 alloc_provider
= ALLOC_HYBRID
;
1279 } else if (strcmp(arg
, "--alloc-ajsheap") == 0) {
1280 alloc_provider
= ALLOC_AJSHEAP
;
1281 } else if (strcmp(arg
, "--ajsheap-log") == 0) {
1283 } else if (strcmp(arg
, "--debugger") == 0) {
1285 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
1286 } else if (strcmp(arg
, "--reattach") == 0) {
1287 debugger_reattach
= 1;
1289 } else if (strcmp(arg
, "--recreate-heap") == 0) {
1291 } else if (strcmp(arg
, "--no-heap-destroy") == 0) {
1292 no_heap_destroy
= 1;
1293 } else if (strcmp(arg
, "--verbose") == 0) {
1295 } else if (strcmp(arg
, "--run-stdin") == 0) {
1297 } else if (strlen(arg
) >= 1 && arg
[0] == '-') {
1303 if (!have_files
&& !have_eval
&& !run_stdin
) {
1311 #if defined(DUK_CMDLINE_RLIMIT)
1312 set_resource_limits(memlimit_high
? MEM_LIMIT_HIGH
: MEM_LIMIT_NORMAL
);
1314 if (memlimit_high
== 0) {
1315 fprintf(stderr
, "Warning: option --restrict-memory ignored, no rlimit support\n");
1324 ctx
= create_duktape_heap(alloc_provider
, debugger
, ajsheap_log
);
1327 * Execute any argument file(s)
1330 for (i
= 1; i
< argc
; i
++) {
1331 char *arg
= argv
[i
];
1334 } else if (strlen(arg
) == 2 && strcmp(arg
, "-e") == 0) {
1335 /* Here we know the eval arg exists but check anyway */
1336 if (i
== argc
- 1) {
1340 if (handle_eval(ctx
, argv
[i
+ 1]) != 0) {
1344 i
++; /* skip code */
1346 } else if (strlen(arg
) == 2 && strcmp(arg
, "-c") == 0) {
1347 i
++; /* skip filename */
1349 } else if (strlen(arg
) >= 1 && arg
[0] == '-') {
1354 fprintf(stderr
, "*** Executing file: %s\n", arg
);
1358 if (handle_file(ctx
, arg
, compile_filename
) != 0) {
1363 if (recreate_heap
) {
1365 fprintf(stderr
, "*** Recreating heap...\n");
1369 destroy_duktape_heap(ctx
, alloc_provider
);
1370 ctx
= create_duktape_heap(alloc_provider
, debugger
, ajsheap_log
);
1375 /* Running stdin like a full file (reading all lines before
1376 * compiling) is useful with emduk:
1377 * cat test.js | ./emduk --run-stdin
1379 if (handle_fh(ctx
, stdin
, "stdin", compile_filename
) != 0) {
1384 if (recreate_heap
) {
1386 fprintf(stderr
, "*** Recreating heap...\n");
1390 destroy_duktape_heap(ctx
, alloc_provider
);
1391 ctx
= create_duktape_heap(alloc_provider
, debugger
, ajsheap_log
);
1396 * Enter interactive mode if options indicate it
1400 if (handle_interactive(ctx
) != 0) {
1412 fprintf(stderr
, "Cleaning up...\n");
1416 if (ctx
&& no_heap_destroy
) {
1419 if (ctx
&& !no_heap_destroy
) {
1420 destroy_duktape_heap(ctx
, alloc_provider
);
1431 fprintf(stderr
, "Usage: duk [options] [<filenames>]\n"
1433 " -i enter interactive mode after executing argument file(s) / eval code\n"
1434 " -e CODE evaluate code\n"
1435 " -c FILE compile into bytecode (use with only one file argument)\n"
1436 " --run-stdin treat stdin like a file, i.e. compile full input (not line by line)\n"
1437 " --verbose verbose messages to stderr\n"
1438 " --restrict-memory use lower memory limit (used by test runner)\n"
1439 " --alloc-default use Duktape default allocator\n"
1440 #if defined(DUK_CMDLINE_ALLOC_LOGGING)
1441 " --alloc-logging use logging allocator (writes to /tmp)\n"
1443 #if defined(DUK_CMDLINE_ALLOC_TORTURE)
1444 " --alloc-torture use torture allocator\n"
1446 #if defined(DUK_CMDLINE_ALLOC_HYBRID)
1447 " --alloc-hybrid use hybrid allocator\n"
1449 #if defined(DUK_CMDLINE_AJSHEAP)
1450 " --alloc-ajsheap use ajsheap allocator (enabled by default with 'ajduk')\n"
1451 " --ajsheap-log write alloc log to /tmp/ajduk-alloc-log.txt\n"
1453 #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
1454 " --debugger start example debugger\n"
1455 " --reattach automatically reattach debugger on detach\n"
1457 " --recreate-heap recreate heap after every file\n"
1458 " --no-heap-destroy force GC, but don't destroy heap at end (leak testing)\n"
1460 "If <filename> is omitted, interactive mode is started automatically.\n");