/* * Command line execution tool. Used by test cases and other manual testing. * * To enable readline and other fancy stuff, compile with -DDUK_CMDLINE_FANCY * (it is not the default to maximize portability). */ #ifndef DUK_CMDLINE_FANCY #define NO_READLINE #define NO_RLIMIT #define NO_SIGNAL #endif #define GREET_CODE(variant) \ "print(" \ "'((o) Duktape" variant "'" \ ", " \ "Math.floor(Duktape.version / 10000) + '.' + " \ "Math.floor(Duktape.version / 100) % 100 + '.' + " \ "Duktape.version % 100" \ ");" #include #include #include #ifndef NO_SIGNAL #include #endif #ifndef NO_RLIMIT #include #endif #ifndef NO_READLINE #include #include #endif #include "duktape.h" #define MEM_LIMIT_NORMAL (128*1024*1024) /* 128 MB */ #define MEM_LIMIT_HIGH (2047*1024*1024) /* ~2 GB */ #define LINEBUF_SIZE 65536 static int interactive_mode = 0; #ifndef NO_RLIMIT static void set_resource_limits(rlim_t mem_limit_value) { int rc; struct rlimit lim; rc = getrlimit(RLIMIT_AS, &lim); if (rc != 0) { fprintf(stderr, "Warning: cannot read RLIMIT_AS\n"); return; } if (lim.rlim_max < mem_limit_value) { fprintf(stderr, "Warning: rlim_max < mem_limit_value (%d < %d)\n", (int) lim.rlim_max, (int) mem_limit_value); return; } lim.rlim_cur = mem_limit_value; lim.rlim_max = mem_limit_value; rc = setrlimit(RLIMIT_AS, &lim); if (rc != 0) { fprintf(stderr, "Warning: setrlimit failed\n"); return; } #if 0 fprintf(stderr, "Set RLIMIT_AS to %d\n", (int) mem_limit_value); #endif } #endif /* NO_RLIMIT */ #ifndef NO_SIGNAL static void my_sighandler(int x) { fprintf(stderr, "Got signal %d\n", x); } static void set_sigint_handler(void) { (void) signal(SIGINT, my_sighandler); } #endif /* NO_SIGNAL */ static int get_stack_raw(duk_context *ctx) { if (!duk_is_object(ctx, -1)) { return 1; } if (!duk_has_prop_string(ctx, -1, "stack")) { return 1; } /* XXX: should check here that object is an Error instance too, * i.e. 'stack' is special. */ duk_get_prop_string(ctx, -1, "stack"); /* caller coerces */ duk_remove(ctx, -2); return 1; } /* Print error to stderr and pop error. */ static void print_pop_error(duk_context *ctx, FILE *f) { /* Print error objects with a stack trace specially. * Note that getting the stack trace may throw an error * so this also needs to be safe call wrapped. */ (void) duk_safe_call(ctx, get_stack_raw, 1 /*nargs*/, 1 /*nrets*/); fprintf(f, "%s\n", duk_safe_to_string(ctx, -1)); fflush(f); duk_pop(ctx); } static int wrapped_compile_execute(duk_context *ctx) { int comp_flags; /* XXX: Here it'd be nice to get some stats for the compilation result * when a suitable command line is given (e.g. code size, constant * count, function count. These are available internally but not through * the public API. */ comp_flags = 0; duk_compile(ctx, comp_flags); duk_push_global_object(ctx); /* 'this' binding */ duk_call_method(ctx, 0); if (interactive_mode) { /* * In interactive mode, write to stdout so output won't * interleave as easily. * * NOTE: the ToString() coercion may fail in some cases; * for instance, if you evaluate: * * ( {valueOf: function() {return {}}, * toString: function() {return {}}}); * * The error is: * * TypeError: failed to coerce with [[DefaultValue]] * duk_api.c:1420 * * These are handled now by the caller which also has stack * trace printing support. User code can print out errors * safely using duk_safe_to_string(). */ fprintf(stdout, "= %s\n", duk_to_string(ctx, -1)); fflush(stdout); } else { /* In non-interactive mode, success results are not written at all. * It is important that the result value is not string coerced, * as the string coercion may cause an error in some cases. */ } duk_pop(ctx); return 0; } static int handle_fh(duk_context *ctx, FILE *f, const char *filename) { char *buf = NULL; int len; int got; int rc; int retval = -1; if (fseek(f, 0, SEEK_END) < 0) { goto error; } len = (int) ftell(f); if (fseek(f, 0, SEEK_SET) < 0) { goto error; } buf = (char *) malloc(len); if (!buf) { goto error; } got = fread((void *) buf, (size_t) 1, (size_t) len, f); duk_push_lstring(ctx, buf, got); duk_push_string(ctx, filename); free(buf); buf = NULL; interactive_mode = 0; /* global */ rc = duk_safe_call(ctx, wrapped_compile_execute, 2 /*nargs*/, 1 /*nret*/); if (rc != DUK_EXEC_SUCCESS) { print_pop_error(ctx, stderr); goto error; } else { duk_pop(ctx); retval = 0; } /* fall thru */ cleanup: if (buf) { free(buf); } return retval; error: fprintf(stderr, "error in executing file %s\n", filename); fflush(stderr); goto cleanup; } static int handle_file(duk_context *ctx, const char *filename) { FILE *f = NULL; int retval; f = fopen(filename, "rb"); if (!f) { fprintf(stderr, "failed to open source file: %s\n", filename); fflush(stderr); goto error; } retval = handle_fh(ctx, f, filename); fclose(f); return retval; error: return -1; } static int handle_eval(duk_context *ctx, const char *code) { int rc; int retval = -1; duk_push_string(ctx, code); duk_push_string(ctx, "eval"); interactive_mode = 0; /* global */ rc = duk_safe_call(ctx, wrapped_compile_execute, 2 /*nargs*/, 1 /*nret*/); if (rc != DUK_EXEC_SUCCESS) { print_pop_error(ctx, stderr); } else { duk_pop(ctx); retval = 0; } return retval; } #ifdef NO_READLINE static int handle_interactive(duk_context *ctx) { const char *prompt = "duk> "; char *buffer = NULL; int retval = 0; int rc; int got_eof = 0; duk_eval_string(ctx, GREET_CODE(" [no readline]")); duk_pop(ctx); buffer = (char *) malloc(LINEBUF_SIZE); if (!buffer) { fprintf(stderr, "failed to allocated a line buffer\n"); fflush(stderr); retval = -1; goto done; } while (!got_eof) { size_t idx = 0; fwrite(prompt, 1, strlen(prompt), stdout); fflush(stdout); for (;;) { int c = fgetc(stdin); if (c == EOF) { got_eof = 1; break; } else if (c == '\n') { break; } else if (idx >= LINEBUF_SIZE) { fprintf(stderr, "line too long\n"); fflush(stderr); retval = -1; goto done; } else { buffer[idx++] = (char) c; } } duk_push_lstring(ctx, buffer, idx); duk_push_string(ctx, "input"); interactive_mode = 1; /* global */ rc = duk_safe_call(ctx, wrapped_compile_execute, 2 /*nargs*/, 1 /*nret*/); if (rc != DUK_EXEC_SUCCESS) { /* in interactive mode, write to stdout */ print_pop_error(ctx, stdout); retval = -1; /* an error 'taints' the execution */ } else { duk_pop(ctx); } } done: if (buffer) { free(buffer); buffer = NULL; } return retval; } #else /* NO_READLINE */ static int handle_interactive(duk_context *ctx) { const char *prompt = "duk> "; char *buffer = NULL; int retval = 0; int rc; duk_eval_string(ctx, GREET_CODE("")); duk_pop(ctx); /* * Note: using readline leads to valgrind-reported leaks inside * readline itself. Execute code from an input file (and not * through stdin) for clean valgrind runs. */ rl_initialize(); for (;;) { if (buffer) { free(buffer); buffer = NULL; } buffer = readline(prompt); if (!buffer) { break; } if (buffer && buffer[0] != (char) 0) { add_history(buffer); } duk_push_lstring(ctx, buffer, strlen(buffer)); duk_push_string(ctx, "input"); if (buffer) { free(buffer); buffer = NULL; } interactive_mode = 1; /* global */ rc = duk_safe_call(ctx, wrapped_compile_execute, 2 /*nargs*/, 1 /*nret*/); if (rc != DUK_EXEC_SUCCESS) { /* in interactive mode, write to stdout */ print_pop_error(ctx, stdout); retval = -1; /* an error 'taints' the execution */ } else { duk_pop(ctx); } } if (buffer) { free(buffer); buffer = NULL; } return retval; } #endif /* NO_READLINE */ int main(int argc, char *argv[]) { duk_context *ctx = NULL; int retval = 0; int have_files = 0; int have_eval = 0; int interactive = 0; int memlimit_high = 1; int i; /* * Signal handling setup */ #ifndef NO_SIGNAL set_sigint_handler(); /* This is useful at the global level; libraries should avoid SIGPIPE though */ /*signal(SIGPIPE, SIG_IGN);*/ #endif /* * Parse options */ for (i = 1; i < argc; i++) { char *arg = argv[i]; if (!arg) { goto usage; } if (strcmp(arg, "-r") == 0) { memlimit_high = 0; } else if (strcmp(arg, "-i") == 0) { interactive = 1; } else if (strcmp(arg, "-e") == 0) { have_eval = 1; if (i == argc - 1) { goto usage; } i++; /* skip code */ } else if (strlen(arg) >= 1 && arg[0] == '-') { goto usage; } else { have_files = 1; } } if (!have_files && !have_eval) { interactive = 1; } /* * Memory limit */ #ifndef NO_RLIMIT set_resource_limits(memlimit_high ? MEM_LIMIT_HIGH : MEM_LIMIT_NORMAL); #else (void) memlimit_high; /* suppress warning */ #endif /* * Create context and execute any argument file(s) */ ctx = duk_create_heap_default(); for (i = 1; i < argc; i++) { char *arg = argv[i]; if (!arg) { continue; } else if (strlen(arg) == 2 && strcmp(arg, "-e") == 0) { /* Here we know the eval arg exists but check anyway */ if (i == argc - 1) { retval = 1; goto cleanup; } if (handle_eval(ctx, argv[i + 1]) != 0) { retval = 1; goto cleanup; } i++; /* skip code */ continue; } else if (strlen(arg) >= 1 && arg[0] == '-') { continue; } if (handle_file(ctx, arg) != 0) { retval = 1; goto cleanup; } } /* * Enter interactive mode if options indicate it */ if (interactive) { if (handle_interactive(ctx) != 0) { retval = 1; goto cleanup; } } /* * Cleanup and exit */ cleanup: if (interactive) { fprintf(stderr, "Cleaning up...\n"); fflush(stderr); } if (ctx) { duk_destroy_heap(ctx); } return retval; /* * Usage */ usage: fprintf(stderr, "Usage: duk [-i] [-r] []\n" "\n" " -i enter interactive mode after executing argument file(s) / eval code\n" " -e CODE evaluate code\n" " -r use lower memory limit (used by test runner)" #ifdef NO_RLIMIT " (disabled)\n" #else "\n" #endif "\n" "If is omitted, interactive mode is started automatically.\n"); fflush(stderr); exit(1); }