You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

479 lines
9.5 KiB

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef NO_SIGNAL
#include <signal.h>
#endif
#ifndef NO_RLIMIT
#include <sys/resource.h>
#endif
#ifndef NO_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#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_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_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;
}
#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_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_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 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 (strlen(arg) >= 1 && arg[0] == '-') {
goto usage;
} else {
have_files = 1;
}
}
if (!have_files) {
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) >= 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] [<filenames>]\n"
"\n"
" -i enter interactive mode after executing argument file(s)\n"
" -r use lower memory limit (used by test runner)"
#ifdef NO_RLIMIT
" (disabled)\n"
#else
"\n"
#endif
"\n"
"If <filename> is omitted, interactive mode is started automatically.\n");
fflush(stderr);
exit(1);
}