12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463 |
- /*
- * Command line execution tool. Useful for test cases and manual testing.
- *
- * To enable linenoise and other fancy stuff, compile with -DDUK_CMDLINE_FANCY.
- * It is not the default to maximize portability. You can also compile in
- * support for example allocators, grep for DUK_CMDLINE_*.
- */
- /* Helper define to enable a feature set; can also use separate defines. */
- #if defined(DUK_CMDLINE_FANCY)
- #define DUK_CMDLINE_LINENOISE
- #define DUK_CMDLINE_LINENOISE_COMPLETION
- #define DUK_CMDLINE_RLIMIT
- #define DUK_CMDLINE_SIGNAL
- #endif
- #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
- defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
- /* Suppress warnings about plain fopen() etc. */
- #define _CRT_SECURE_NO_WARNINGS
- #if defined(_MSC_VER) && (_MSC_VER < 1900)
- /* Workaround for snprintf() missing in older MSVC versions.
- * Note that _snprintf() may not NUL terminate the string, but
- * this difference does not matter here as a NUL terminator is
- * always explicitly added.
- */
- #define snprintf _snprintf
- #endif
- #endif
- #define GREET_CODE(variant) \
- "print('((o) Duktape" variant " ' + " \
- "Math.floor(Duktape.version / 10000) + '.' + " \
- "Math.floor(Duktape.version / 100) % 100 + '.' + " \
- "Duktape.version % 100" \
- ", '(" DUK_GIT_DESCRIBE ")');"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #if defined(DUK_CMDLINE_SIGNAL)
- #include <signal.h>
- #endif
- #if defined(DUK_CMDLINE_RLIMIT)
- #include <sys/resource.h>
- #endif
- #if defined(DUK_CMDLINE_LINENOISE)
- #include "linenoise.h"
- #endif
- #if defined(DUK_CMDLINE_FILEIO)
- #include <errno.h>
- #endif
- #if defined(EMSCRIPTEN)
- #include <emscripten.h>
- #endif
- #if defined(DUK_CMDLINE_ALLOC_LOGGING)
- #include "duk_alloc_logging.h"
- #endif
- #if defined(DUK_CMDLINE_ALLOC_TORTURE)
- #include "duk_alloc_torture.h"
- #endif
- #if defined(DUK_CMDLINE_ALLOC_HYBRID)
- #include "duk_alloc_hybrid.h"
- #endif
- #include "duktape.h"
- #if defined(DUK_CMDLINE_AJSHEAP)
- /* Defined in duk_cmdline_ajduk.c or alljoyn.js headers. */
- void ajsheap_init(void);
- void ajsheap_free(void);
- void ajsheap_dump(void);
- void ajsheap_register(duk_context *ctx);
- void ajsheap_start_exec_timeout(void);
- void ajsheap_clear_exec_timeout(void);
- void *ajsheap_alloc_wrapped(void *udata, duk_size_t size);
- void *ajsheap_realloc_wrapped(void *udata, void *ptr, duk_size_t size);
- void ajsheap_free_wrapped(void *udata, void *ptr);
- void *AJS_Alloc(void *udata, duk_size_t size);
- void *AJS_Realloc(void *udata, void *ptr, duk_size_t size);
- void AJS_Free(void *udata, void *ptr);
- #endif
- #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
- #include "duk_trans_socket.h"
- #endif
- #define MEM_LIMIT_NORMAL (128*1024*1024) /* 128 MB */
- #define MEM_LIMIT_HIGH (2047*1024*1024) /* ~2 GB */
- #define LINEBUF_SIZE 65536
- static int main_argc = 0;
- static char **main_argv = NULL;
- static int interactive_mode = 0;
- #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
- static int debugger_reattach = 0;
- #endif
- /*
- * Misc helpers
- */
- #if defined(DUK_CMDLINE_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 /* DUK_CMDLINE_RLIMIT */
- #if defined(DUK_CMDLINE_SIGNAL)
- static void my_sighandler(int x) {
- fprintf(stderr, "Got signal %d\n", x);
- fflush(stderr);
- }
- static void set_sigint_handler(void) {
- (void) signal(SIGINT, my_sighandler);
- (void) signal(SIGPIPE, SIG_IGN); /* avoid SIGPIPE killing process */
- }
- #endif /* DUK_CMDLINE_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;
- }
- if (!duk_is_error(ctx, -1)) {
- /* Not an Error instance, don't read "stack". */
- return 1;
- }
- 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) {
- const char *src_data;
- duk_size_t src_len;
- 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.
- */
- /* Use duk_compile_lstring_filename() variant which avoids interning
- * the source code. This only really matters for low memory environments.
- */
- /* [ ... bytecode_filename src_data src_len filename ] */
- src_data = (const char *) duk_require_pointer(ctx, -3);
- src_len = (duk_size_t) duk_require_uint(ctx, -2);
- if (src_data != NULL && src_len >= 2 && src_data[0] == (char) 0xff) {
- /* Bytecode. */
- duk_push_lstring(ctx, src_data, src_len);
- duk_to_buffer(ctx, -1, NULL);
- duk_load_function(ctx);
- } else {
- /* Source code. */
- comp_flags = 0;
- duk_compile_lstring_filename(ctx, comp_flags, src_data, src_len);
- }
- /* [ ... bytecode_filename src_data src_len function ] */
- /* Optional bytecode dump. */
- if (duk_is_string(ctx, -4)) {
- FILE *f;
- void *bc_ptr;
- duk_size_t bc_len;
- size_t wrote;
- char fnbuf[256];
- const char *filename;
- duk_dup_top(ctx);
- duk_dump_function(ctx);
- bc_ptr = duk_require_buffer(ctx, -1, &bc_len);
- filename = duk_require_string(ctx, -5);
- #if defined(EMSCRIPTEN)
- if (filename[0] == '/') {
- snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
- } else {
- snprintf(fnbuf, sizeof(fnbuf), "/working/%s", filename);
- }
- #else
- snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
- #endif
- fnbuf[sizeof(fnbuf) - 1] = (char) 0;
- f = fopen(fnbuf, "wb");
- if (!f) {
- duk_error(ctx, DUK_ERR_ERROR, "failed to open bytecode output file");
- }
- wrote = fwrite(bc_ptr, 1, (size_t) bc_len, f); /* XXX: handle partial writes */
- (void) fclose(f);
- if (wrote != bc_len) {
- duk_error(ctx, DUK_ERR_ERROR, "failed to write all bytecode");
- }
- return 0; /* duk_safe_call() cleans up */
- }
- #if 0
- /* Manual test for bytecode dump/load cycle: dump and load before
- * execution. Enable manually, then run "make qecmatest" for a
- * reasonably good coverage of different functions and programs.
- */
- duk_dump_function(ctx);
- duk_load_function(ctx);
- #endif
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_start_exec_timeout();
- #endif
- duk_push_global_object(ctx); /* 'this' binding */
- duk_call_method(ctx, 0);
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_clear_exec_timeout();
- #endif
- 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.
- */
- }
- return 0; /* duk_safe_call() cleans up */
- }
- /*
- * Minimal Linenoise completion support
- */
- #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
- static duk_context *completion_ctx;
- static int completion_idpart(unsigned char c) {
- /* Very simplified "is identifier part" check. */
- if ((c >= (unsigned char) 'a' && c <= (unsigned char) 'z') ||
- (c >= (unsigned char) 'A' && c <= (unsigned char) 'Z') ||
- (c >= (unsigned char) '0' && c <= (unsigned char) '9') ||
- c == (unsigned char) '$' || c == (unsigned char) '_') {
- return 1;
- }
- return 0;
- }
- static int completion_digit(unsigned char c) {
- return (c >= (unsigned char) '0' && c <= (unsigned char) '9');
- }
- static duk_ret_t linenoise_completion_lookup(duk_context *ctx) {
- duk_size_t len;
- const char *orig;
- const unsigned char *p;
- const unsigned char *p_curr;
- const unsigned char *p_end;
- const char *key;
- const char *prefix;
- linenoiseCompletions *lc;
- duk_idx_t idx_obj;
- orig = duk_require_string(ctx, -3);
- p_curr = (const unsigned char *) duk_require_lstring(ctx, -2, &len);
- p_end = p_curr + len;
- lc = duk_require_pointer(ctx, -1);
- duk_push_global_object(ctx);
- idx_obj = duk_require_top_index(ctx);
- while (p_curr <= p_end) {
- /* p_curr == p_end allowed on purpose, to handle 'Math.' for example. */
- p = p_curr;
- while (p < p_end && p[0] != (unsigned char) '.') {
- p++;
- }
- /* 'p' points to a NUL (p == p_end) or a period. */
- prefix = duk_push_lstring(ctx, (const char *) p_curr, (duk_size_t) (p - p_curr));
- #if 0
- fprintf(stderr, "Completion check: '%s'\n", prefix);
- fflush(stderr);
- #endif
- if (p == p_end) {
- /* 'idx_obj' points to the object matching the last
- * full component, use [p_curr,p[ as a filter for
- * that object.
- */
- duk_enum(ctx, idx_obj, DUK_ENUM_INCLUDE_NONENUMERABLE);
- while (duk_next(ctx, -1, 0 /*get_value*/)) {
- key = duk_get_string(ctx, -1);
- #if 0
- fprintf(stderr, "Key: %s\n", key ? key : "");
- fflush(stderr);
- #endif
- if (!key) {
- /* Should never happen, just in case. */
- goto next;
- }
- /* Ignore array index keys: usually not desirable, and would
- * also require ['0'] quoting.
- */
- if (completion_digit(key[0])) {
- goto next;
- }
- /* XXX: There's no key quoting now, it would require replacing the
- * last component with a ['foo\nbar'] style lookup when appropriate.
- */
- if (strlen(prefix) == 0) {
- /* Partial ends in a period, e.g. 'Math.' -> complete all Math properties. */
- duk_push_string(ctx, orig); /* original, e.g. 'Math.' */
- duk_push_string(ctx, key);
- duk_concat(ctx, 2);
- linenoiseAddCompletion(lc, duk_require_string(ctx, -1));
- duk_pop(ctx);
- } else if (prefix && strcmp(key, prefix) == 0) {
- /* Full completion, add a period, e.g. input 'Math' -> 'Math.'. */
- duk_push_string(ctx, orig); /* original, including partial last component */
- duk_push_string(ctx, ".");
- duk_concat(ctx, 2);
- linenoiseAddCompletion(lc, duk_require_string(ctx, -1));
- duk_pop(ctx);
- } else if (prefix && strncmp(key, prefix, strlen(prefix)) == 0) {
- /* Last component is partial, complete. */
- duk_push_string(ctx, orig); /* original, including partial last component */
- duk_push_string(ctx, key + strlen(prefix)); /* completion to last component */
- duk_concat(ctx, 2);
- linenoiseAddCompletion(lc, duk_require_string(ctx, -1));
- duk_pop(ctx);
- }
- next:
- duk_pop(ctx);
- }
- return 0;
- } else {
- if (duk_get_prop(ctx, idx_obj)) {
- duk_to_object(ctx, -1); /* for properties of plain strings etc */
- duk_replace(ctx, idx_obj);
- p_curr = p + 1;
- } else {
- /* Not found. */
- return 0;
- }
- }
- }
- return 0;
- }
- static void linenoise_completion(const char *buf, linenoiseCompletions *lc) {
- duk_context *ctx;
- const unsigned char *p_start;
- const unsigned char *p_end;
- const unsigned char *p;
- duk_int_t rc;
- if (!buf) {
- return;
- }
- ctx = completion_ctx;
- if (!ctx) {
- return;
- }
- p_start = (const unsigned char *) buf;
- p_end = (const unsigned char *) (buf + strlen(buf));
- p = p_end;
- /* Scan backwards for a maximal string which looks like a property
- * chain (e.g. foo.bar.quux).
- */
- while (--p >= p_start) {
- if (p[0] == (unsigned char) '.') {
- if (p <= p_start) {
- break;
- }
- if (!completion_idpart(p[-1])) {
- /* Catches e.g. 'foo..bar' -> we want 'bar' only. */
- break;
- }
- } else if (!completion_idpart(p[0])) {
- break;
- }
- }
- /* 'p' will either be p_start - 1 (ran out of buffer) or point to
- * the first offending character.
- */
- p++;
- if (p < p_start || p >= p_end) {
- return; /* should never happen, but just in case */
- }
- /* 'p' now points to a string of the form 'foo.bar.quux'. Look up
- * all the components except the last; treat the last component as
- * a partial name which is used as a filter for the previous full
- * component. All lookups are from the global object now.
- */
- #if 0
- fprintf(stderr, "Completion starting point: '%s'\n", p);
- fflush(stderr);
- #endif
- duk_push_string(ctx, (const char *) buf);
- duk_push_lstring(ctx, (const char *) p, (duk_size_t) (p_end - p));
- duk_push_pointer(ctx, (void *) lc);
- rc = duk_safe_call(ctx, linenoise_completion_lookup, 3 /*nargs*/, 1 /*nrets*/);
- if (rc != DUK_EXEC_SUCCESS) {
- fprintf(stderr, "Completion handling failure: %s\n", duk_safe_to_string(ctx, -1));
- }
- duk_pop(ctx);
- }
- #endif /* DUK_CMDLINE_LINENOISE_COMPLETION */
- /*
- * Execute from file handle etc
- */
- static int handle_fh(duk_context *ctx, FILE *f, const char *filename, const char *bytecode_filename) {
- char *buf = NULL;
- size_t bufsz;
- size_t bufoff;
- size_t got;
- int rc;
- int retval = -1;
- buf = (char *) malloc(1024);
- if (!buf) {
- goto error;
- }
- bufsz = 1024;
- bufoff = 0;
- /* Read until EOF, avoid fseek/stat because it won't work with stdin. */
- for (;;) {
- size_t avail;
- avail = bufsz - bufoff;
- if (avail < 1024) {
- size_t newsz;
- char *buf_new;
- #if 0
- fprintf(stderr, "resizing read buffer: %ld -> %ld\n", (long) bufsz, (long) (bufsz * 2));
- #endif
- newsz = bufsz + (bufsz >> 2) + 1024; /* +25% and some extra */
- buf_new = (char *) realloc(buf, newsz);
- if (!buf_new) {
- goto error;
- }
- buf = buf_new;
- bufsz = newsz;
- }
- avail = bufsz - bufoff;
- #if 0
- fprintf(stderr, "reading input: buf=%p bufsz=%ld bufoff=%ld avail=%ld\n",
- (void *) buf, (long) bufsz, (long) bufoff, (long) avail);
- #endif
- got = fread((void *) (buf + bufoff), (size_t) 1, avail, f);
- #if 0
- fprintf(stderr, "got=%ld\n", (long) got);
- #endif
- if (got == 0) {
- break;
- }
- bufoff += got;
- /* Emscripten specific: stdin EOF doesn't work as expected.
- * Instead, when 'emduk' is executed using Node.js, a file
- * piped to stdin repeats (!). Detect that repeat and cut off
- * the stdin read. Ensure the loop repeats enough times to
- * avoid detecting spurious loops.
- *
- * This only seems to work for inputs up to 256 bytes long.
- */
- #if defined(EMSCRIPTEN)
- if (bufoff >= 16384) {
- size_t i, j, nloops;
- int looped = 0;
- for (i = 16; i < bufoff / 8; i++) {
- int ok;
- nloops = bufoff / i;
- ok = 1;
- for (j = 1; j < nloops; j++) {
- if (memcmp((void *) buf, (void *) (buf + i * j), i) != 0) {
- ok = 0;
- break;
- }
- }
- if (ok) {
- fprintf(stderr, "emscripten workaround: detect looping at index %ld, verified with %ld loops\n", (long) i, (long) (nloops - 1));
- bufoff = i;
- looped = 1;
- break;
- }
- }
- if (looped) {
- break;
- }
- }
- #endif
- }
- duk_push_string(ctx, bytecode_filename);
- duk_push_pointer(ctx, (void *) buf);
- duk_push_uint(ctx, (duk_uint_t) bufoff);
- duk_push_string(ctx, filename);
- interactive_mode = 0; /* global */
- rc = duk_safe_call(ctx, wrapped_compile_execute, 4 /*nargs*/, 1 /*nret*/);
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_clear_exec_timeout();
- #endif
- free(buf);
- buf = NULL;
- 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);
- buf = NULL;
- }
- 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, const char *bytecode_filename) {
- FILE *f = NULL;
- int retval;
- char fnbuf[256];
- /* Example of sending an application specific debugger notification. */
- duk_push_string(ctx, "DebuggerHandleFile");
- duk_push_string(ctx, filename);
- duk_debugger_notify(ctx, 2);
- #if defined(EMSCRIPTEN)
- if (filename[0] == '/') {
- snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
- } else {
- snprintf(fnbuf, sizeof(fnbuf), "/working/%s", filename);
- }
- #else
- snprintf(fnbuf, sizeof(fnbuf), "%s", filename);
- #endif
- fnbuf[sizeof(fnbuf) - 1] = (char) 0;
- f = fopen(fnbuf, "rb");
- if (!f) {
- fprintf(stderr, "failed to open source file: %s\n", filename);
- fflush(stderr);
- goto error;
- }
- retval = handle_fh(ctx, f, filename, bytecode_filename);
- fclose(f);
- return retval;
- error:
- return -1;
- }
- static int handle_eval(duk_context *ctx, char *code) {
- int rc;
- int retval = -1;
- duk_push_pointer(ctx, (void *) code);
- duk_push_uint(ctx, (duk_uint_t) strlen(code));
- duk_push_string(ctx, "eval");
- interactive_mode = 0; /* global */
- rc = duk_safe_call(ctx, wrapped_compile_execute, 3 /*nargs*/, 1 /*nret*/);
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_clear_exec_timeout();
- #endif
- if (rc != DUK_EXEC_SUCCESS) {
- print_pop_error(ctx, stderr);
- } else {
- duk_pop(ctx);
- retval = 0;
- }
- return retval;
- }
- #if defined(DUK_CMDLINE_LINENOISE)
- 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(" [linenoise]"));
- duk_pop(ctx);
- linenoiseSetMultiLine(1);
- linenoiseHistorySetMaxLen(64);
- #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
- linenoiseSetCompletionCallback(linenoise_completion);
- #endif
- for (;;) {
- if (buffer) {
- linenoiseFree(buffer);
- buffer = NULL;
- }
- #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
- completion_ctx = ctx;
- #endif
- buffer = linenoise(prompt);
- #if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
- completion_ctx = NULL;
- #endif
- if (!buffer) {
- break;
- }
- if (buffer && buffer[0] != (char) 0) {
- linenoiseHistoryAdd(buffer);
- }
- duk_push_pointer(ctx, (void *) buffer);
- duk_push_uint(ctx, (duk_uint_t) strlen(buffer));
- duk_push_string(ctx, "input");
- interactive_mode = 1; /* global */
- rc = duk_safe_call(ctx, wrapped_compile_execute, 3 /*nargs*/, 1 /*nret*/);
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_clear_exec_timeout();
- #endif
- if (buffer) {
- linenoiseFree(buffer);
- buffer = NULL;
- }
- 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) {
- linenoiseFree(buffer);
- buffer = NULL;
- }
- return retval;
- }
- #else /* DUK_CMDLINE_LINENOISE */
- 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(""));
- 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_pointer(ctx, (void *) buffer);
- duk_push_uint(ctx, (duk_uint_t) idx);
- duk_push_string(ctx, "input");
- interactive_mode = 1; /* global */
- rc = duk_safe_call(ctx, wrapped_compile_execute, 3 /*nargs*/, 1 /*nret*/);
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_clear_exec_timeout();
- #endif
- 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;
- }
- #endif /* DUK_CMDLINE_LINENOISE */
- /*
- * Simple file read/write bindings
- */
- #if defined(DUK_CMDLINE_FILEIO)
- static duk_ret_t fileio_read_file(duk_context *ctx) {
- const char *fn;
- char *buf;
- size_t len;
- size_t off;
- int rc;
- FILE *f;
- fn = duk_require_string(ctx, 0);
- f = fopen(fn, "rb");
- if (!f) {
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "cannot open file %s for reading, errno %ld: %s",
- fn, (long) errno, strerror(errno));
- }
- rc = fseek(f, 0, SEEK_END);
- if (rc < 0) {
- (void) fclose(f);
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "fseek() failed for %s, errno %ld: %s",
- fn, (long) errno, strerror(errno));
- }
- len = (size_t) ftell(f);
- rc = fseek(f, 0, SEEK_SET);
- if (rc < 0) {
- (void) fclose(f);
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "fseek() failed for %s, errno %ld: %s",
- fn, (long) errno, strerror(errno));
- }
- buf = (char *) duk_push_fixed_buffer(ctx, (duk_size_t) len);
- for (off = 0; off < len;) {
- size_t got;
- got = fread((void *) (buf + off), 1, len - off, f);
- if (ferror(f)) {
- (void) fclose(f);
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "error while reading %s", fn);
- }
- if (got == 0) {
- if (feof(f)) {
- break;
- } else {
- (void) fclose(f);
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "error while reading %s", fn);
- }
- }
- off += got;
- }
- if (f) {
- (void) fclose(f);
- }
- return 1;
- }
- static duk_ret_t fileio_write_file(duk_context *ctx) {
- const char *fn;
- const char *buf;
- size_t len;
- size_t off;
- FILE *f;
- fn = duk_require_string(ctx, 0);
- f = fopen(fn, "wb");
- if (!f) {
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "cannot open file %s for writing, errno %ld: %s",
- fn, (long) errno, strerror(errno));
- }
- len = 0;
- buf = (char *) duk_to_buffer(ctx, 1, &len);
- for (off = 0; off < len;) {
- size_t got;
- got = fwrite((const void *) (buf + off), 1, len - off, f);
- if (ferror(f)) {
- (void) fclose(f);
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "error while writing %s", fn);
- }
- if (got == 0) {
- (void) fclose(f);
- duk_error(ctx, DUK_ERR_TYPE_ERROR, "error while writing %s", fn);
- }
- off += got;
- }
- if (f) {
- (void) fclose(f);
- }
- return 0;
- }
- #endif /* DUK_CMDLINE_FILEIO */
- /*
- * Duktape heap lifecycle
- */
- #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
- static duk_idx_t debugger_request(duk_context *ctx, void *udata, duk_idx_t nvalues) {
- const char *cmd;
- int i;
- (void) udata;
- if (nvalues < 1) {
- duk_push_string(ctx, "missing AppRequest argument(s)");
- return -1;
- }
- cmd = duk_get_string(ctx, -nvalues + 0);
- if (cmd && strcmp(cmd, "CommandLine") == 0) {
- if (!duk_check_stack(ctx, main_argc)) {
- /* Callback should avoid errors for now, so use
- * duk_check_stack() rather than duk_require_stack().
- */
- duk_push_string(ctx, "failed to extend stack");
- return -1;
- }
- for (i = 0; i < main_argc; i++) {
- duk_push_string(ctx, main_argv[i]);
- }
- return main_argc;
- }
- duk_push_sprintf(ctx, "command not supported");
- return -1;
- }
- static void debugger_detached(void *udata) {
- duk_context *ctx = (duk_context *) udata;
- (void) ctx;
- fprintf(stderr, "Debugger detached, udata: %p\n", (void *) udata);
- fflush(stderr);
- /* Ensure socket is closed even when detach is initiated by Duktape
- * rather than debug client.
- */
- duk_trans_socket_finish();
- if (debugger_reattach) {
- /* For automatic reattach testing. */
- duk_trans_socket_init();
- duk_trans_socket_waitconn();
- fprintf(stderr, "Debugger reconnected, call duk_debugger_attach()\n");
- fflush(stderr);
- #if 0
- /* This is not necessary but should be harmless. */
- duk_debugger_detach(ctx);
- #endif
- duk_debugger_attach_custom(ctx,
- duk_trans_socket_read_cb,
- duk_trans_socket_write_cb,
- duk_trans_socket_peek_cb,
- duk_trans_socket_read_flush_cb,
- duk_trans_socket_write_flush_cb,
- debugger_request,
- debugger_detached,
- (void *) ctx);
- }
- }
- #endif
- #define ALLOC_DEFAULT 0
- #define ALLOC_LOGGING 1
- #define ALLOC_TORTURE 2
- #define ALLOC_HYBRID 3
- #define ALLOC_AJSHEAP 4
- static duk_context *create_duktape_heap(int alloc_provider, int debugger, int ajsheap_log) {
- duk_context *ctx;
- (void) ajsheap_log; /* suppress warning */
- ctx = NULL;
- if (!ctx && alloc_provider == ALLOC_LOGGING) {
- #if defined(DUK_CMDLINE_ALLOC_LOGGING)
- ctx = duk_create_heap(duk_alloc_logging,
- duk_realloc_logging,
- duk_free_logging,
- (void *) 0xdeadbeef,
- NULL);
- #else
- fprintf(stderr, "Warning: option --alloc-logging ignored, no logging allocator support\n");
- fflush(stderr);
- #endif
- }
- if (!ctx && alloc_provider == ALLOC_TORTURE) {
- #if defined(DUK_CMDLINE_ALLOC_TORTURE)
- ctx = duk_create_heap(duk_alloc_torture,
- duk_realloc_torture,
- duk_free_torture,
- (void *) 0xdeadbeef,
- NULL);
- #else
- fprintf(stderr, "Warning: option --alloc-torture ignored, no torture allocator support\n");
- fflush(stderr);
- #endif
- }
- if (!ctx && alloc_provider == ALLOC_HYBRID) {
- #if defined(DUK_CMDLINE_ALLOC_HYBRID)
- void *udata = duk_alloc_hybrid_init();
- if (!udata) {
- fprintf(stderr, "Failed to init hybrid allocator\n");
- fflush(stderr);
- } else {
- ctx = duk_create_heap(duk_alloc_hybrid,
- duk_realloc_hybrid,
- duk_free_hybrid,
- udata,
- NULL);
- }
- #else
- fprintf(stderr, "Warning: option --alloc-hybrid ignored, no hybrid allocator support\n");
- fflush(stderr);
- #endif
- }
- if (!ctx && alloc_provider == ALLOC_AJSHEAP) {
- #if defined(DUK_CMDLINE_AJSHEAP)
- ajsheap_init();
- ctx = duk_create_heap(
- ajsheap_log ? ajsheap_alloc_wrapped : AJS_Alloc,
- ajsheap_log ? ajsheap_realloc_wrapped : AJS_Realloc,
- ajsheap_log ? ajsheap_free_wrapped : AJS_Free,
- (void *) 0xdeadbeef, /* heap_udata: ignored by AjsHeap, use as marker */
- NULL
- ); /* fatal_handler */
- #else
- fprintf(stderr, "Warning: option --alloc-ajsheap ignored, no ajsheap allocator support\n");
- fflush(stderr);
- #endif
- }
- if (!ctx && alloc_provider == ALLOC_DEFAULT) {
- ctx = duk_create_heap_default();
- }
- if (!ctx) {
- fprintf(stderr, "Failed to create Duktape heap\n");
- fflush(stderr);
- exit(-1);
- }
- #if defined(DUK_CMDLINE_AJSHEAP)
- if (alloc_provider == ALLOC_AJSHEAP) {
- fprintf(stdout, "Pool dump after heap creation\n");
- ajsheap_dump();
- }
- #endif
- #if defined(DUK_CMDLINE_AJSHEAP)
- if (alloc_provider == ALLOC_AJSHEAP) {
- ajsheap_register(ctx);
- }
- #endif
- #if defined(DUK_CMDLINE_FILEIO)
- duk_push_c_function(ctx, fileio_read_file, 1 /*nargs*/);
- duk_put_global_string(ctx, "readFile");
- duk_push_c_function(ctx, fileio_write_file, 2 /*nargs*/);
- duk_put_global_string(ctx, "writeFile");
- #endif
- if (debugger) {
- #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
- fprintf(stderr, "Debugger enabled, create socket and wait for connection\n");
- fflush(stderr);
- duk_trans_socket_init();
- duk_trans_socket_waitconn();
- fprintf(stderr, "Debugger connected, call duk_debugger_attach() and then execute requested file(s)/eval\n");
- fflush(stderr);
- duk_debugger_attach_custom(ctx,
- duk_trans_socket_read_cb,
- duk_trans_socket_write_cb,
- duk_trans_socket_peek_cb,
- duk_trans_socket_read_flush_cb,
- duk_trans_socket_write_flush_cb,
- debugger_request,
- debugger_detached,
- (void *) ctx);
- #else
- fprintf(stderr, "Warning: option --debugger ignored, no debugger support\n");
- fflush(stderr);
- #endif
- }
- #if 0
- /* Manual test for duk_debugger_cooperate() */
- {
- for (i = 0; i < 60; i++) {
- printf("cooperate: %d\n", i);
- usleep(1000000);
- duk_debugger_cooperate(ctx);
- }
- }
- #endif
- return ctx;
- }
- static void destroy_duktape_heap(duk_context *ctx, int alloc_provider) {
- (void) alloc_provider;
- #if defined(DUK_CMDLINE_AJSHEAP)
- if (alloc_provider == ALLOC_AJSHEAP) {
- fprintf(stdout, "Pool dump before duk_destroy_heap(), before forced gc\n");
- ajsheap_dump();
- duk_gc(ctx, 0);
- fprintf(stdout, "Pool dump before duk_destroy_heap(), after forced gc\n");
- ajsheap_dump();
- }
- #endif
- if (ctx) {
- duk_destroy_heap(ctx);
- }
- #if defined(DUK_CMDLINE_AJSHEAP)
- if (alloc_provider == ALLOC_AJSHEAP) {
- fprintf(stdout, "Pool dump after duk_destroy_heap() (should have zero allocs)\n");
- ajsheap_dump();
- }
- ajsheap_free();
- #endif
- }
- /*
- * Main
- */
- 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 alloc_provider = ALLOC_DEFAULT;
- int ajsheap_log = 0;
- int debugger = 0;
- int recreate_heap = 0;
- int no_heap_destroy = 0;
- int verbose = 0;
- int run_stdin = 0;
- const char *compile_filename = NULL;
- int i;
- main_argc = argc;
- main_argv = (char **) argv;
- #if defined(EMSCRIPTEN)
- /* Try to use NODEFS to provide access to local files. Mount the
- * CWD as /working, and then prepend "/working/" to relative native
- * paths in file calls to get something that works reasonably for
- * relative paths. Emscripten doesn't support replacing virtual
- * "/" with host "/" (the default MEMFS at "/" can't be unmounted)
- * but we can mount "/tmp" as host "/tmp" to allow testcase runs.
- *
- * https://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html#filesystem-api-nodefs
- * https://github.com/kripken/emscripten/blob/master/tests/fs/test_nodefs_rw.c
- */
- EM_ASM(
- /* At the moment it's not possible to replace the default MEMFS mounted at '/':
- * https://github.com/kripken/emscripten/issues/2040
- * https://github.com/kripken/emscripten/blob/incoming/src/library_fs.js#L1341-L1358
- */
- /*
- try {
- FS.unmount("/");
- } catch (e) {
- console.log("Failed to unmount default '/' MEMFS mount: " + e);
- }
- */
- try {
- FS.mkdir("/working");
- FS.mount(NODEFS, { root: "." }, "/working");
- } catch (e) {
- console.log("Failed to mount NODEFS /working: " + e);
- }
- /* A virtual '/tmp' exists by default:
- * https://gist.github.com/evanw/e6be28094f34451bd5bd#file-temp-js-L3806-L3809
- */
- /*
- try {
- FS.mkdir("/tmp");
- } catch (e) {
- console.log("Failed to create virtual /tmp: " + e);
- }
- */
- try {
- FS.mount(NODEFS, { root: "/tmp" }, "/tmp");
- } catch (e) {
- console.log("Failed to mount NODEFS /tmp: " + e);
- }
- );
- #endif /* EMSCRIPTEN */
- #if defined(DUK_CMDLINE_AJSHEAP)
- alloc_provider = ALLOC_AJSHEAP;
- #endif
- (void) ajsheap_log;
- /*
- * Signal handling setup
- */
- #if defined(DUK_CMDLINE_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, "--restrict-memory") == 0) {
- memlimit_high = 0;
- } else if (strcmp(arg, "-i") == 0) {
- interactive = 1;
- } else if (strcmp(arg, "-c") == 0) {
- if (i == argc - 1) {
- goto usage;
- }
- i++;
- compile_filename = argv[i];
- } else if (strcmp(arg, "-e") == 0) {
- have_eval = 1;
- if (i == argc - 1) {
- goto usage;
- }
- i++; /* skip code */
- } else if (strcmp(arg, "--alloc-default") == 0) {
- alloc_provider = ALLOC_DEFAULT;
- } else if (strcmp(arg, "--alloc-logging") == 0) {
- alloc_provider = ALLOC_LOGGING;
- } else if (strcmp(arg, "--alloc-torture") == 0) {
- alloc_provider = ALLOC_TORTURE;
- } else if (strcmp(arg, "--alloc-hybrid") == 0) {
- alloc_provider = ALLOC_HYBRID;
- } else if (strcmp(arg, "--alloc-ajsheap") == 0) {
- alloc_provider = ALLOC_AJSHEAP;
- } else if (strcmp(arg, "--ajsheap-log") == 0) {
- ajsheap_log = 1;
- } else if (strcmp(arg, "--debugger") == 0) {
- debugger = 1;
- #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
- } else if (strcmp(arg, "--reattach") == 0) {
- debugger_reattach = 1;
- #endif
- } else if (strcmp(arg, "--recreate-heap") == 0) {
- recreate_heap = 1;
- } else if (strcmp(arg, "--no-heap-destroy") == 0) {
- no_heap_destroy = 1;
- } else if (strcmp(arg, "--verbose") == 0) {
- verbose = 1;
- } else if (strcmp(arg, "--run-stdin") == 0) {
- run_stdin = 1;
- } else if (strlen(arg) >= 1 && arg[0] == '-') {
- goto usage;
- } else {
- have_files = 1;
- }
- }
- if (!have_files && !have_eval && !run_stdin) {
- interactive = 1;
- }
- /*
- * Memory limit
- */
- #if defined(DUK_CMDLINE_RLIMIT)
- set_resource_limits(memlimit_high ? MEM_LIMIT_HIGH : MEM_LIMIT_NORMAL);
- #else
- if (memlimit_high == 0) {
- fprintf(stderr, "Warning: option --restrict-memory ignored, no rlimit support\n");
- fflush(stderr);
- }
- #endif
- /*
- * Create heap
- */
- ctx = create_duktape_heap(alloc_provider, debugger, ajsheap_log);
- /*
- * Execute any argument file(s)
- */
- 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) == 2 && strcmp(arg, "-c") == 0) {
- i++; /* skip filename */
- continue;
- } else if (strlen(arg) >= 1 && arg[0] == '-') {
- continue;
- }
- if (verbose) {
- fprintf(stderr, "*** Executing file: %s\n", arg);
- fflush(stderr);
- }
- if (handle_file(ctx, arg, compile_filename) != 0) {
- retval = 1;
- goto cleanup;
- }
- if (recreate_heap) {
- if (verbose) {
- fprintf(stderr, "*** Recreating heap...\n");
- fflush(stderr);
- }
- destroy_duktape_heap(ctx, alloc_provider);
- ctx = create_duktape_heap(alloc_provider, debugger, ajsheap_log);
- }
- }
- if (run_stdin) {
- /* Running stdin like a full file (reading all lines before
- * compiling) is useful with emduk:
- * cat test.js | ./emduk --run-stdin
- */
- if (handle_fh(ctx, stdin, "stdin", compile_filename) != 0) {
- retval = 1;
- goto cleanup;
- }
- if (recreate_heap) {
- if (verbose) {
- fprintf(stderr, "*** Recreating heap...\n");
- fflush(stderr);
- }
- destroy_duktape_heap(ctx, alloc_provider);
- ctx = create_duktape_heap(alloc_provider, debugger, ajsheap_log);
- }
- }
- /*
- * 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 && no_heap_destroy) {
- duk_gc(ctx, 0);
- }
- if (ctx && !no_heap_destroy) {
- destroy_duktape_heap(ctx, alloc_provider);
- }
- ctx = NULL;
- return retval;
- /*
- * Usage
- */
- usage:
- fprintf(stderr, "Usage: duk [options] [<filenames>]\n"
- "\n"
- " -i enter interactive mode after executing argument file(s) / eval code\n"
- " -e CODE evaluate code\n"
- " -c FILE compile into bytecode (use with only one file argument)\n"
- " --run-stdin treat stdin like a file, i.e. compile full input (not line by line)\n"
- " --verbose verbose messages to stderr\n"
- " --restrict-memory use lower memory limit (used by test runner)\n"
- " --alloc-default use Duktape default allocator\n"
- #if defined(DUK_CMDLINE_ALLOC_LOGGING)
- " --alloc-logging use logging allocator (writes to /tmp)\n"
- #endif
- #if defined(DUK_CMDLINE_ALLOC_TORTURE)
- " --alloc-torture use torture allocator\n"
- #endif
- #if defined(DUK_CMDLINE_ALLOC_HYBRID)
- " --alloc-hybrid use hybrid allocator\n"
- #endif
- #if defined(DUK_CMDLINE_AJSHEAP)
- " --alloc-ajsheap use ajsheap allocator (enabled by default with 'ajduk')\n"
- " --ajsheap-log write alloc log to /tmp/ajduk-alloc-log.txt\n"
- #endif
- #if defined(DUK_CMDLINE_DEBUGGER_SUPPORT)
- " --debugger start example debugger\n"
- " --reattach automatically reattach debugger on detach\n"
- #endif
- " --recreate-heap recreate heap after every file\n"
- " --no-heap-destroy force GC, but don't destroy heap at end (leak testing)\n"
- "\n"
- "If <filename> is omitted, interactive mode is started automatically.\n");
- fflush(stderr);
- exit(1);
- }
|