/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sw=4 et tw=99: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #define __STDC_LIMIT_MACROS /* * JS shell. */ #include #include #include #include #include #include #include #include "jstypes.h" #include "jsstdint.h" #include "jsarena.h" #include "jsutil.h" #include "jsprf.h" #include "jswrapper.h" #include "jsapi.h" #include "jsarray.h" #include "jsatom.h" #include "jsbuiltins.h" #include "jscntxt.h" #include "jsdate.h" #include "jsdbgapi.h" #include "jsemit.h" #include "jsfun.h" #include "jsgc.h" #include "jsiter.h" #include "jslock.h" #include "jsnum.h" #include "jsobj.h" #include "jsparse.h" #include "jsreflect.h" #include "jsscope.h" #include "jsscript.h" #include "jstypedarray.h" #include "jsxml.h" #include "jsperf.h" #include "prmjtime.h" #ifdef JSDEBUGGER #include "jsdebug.h" #ifdef JSDEBUGGER_JAVA_UI #include "jsdjava.h" #endif /* JSDEBUGGER_JAVA_UI */ #ifdef JSDEBUGGER_C_UI #include "jsdb.h" #endif /* JSDEBUGGER_C_UI */ #endif /* JSDEBUGGER */ #include "jsworkers.h" #include "jsinterpinlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #ifdef XP_UNIX #include #include #include #endif #if defined(XP_WIN) || defined(XP_OS2) #include /* for isatty() */ #endif #ifdef XP_WIN #include "jswin.h" #endif using namespace js; typedef enum JSShellExitCode { EXITCODE_RUNTIME_ERROR = 3, EXITCODE_FILE_NOT_FOUND = 4, EXITCODE_OUT_OF_MEMORY = 5, EXITCODE_TIMEOUT = 6 } JSShellExitCode; size_t gStackChunkSize = 8192; /* Assume that we can not use more than 5e5 bytes of C stack by default. */ #if (defined(DEBUG) && defined(__SUNPRO_CC)) || defined(JS_CPU_SPARC) /* Sun compiler uses larger stack space for js_Interpret() with debug Use a bigger gMaxStackSize to make "make check" happy. */ #define DEFAULT_MAX_STACK_SIZE 5000000 #else #define DEFAULT_MAX_STACK_SIZE 500000 #endif size_t gMaxStackSize = DEFAULT_MAX_STACK_SIZE; #ifdef JS_THREADSAFE static PRUintn gStackBaseThreadIndex; #else static jsuword gStackBase; #endif static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA; /* * Limit the timeout to 30 minutes to prevent an overflow on platfoms * that represent the time internally in microseconds using 32-bit int. */ static jsdouble MAX_TIMEOUT_INTERVAL = 1800.0; static jsdouble gTimeoutInterval = -1.0; static volatile bool gCanceled = false; static bool enableTraceJit = false; static bool enableMethodJit = false; static bool enableProfiling = false; static bool printTiming = false; static JSBool SetTimeoutValue(JSContext *cx, jsdouble t); static bool InitWatchdog(JSRuntime *rt); static void KillWatchdog(); static bool ScheduleWatchdog(JSRuntime *rt, jsdouble t); static void CancelExecution(JSRuntime *rt); /* * Watchdog thread state. */ #ifdef JS_THREADSAFE static PRLock *gWatchdogLock = NULL; static PRCondVar *gWatchdogWakeup = NULL; static PRThread *gWatchdogThread = NULL; static bool gWatchdogHasTimeout = false; static PRIntervalTime gWatchdogTimeout = 0; static PRCondVar *gSleepWakeup = NULL; #else static JSRuntime *gRuntime = NULL; #endif int gExitCode = 0; JSBool gQuitting = JS_FALSE; FILE *gErrFile = NULL; FILE *gOutFile = NULL; #ifdef JS_THREADSAFE JSObject *gWorkers = NULL; js::workers::ThreadPool *gWorkerThreadPool = NULL; #endif static JSBool reportWarnings = JS_TRUE; static JSBool compileOnly = JS_FALSE; #ifdef DEBUG static JSBool OOM_printAllocationCount = JS_FALSE; #endif typedef enum JSShellErrNum { #define MSG_DEF(name, number, count, exception, format) \ name = number, #include "jsshell.msg" #undef MSG_DEF JSShellErr_Limit #undef MSGDEF } JSShellErrNum; static JSContext * NewContext(JSRuntime *rt); static void DestroyContext(JSContext *cx, bool withGC); static const JSErrorFormatString * my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber); static JSObject * split_setup(JSContext *cx, JSBool evalcx); #ifdef EDITLINE JS_BEGIN_EXTERN_C JS_EXTERN_API(char) *readline(const char *prompt); JS_EXTERN_API(void) add_history(char *line); JS_END_EXTERN_C #endif static void ReportException(JSContext *cx) { if (JS_IsExceptionPending(cx)) { if (!JS_ReportPendingException(cx)) JS_ClearPendingException(cx); } } class ToString { public: ToString(JSContext *aCx, jsval v, JSBool aThrow = JS_FALSE) : cx(aCx), mThrow(aThrow) { mStr = JS_ValueToString(cx, v); if (!aThrow && !mStr) ReportException(cx); JS_AddNamedStringRoot(cx, &mStr, "Value ToString helper"); } ~ToString() { JS_RemoveStringRoot(cx, &mStr); } JSBool threw() { return !mStr; } jsval getJSVal() { return STRING_TO_JSVAL(mStr); } const char *getBytes() { if (mStr && (mBytes.ptr() || mBytes.encode(cx, mStr))) return mBytes.ptr(); return "(error converting value)"; } private: JSContext *cx; JSString *mStr; JSBool mThrow; JSAutoByteString mBytes; }; class IdToString : public ToString { public: IdToString(JSContext *cx, jsid id, JSBool aThrow = JS_FALSE) : ToString(cx, IdToJsval(id), aThrow) { } }; static char * GetLine(FILE *file, const char * prompt) { size_t size; char *buffer; #ifdef EDITLINE /* * Use readline only if file is stdin, because there's no way to specify * another handle. Are other filehandles interactive? */ if (file == stdin) { char *linep = readline(prompt); /* * We set it to zero to avoid complaining about inappropriate ioctl * for device in the case of EOF. Looks like errno == 251 if line is * finished with EOF and errno == 25 (EINVAL on Mac) if there is * nothing left to read. */ if (errno == 251 || errno == 25 || errno == EINVAL) errno = 0; if (!linep) return NULL; if (linep[0] != '\0') add_history(linep); return linep; } #endif size_t len = 0; if (*prompt != '\0') { fprintf(gOutFile, "%s", prompt); fflush(gOutFile); } size = 80; buffer = (char *) malloc(size); if (!buffer) return NULL; char *current = buffer; while (fgets(current, size - len, file)) { len += strlen(current); char *t = buffer + len - 1; if (*t == '\n') { /* Line was read. We remove '\n' and exit. */ *t = '\0'; return buffer; } if (len + 1 == size) { size = size * 2; char *tmp = (char *) realloc(buffer, size); if (!tmp) { free(buffer); return NULL; } buffer = tmp; } current = buffer + len; } if (len && !ferror(file)) return buffer; free(buffer); return NULL; } /* * State to store as JSContext private. * * We declare such timestamp as volatile as they are updated in the operation * callback without taking any locks. Any possible race can only lead to more * frequent callback calls. This is safe as the callback does everything based * on timing. */ struct JSShellContextData { volatile JSIntervalTime startTime; }; static JSShellContextData * NewContextData() { /* Prevent creation of new contexts after we have been canceled. */ if (gCanceled) return NULL; JSShellContextData *data = (JSShellContextData *) calloc(sizeof(JSShellContextData), 1); if (!data) return NULL; data->startTime = js_IntervalNow(); return data; } static inline JSShellContextData * GetContextData(JSContext *cx) { JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx); JS_ASSERT(data); return data; } static JSBool ShellOperationCallback(JSContext *cx) { if (!gCanceled) return JS_TRUE; JS_ClearPendingException(cx); return JS_FALSE; } static void SetContextOptions(JSContext *cx) { JS_SetNativeStackQuota(cx, gMaxStackSize); JS_SetScriptStackQuota(cx, gScriptStackQuota); JS_SetOperationCallback(cx, ShellOperationCallback); } #ifdef WINCE int errno; #endif static void Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY) { JSBool ok, hitEOF; JSObject *scriptObj; jsval result; JSString *str; char *buffer; size_t size; int lineno; int startline; FILE *file; uint32 oldopts; if (forceTTY || !filename || strcmp(filename, "-") == 0) { file = stdin; } else { file = fopen(filename, "r"); if (!file) { JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_CANT_OPEN, filename, strerror(errno)); gExitCode = EXITCODE_FILE_NOT_FOUND; return; } } SetContextOptions(cx); #ifndef WINCE /* windows mobile (and possibly other os's) does not have a TTY */ if (!forceTTY && !isatty(fileno(file))) #endif { /* * It's not interactive - just execute it. * * Support the UNIX #! shell hack; gobble the first line if it starts * with '#'. TODO - this isn't quite compatible with sharp variables, * as a legal js program (using sharp variables) might start with '#'. * But that would require multi-character lookahead. */ int ch = fgetc(file); if (ch == '#') { while((ch = fgetc(file)) != EOF) { if (ch == '\n' || ch == '\r') break; } } ungetc(ch, file); int64 t1 = PRMJ_Now(); oldopts = JS_GetOptions(cx); JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); scriptObj = JS_CompileFileHandle(cx, obj, filename, file); JS_SetOptions(cx, oldopts); if (scriptObj && !compileOnly) { (void) JS_ExecuteScript(cx, obj, scriptObj, NULL); int64 t2 = PRMJ_Now() - t1; if (printTiming) printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC); } goto cleanup; } /* It's an interactive filehandle; drop into read-eval-print loop. */ lineno = 1; hitEOF = JS_FALSE; buffer = NULL; size = 0; /* assign here to avoid warnings */ do { /* * Accumulate lines until we get a 'compilable unit' - one that either * generates an error (before running out of source) or that compiles * cleanly. This should be whenever we get a complete statement that * coincides with the end of a line. */ startline = lineno; size_t len = 0; /* initialize to avoid warnings */ do { ScheduleWatchdog(cx->runtime, -1); gCanceled = false; errno = 0; char *line; { JSAutoSuspendRequest suspended(cx); line = GetLine(file, startline == lineno ? "js> " : ""); } if (!line) { if (errno) { JS_ReportError(cx, strerror(errno)); free(buffer); goto cleanup; } hitEOF = JS_TRUE; break; } if (!buffer) { buffer = line; len = strlen(buffer); size = len + 1; } else { /* * len + 1 is required to store '\n' in the end of line. */ size_t newlen = strlen(line) + (len ? len + 1 : 0); if (newlen + 1 > size) { size = newlen + 1 > size * 2 ? newlen + 1 : size * 2; char *newBuf = (char *) realloc(buffer, size); if (!newBuf) { free(buffer); free(line); JS_ReportOutOfMemory(cx); goto cleanup; } buffer = newBuf; } char *current = buffer + len; if (startline != lineno) *current++ = '\n'; strcpy(current, line); len = newlen; free(line); } lineno++; if (!ScheduleWatchdog(cx->runtime, gTimeoutInterval)) { hitEOF = JS_TRUE; break; } } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, len)); if (hitEOF && !buffer) break; /* Clear any pending exception from previous failed compiles. */ JS_ClearPendingException(cx); /* Even though we're interactive, we have a compile-n-go opportunity. */ oldopts = JS_GetOptions(cx); if (!compileOnly) JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO); scriptObj = JS_CompileScript(cx, obj, buffer, len, "typein", startline); if (!compileOnly) JS_SetOptions(cx, oldopts); if (scriptObj && !compileOnly) { ok = JS_ExecuteScript(cx, obj, scriptObj, &result); if (ok && !JSVAL_IS_VOID(result)) { str = JS_ValueToSource(cx, result); ok = !!str; if (ok) { JSAutoByteString bytes(cx, str); ok = !!bytes; if (ok) fprintf(gOutFile, "%s\n", bytes.ptr()); } } } *buffer = '\0'; } while (!hitEOF && !gQuitting); free(buffer); fprintf(gOutFile, "\n"); cleanup: if (file != stdin) fclose(file); return; } static int usage(void) { fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); fprintf(gErrFile, "usage: js [options] [scriptfile] [scriptarg...]\n" "Options:\n" " -h Display this information\n" " -z Create a split global object\n" " Warning: this option is probably not useful\n" " -P Deeply freeze the global object prototype\n" " -s Toggle JSOPTION_STRICT flag\n" " -w Report strict warnings\n" " -W Do not report strict warnings\n" " -x Toggle JSOPTION_XML flag\n" " -C Compile-only; do not execute\n" " -i Enable interactive read-eval-print loop\n" " -j Enable the TraceMonkey tracing JIT\n" " -m Enable the JaegerMonkey method JIT\n" " -a Always method JIT, ignore internal tuning\n" " This only has effect with -m\n" " -p Enable loop profiling for TraceMonkey\n" " -d Enable debug mode\n" " -b Print timing statistics\n" " -t Interrupt long-running execution after seconds, where\n" " <= 1800.0. Negative values indicate no timeout (default).\n" " -c Suggest stack chunk size of bytes. Default is 8192.\n" " Warning: this option is currently ignored.\n" " -o