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