michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* JS shell. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Atomics.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/GuardObjects.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #ifdef XP_WIN michael@0: # include michael@0: # include michael@0: #endif michael@0: #include michael@0: #include michael@0: #if defined(XP_WIN) michael@0: # include /* for isatty() */ michael@0: #endif michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #ifdef XP_UNIX michael@0: # include michael@0: # include michael@0: # include michael@0: # include michael@0: #endif michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsarray.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfun.h" michael@0: #ifdef JS_THREADSAFE michael@0: #include "jslock.h" michael@0: #endif michael@0: #include "jsobj.h" michael@0: #include "jsprf.h" michael@0: #include "jsscript.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: #ifdef XP_WIN michael@0: # include "jswin.h" michael@0: #endif michael@0: #include "jsworkers.h" michael@0: #include "jswrapper.h" michael@0: #include "prmjtime.h" michael@0: michael@0: #include "builtin/TestingFunctions.h" michael@0: #include "frontend/Parser.h" michael@0: #include "jit/arm/Simulator-arm.h" michael@0: #include "jit/Ion.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "js/StructuredClone.h" michael@0: #include "perf/jsperf.h" michael@0: #include "shell/jsheaptools.h" michael@0: #include "shell/jsoptparse.h" michael@0: #include "vm/ArgumentsObject.h" michael@0: #include "vm/Monitor.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/TypedArrayObject.h" michael@0: #include "vm/WrapperObject.h" michael@0: michael@0: #include "jscompartmentinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: #ifdef XP_WIN michael@0: # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR) michael@0: #else michael@0: # include michael@0: #endif michael@0: michael@0: using namespace js; michael@0: using namespace js::cli; michael@0: michael@0: using mozilla::ArrayLength; michael@0: using mozilla::NumberEqualsInt32; michael@0: using mozilla::Maybe; michael@0: using mozilla::PodCopy; michael@0: michael@0: enum JSShellExitCode { michael@0: EXITCODE_RUNTIME_ERROR = 3, michael@0: EXITCODE_FILE_NOT_FOUND = 4, michael@0: EXITCODE_OUT_OF_MEMORY = 5, michael@0: EXITCODE_TIMEOUT = 6 michael@0: }; michael@0: michael@0: enum PathResolutionMode { michael@0: RootRelative, michael@0: ScriptRelative michael@0: }; michael@0: michael@0: static size_t gStackChunkSize = 8192; michael@0: michael@0: /* michael@0: * Note: This limit should match the stack limit set by the browser in michael@0: * js/xpconnect/src/XPCJSRuntime.cpp michael@0: */ michael@0: #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN)) michael@0: static size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; michael@0: #else michael@0: static size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; michael@0: #endif michael@0: michael@0: /* michael@0: * Limit the timeout to 30 minutes to prevent an overflow on platfoms michael@0: * that represent the time internally in microseconds using 32-bit int. michael@0: */ michael@0: static double MAX_TIMEOUT_INTERVAL = 1800.0; michael@0: static double gTimeoutInterval = -1.0; michael@0: static volatile bool gServiceInterrupt = false; michael@0: static Maybe gInterruptFunc; michael@0: michael@0: static bool enableDisassemblyDumps = false; michael@0: michael@0: static bool printTiming = false; michael@0: static const char *jsCacheDir = nullptr; michael@0: static const char *jsCacheAsmJSPath = nullptr; michael@0: static bool jsCachingEnabled = false; michael@0: mozilla::Atomic jsCacheOpened(false); michael@0: michael@0: static bool michael@0: SetTimeoutValue(JSContext *cx, double t); michael@0: michael@0: static bool michael@0: InitWatchdog(JSRuntime *rt); michael@0: michael@0: static void michael@0: KillWatchdog(); michael@0: michael@0: static bool michael@0: ScheduleWatchdog(JSRuntime *rt, double t); michael@0: michael@0: static void michael@0: CancelExecution(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Watchdog thread state. michael@0: */ michael@0: #ifdef JS_THREADSAFE michael@0: michael@0: static PRLock *gWatchdogLock = nullptr; michael@0: static PRCondVar *gWatchdogWakeup = nullptr; michael@0: static PRThread *gWatchdogThread = nullptr; michael@0: static bool gWatchdogHasTimeout = false; michael@0: static int64_t gWatchdogTimeout = 0; michael@0: michael@0: static PRCondVar *gSleepWakeup = nullptr; michael@0: michael@0: #else michael@0: michael@0: static JSRuntime *gRuntime = nullptr; michael@0: michael@0: #endif michael@0: michael@0: static int gExitCode = 0; michael@0: static bool gQuitting = false; michael@0: static bool gGotError = false; michael@0: static FILE *gErrFile = nullptr; michael@0: static FILE *gOutFile = nullptr; michael@0: michael@0: static bool reportWarnings = true; michael@0: static bool compileOnly = false; michael@0: static bool fuzzingSafe = false; michael@0: michael@0: #ifdef DEBUG michael@0: static bool dumpEntrainedVariables = false; michael@0: static bool OOM_printAllocationCount = false; michael@0: #endif michael@0: michael@0: enum JSShellErrNum { michael@0: #define MSG_DEF(name, number, count, exception, format) \ michael@0: name = number, michael@0: #include "jsshell.msg" michael@0: #undef MSG_DEF michael@0: JSShellErr_Limit michael@0: }; michael@0: michael@0: static JSContext * michael@0: NewContext(JSRuntime *rt); michael@0: michael@0: static void michael@0: DestroyContext(JSContext *cx, bool withGC); michael@0: michael@0: static JSObject * michael@0: NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options, michael@0: JSPrincipals *principals); michael@0: michael@0: static const JSErrorFormatString * michael@0: my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber); michael@0: michael@0: michael@0: /* michael@0: * A toy principals type for the shell. michael@0: * michael@0: * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the michael@0: * set bits in P are a superset of those in Q. Thus, the principal 0 is michael@0: * subsumed by everything, and the principal ~0 subsumes everything. michael@0: * michael@0: * As a special case, a null pointer as a principal is treated like 0xffff. michael@0: * michael@0: * The 'newGlobal' function takes an option indicating which principal the michael@0: * new global should have; 'evaluate' does for the new code. michael@0: */ michael@0: class ShellPrincipals: public JSPrincipals { michael@0: uint32_t bits; michael@0: michael@0: static uint32_t getBits(JSPrincipals *p) { michael@0: if (!p) michael@0: return 0xffff; michael@0: return static_cast(p)->bits; michael@0: } michael@0: michael@0: public: michael@0: ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) { michael@0: this->refcount = refcount; michael@0: } michael@0: michael@0: static void destroy(JSPrincipals *principals) { michael@0: MOZ_ASSERT(principals != &fullyTrusted); michael@0: MOZ_ASSERT(principals->refcount == 0); michael@0: js_free(static_cast(principals)); michael@0: } michael@0: michael@0: static bool subsumes(JSPrincipals *first, JSPrincipals *second) { michael@0: uint32_t firstBits = getBits(first); michael@0: uint32_t secondBits = getBits(second); michael@0: return (firstBits | secondBits) == firstBits; michael@0: } michael@0: michael@0: static JSSecurityCallbacks securityCallbacks; michael@0: michael@0: // Fully-trusted principals singleton. michael@0: static ShellPrincipals fullyTrusted; michael@0: }; michael@0: michael@0: JSSecurityCallbacks ShellPrincipals::securityCallbacks = { michael@0: nullptr, // contentSecurityPolicyAllows michael@0: subsumes michael@0: }; michael@0: michael@0: // The fully-trusted principal subsumes all other principals. michael@0: ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1); michael@0: michael@0: #ifdef EDITLINE michael@0: extern "C" { michael@0: extern JS_EXPORT_API(char *) readline(const char *prompt); michael@0: extern JS_EXPORT_API(void) add_history(char *line); michael@0: } // extern "C" michael@0: #endif michael@0: michael@0: static char * michael@0: GetLine(FILE *file, const char * prompt) michael@0: { michael@0: size_t size; michael@0: char *buffer; michael@0: #ifdef EDITLINE michael@0: /* michael@0: * Use readline only if file is stdin, because there's no way to specify michael@0: * another handle. Are other filehandles interactive? michael@0: */ michael@0: if (file == stdin) { michael@0: char *linep = readline(prompt); michael@0: /* michael@0: * We set it to zero to avoid complaining about inappropriate ioctl michael@0: * for device in the case of EOF. Looks like errno == 251 if line is michael@0: * finished with EOF and errno == 25 (EINVAL on Mac) if there is michael@0: * nothing left to read. michael@0: */ michael@0: if (errno == 251 || errno == 25 || errno == EINVAL) michael@0: errno = 0; michael@0: if (!linep) michael@0: return nullptr; michael@0: if (linep[0] != '\0') michael@0: add_history(linep); michael@0: return linep; michael@0: } michael@0: #endif michael@0: size_t len = 0; michael@0: if (*prompt != '\0') { michael@0: fprintf(gOutFile, "%s", prompt); michael@0: fflush(gOutFile); michael@0: } michael@0: size = 80; michael@0: buffer = (char *) malloc(size); michael@0: if (!buffer) michael@0: return nullptr; michael@0: char *current = buffer; michael@0: while (fgets(current, size - len, file)) { michael@0: len += strlen(current); michael@0: char *t = buffer + len - 1; michael@0: if (*t == '\n') { michael@0: /* Line was read. We remove '\n' and exit. */ michael@0: *t = '\0'; michael@0: return buffer; michael@0: } michael@0: if (len + 1 == size) { michael@0: size = size * 2; michael@0: char *tmp = (char *) js_realloc(buffer, size); michael@0: if (!tmp) { michael@0: free(buffer); michael@0: return nullptr; michael@0: } michael@0: buffer = tmp; michael@0: } michael@0: current = buffer + len; michael@0: } michael@0: if (len && !ferror(file)) michael@0: return buffer; michael@0: free(buffer); michael@0: return nullptr; michael@0: } michael@0: michael@0: static char * michael@0: JSStringToUTF8(JSContext *cx, JSString *str) michael@0: { michael@0: JSLinearString *linear = str->ensureLinear(cx); michael@0: if (!linear) michael@0: return nullptr; michael@0: michael@0: return TwoByteCharsToNewUTF8CharsZ(cx, linear->range()).c_str(); michael@0: } michael@0: michael@0: /* State to store as JSContext private. */ michael@0: struct JSShellContextData { michael@0: /* Creation timestamp, used by the elapsed() shell builtin. */ michael@0: int64_t startTime; michael@0: }; michael@0: michael@0: static JSShellContextData * michael@0: NewContextData() michael@0: { michael@0: /* Prevent creation of new contexts after we have been canceled. */ michael@0: if (gServiceInterrupt) michael@0: return nullptr; michael@0: michael@0: JSShellContextData *data = (JSShellContextData *) michael@0: js_calloc(sizeof(JSShellContextData), 1); michael@0: if (!data) michael@0: return nullptr; michael@0: data->startTime = PRMJ_Now(); michael@0: return data; michael@0: } michael@0: michael@0: static inline JSShellContextData * michael@0: GetContextData(JSContext *cx) michael@0: { michael@0: JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx); michael@0: michael@0: JS_ASSERT(data); michael@0: return data; michael@0: } michael@0: michael@0: static bool michael@0: ShellInterruptCallback(JSContext *cx) michael@0: { michael@0: if (!gServiceInterrupt) michael@0: return true; michael@0: michael@0: bool result; michael@0: RootedValue interruptFunc(cx, gInterruptFunc.ref()); michael@0: if (!interruptFunc.isNull()) { michael@0: JS::AutoSaveExceptionState savedExc(cx); michael@0: JSAutoCompartment ac(cx, &interruptFunc.toObject()); michael@0: RootedValue rval(cx); michael@0: if (!JS_CallFunctionValue(cx, JS::NullPtr(), interruptFunc, michael@0: JS::HandleValueArray::empty(), &rval)) michael@0: { michael@0: return false; michael@0: } michael@0: if (rval.isBoolean()) michael@0: result = rval.toBoolean(); michael@0: else michael@0: result = false; michael@0: } else { michael@0: result = false; michael@0: } michael@0: michael@0: if (!result && gExitCode == 0) michael@0: gExitCode = EXITCODE_TIMEOUT; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: * Some UTF-8 files, notably those written using Notepad, have a Unicode michael@0: * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order michael@0: * is meaningless for UTF-8) but causes a syntax error unless we skip it. michael@0: */ michael@0: static void michael@0: SkipUTF8BOM(FILE* file) michael@0: { michael@0: int ch1 = fgetc(file); michael@0: int ch2 = fgetc(file); michael@0: int ch3 = fgetc(file); michael@0: michael@0: // Skip the BOM michael@0: if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) michael@0: return; michael@0: michael@0: // No BOM - revert michael@0: if (ch3 != EOF) michael@0: ungetc(ch3, file); michael@0: if (ch2 != EOF) michael@0: ungetc(ch2, file); michael@0: if (ch1 != EOF) michael@0: ungetc(ch1, file); michael@0: } michael@0: michael@0: static void michael@0: RunFile(JSContext *cx, Handle obj, const char *filename, FILE *file, bool compileOnly) michael@0: { michael@0: SkipUTF8BOM(file); michael@0: michael@0: // To support the UNIX #! shell hack, gobble the first line if it starts michael@0: // with '#'. michael@0: int ch = fgetc(file); michael@0: if (ch == '#') { michael@0: while ((ch = fgetc(file)) != EOF) { michael@0: if (ch == '\n' || ch == '\r') michael@0: break; michael@0: } michael@0: } michael@0: ungetc(ch, file); michael@0: michael@0: int64_t t1 = PRMJ_Now(); michael@0: RootedScript script(cx); michael@0: michael@0: { michael@0: JS::AutoSaveContextOptions asco(cx); michael@0: JS::ContextOptionsRef(cx).setNoScriptRval(true); michael@0: michael@0: CompileOptions options(cx); michael@0: options.setIntroductionType("js shell file") michael@0: .setUTF8(true) michael@0: .setFileAndLine(filename, 1) michael@0: .setCompileAndGo(true); michael@0: michael@0: gGotError = false; michael@0: script = JS::Compile(cx, obj, options, file); michael@0: JS_ASSERT_IF(!script, gGotError); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: if (dumpEntrainedVariables) michael@0: AnalyzeEntrainedVariables(cx, script); michael@0: #endif michael@0: if (script && !compileOnly) { michael@0: if (!JS_ExecuteScript(cx, obj, script)) { michael@0: if (!gQuitting && !gServiceInterrupt) michael@0: gExitCode = EXITCODE_RUNTIME_ERROR; michael@0: } michael@0: int64_t t2 = PRMJ_Now() - t1; michael@0: if (printTiming) michael@0: printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: EvalAndPrint(JSContext *cx, Handle global, const char *bytes, size_t length, michael@0: int lineno, bool compileOnly, FILE *out) michael@0: { michael@0: // Eval. michael@0: JS::CompileOptions options(cx); michael@0: options.setIntroductionType("js shell interactive") michael@0: .setUTF8(true) michael@0: .setCompileAndGo(true) michael@0: .setFileAndLine("typein", lineno); michael@0: RootedScript script(cx); michael@0: script = JS::Compile(cx, global, options, bytes, length); michael@0: if (!script) michael@0: return false; michael@0: if (compileOnly) michael@0: return true; michael@0: RootedValue result(cx); michael@0: if (!JS_ExecuteScript(cx, global, script, &result)) michael@0: return false; michael@0: michael@0: if (!result.isUndefined()) { michael@0: // Print. michael@0: RootedString str(cx); michael@0: str = JS_ValueToSource(cx, result); michael@0: if (!str) michael@0: return false; michael@0: michael@0: char *utf8chars = JSStringToUTF8(cx, str); michael@0: if (!utf8chars) michael@0: return false; michael@0: fprintf(out, "%s\n", utf8chars); michael@0: JS_free(cx, utf8chars); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: ReadEvalPrintLoop(JSContext *cx, Handle global, FILE *in, FILE *out, bool compileOnly) michael@0: { michael@0: int lineno = 1; michael@0: bool hitEOF = false; michael@0: michael@0: do { michael@0: /* michael@0: * Accumulate lines until we get a 'compilable unit' - one that either michael@0: * generates an error (before running out of source) or that compiles michael@0: * cleanly. This should be whenever we get a complete statement that michael@0: * coincides with the end of a line. michael@0: */ michael@0: int startline = lineno; michael@0: typedef Vector CharBuffer; michael@0: CharBuffer buffer(cx); michael@0: do { michael@0: ScheduleWatchdog(cx->runtime(), -1); michael@0: gServiceInterrupt = false; michael@0: errno = 0; michael@0: michael@0: char *line = GetLine(in, startline == lineno ? "js> " : ""); michael@0: if (!line) { michael@0: if (errno) { michael@0: JS_ReportError(cx, strerror(errno)); michael@0: return; michael@0: } michael@0: hitEOF = true; michael@0: break; michael@0: } michael@0: michael@0: if (!buffer.append(line, strlen(line)) || !buffer.append('\n')) michael@0: return; michael@0: michael@0: lineno++; michael@0: if (!ScheduleWatchdog(cx->runtime(), gTimeoutInterval)) { michael@0: hitEOF = true; michael@0: break; michael@0: } michael@0: } while (!JS_BufferIsCompilableUnit(cx, global, buffer.begin(), buffer.length())); michael@0: michael@0: if (hitEOF && buffer.empty()) michael@0: break; michael@0: michael@0: if (!EvalAndPrint(cx, global, buffer.begin(), buffer.length(), startline, compileOnly, michael@0: out)) michael@0: { michael@0: // Catch the error, report it, and keep going. michael@0: JS_ReportPendingException(cx); michael@0: } michael@0: } while (!hitEOF && !gQuitting); michael@0: michael@0: fprintf(out, "\n"); michael@0: } michael@0: michael@0: class AutoCloseInputFile michael@0: { michael@0: private: michael@0: FILE *f_; michael@0: public: michael@0: explicit AutoCloseInputFile(FILE *f) : f_(f) {} michael@0: ~AutoCloseInputFile() { michael@0: if (f_ && f_ != stdin) michael@0: fclose(f_); michael@0: } michael@0: }; michael@0: michael@0: static void michael@0: Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY) michael@0: { michael@0: RootedObject obj(cx, obj_); michael@0: michael@0: FILE *file; michael@0: if (forceTTY || !filename || strcmp(filename, "-") == 0) { michael@0: file = stdin; michael@0: } else { michael@0: file = fopen(filename, "r"); michael@0: if (!file) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_CANT_OPEN, filename, strerror(errno)); michael@0: gExitCode = EXITCODE_FILE_NOT_FOUND; michael@0: return; michael@0: } michael@0: } michael@0: AutoCloseInputFile autoClose(file); michael@0: michael@0: if (!forceTTY && !isatty(fileno(file))) { michael@0: // It's not interactive - just execute it. michael@0: RunFile(cx, obj, filename, file, compileOnly); michael@0: } else { michael@0: // It's an interactive filehandle; drop into read-eval-print loop. michael@0: ReadEvalPrintLoop(cx, obj, file, gOutFile, compileOnly); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: Version(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSVersion origVersion = JS_GetVersion(cx); michael@0: if (args.length() == 0 || JSVAL_IS_VOID(args[0])) { michael@0: /* Get version. */ michael@0: args.rval().setInt32(origVersion); michael@0: } else { michael@0: /* Set version. */ michael@0: int32_t v = -1; michael@0: if (args[0].isInt32()) { michael@0: v = args[0].toInt32(); michael@0: } else if (args[0].isDouble()) { michael@0: double fv = args[0].toDouble(); michael@0: int32_t fvi; michael@0: if (NumberEqualsInt32(fv, &fvi)) michael@0: v = fvi; michael@0: } michael@0: if (v < 0 || v > JSVERSION_LATEST) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "version"); michael@0: return false; michael@0: } michael@0: JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v)); michael@0: args.rval().setInt32(origVersion); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Resolve a (possibly) relative filename to an absolute path. If michael@0: * |scriptRelative| is true, then the result will be relative to the directory michael@0: * containing the currently-running script, or the current working directory if michael@0: * the currently-running script is "-e" (namely, you're using it from the michael@0: * command line.) Otherwise, it will be relative to the current working michael@0: * directory. michael@0: */ michael@0: static JSString * michael@0: ResolvePath(JSContext *cx, HandleString filenameStr, PathResolutionMode resolveMode) michael@0: { michael@0: JSAutoByteString filename(cx, filenameStr); michael@0: if (!filename) michael@0: return nullptr; michael@0: michael@0: const char *pathname = filename.ptr(); michael@0: if (pathname[0] == '/') michael@0: return filenameStr; michael@0: #ifdef XP_WIN michael@0: // Various forms of absolute paths per http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx michael@0: // "\..." michael@0: if (pathname[0] == '\\') michael@0: return filenameStr; michael@0: // "C:\..." michael@0: if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\') michael@0: return filenameStr; michael@0: // "\\..." michael@0: if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\') michael@0: return filenameStr; michael@0: #endif michael@0: michael@0: /* Get the currently executing script's name. */ michael@0: JS::AutoFilename scriptFilename; michael@0: if (!DescribeScriptedCaller(cx, &scriptFilename)) michael@0: return nullptr; michael@0: michael@0: if (!scriptFilename.get()) michael@0: return nullptr; michael@0: michael@0: if (strcmp(scriptFilename.get(), "-e") == 0 || strcmp(scriptFilename.get(), "typein") == 0) michael@0: resolveMode = RootRelative; michael@0: michael@0: static char buffer[PATH_MAX+1]; michael@0: if (resolveMode == ScriptRelative) { michael@0: #ifdef XP_WIN michael@0: // The docs say it can return EINVAL, but the compiler says it's void michael@0: _splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr); michael@0: #else michael@0: strncpy(buffer, scriptFilename.get(), PATH_MAX+1); michael@0: if (buffer[PATH_MAX] != '\0') michael@0: return nullptr; michael@0: michael@0: // dirname(buffer) might return buffer, or it might return a michael@0: // statically-allocated string michael@0: memmove(buffer, dirname(buffer), strlen(buffer) + 1); michael@0: #endif michael@0: } else { michael@0: const char *cwd = getcwd(buffer, PATH_MAX); michael@0: if (!cwd) michael@0: return nullptr; michael@0: } michael@0: michael@0: size_t len = strlen(buffer); michael@0: buffer[len] = '/'; michael@0: strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1)); michael@0: if (buffer[PATH_MAX] != '\0') michael@0: return nullptr; michael@0: michael@0: return JS_NewStringCopyZ(cx, buffer); michael@0: } michael@0: michael@0: static bool michael@0: CreateMappedArrayBuffer(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1 || args.length() > 3) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, michael@0: "createMappedArrayBuffer"); michael@0: return false; michael@0: } michael@0: michael@0: RootedString rawFilenameStr(cx, JS::ToString(cx, args[0])); michael@0: if (!rawFilenameStr) michael@0: return false; michael@0: // It's a little bizarre to resolve relative to the script, but for testing michael@0: // I need a file at a known location, and the only good way I know of to do michael@0: // that right now is to include it in the repo alongside the test script. michael@0: // Bug 944164 would introduce an alternative. michael@0: JSString *filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative); michael@0: if (!filenameStr) michael@0: return false; michael@0: JSAutoByteString filename(cx, filenameStr); michael@0: if (!filename) michael@0: return false; michael@0: michael@0: uint32_t offset = 0; michael@0: if (args.length() >= 2) { michael@0: if (!JS::ToUint32(cx, args[1], &offset)) michael@0: return false; michael@0: } michael@0: michael@0: bool sizeGiven = false; michael@0: uint32_t size; michael@0: if (args.length() >= 3) { michael@0: if (!JS::ToUint32(cx, args[2], &size)) michael@0: return false; michael@0: sizeGiven = true; michael@0: if (offset > size) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: FILE *file = fopen(filename.ptr(), "r"); michael@0: if (!file) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_CANT_OPEN, filename.ptr(), strerror(errno)); michael@0: return false; michael@0: } michael@0: AutoCloseInputFile autoClose(file); michael@0: michael@0: if (!sizeGiven) { michael@0: struct stat st; michael@0: if (fstat(fileno(file), &st) < 0) { michael@0: JS_ReportError(cx, "Unable to stat file"); michael@0: return false; michael@0: } michael@0: if (st.st_size < offset) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); michael@0: return false; michael@0: } michael@0: size = st.st_size - offset; michael@0: } michael@0: michael@0: void *contents = JS_CreateMappedArrayBufferContents(fileno(file), offset, size); michael@0: if (!contents) { michael@0: JS_ReportError(cx, "failed to allocate mapped array buffer contents (possibly due to bad alignment)"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject obj(cx, JS_NewMappedArrayBufferWithContents(cx, size, contents)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Options(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx); michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: JSString *str = JS::ToString(cx, args[i]); michael@0: if (!str) michael@0: return false; michael@0: args[i].setString(str); michael@0: michael@0: JSAutoByteString opt(cx, str); michael@0: if (!opt) michael@0: return false; michael@0: michael@0: if (strcmp(opt.ptr(), "strict") == 0) michael@0: JS::ContextOptionsRef(cx).toggleExtraWarnings(); michael@0: else if (strcmp(opt.ptr(), "werror") == 0) michael@0: JS::ContextOptionsRef(cx).toggleWerror(); michael@0: else if (strcmp(opt.ptr(), "strict_mode") == 0) michael@0: JS::ContextOptionsRef(cx).toggleStrictMode(); michael@0: else { michael@0: char* msg = JS_sprintf_append(nullptr, michael@0: "unknown option name '%s'." michael@0: " The valid names are strict," michael@0: " werror, and strict_mode.", michael@0: opt.ptr()); michael@0: if (!msg) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: JS_ReportError(cx, msg); michael@0: free(msg); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: char *names = strdup(""); michael@0: bool found = false; michael@0: if (!names && oldContextOptions.extraWarnings()) { michael@0: names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict"); michael@0: found = true; michael@0: } michael@0: if (!names && oldContextOptions.werror()) { michael@0: names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror"); michael@0: found = true; michael@0: } michael@0: if (!names && oldContextOptions.strictMode()) { michael@0: names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict_mode"); michael@0: found = true; michael@0: } michael@0: if (!names) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, names); michael@0: free(names); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: LoadScript(JSContext *cx, unsigned argc, jsval *vp, bool scriptRelative) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!thisobj) michael@0: return false; michael@0: michael@0: RootedString str(cx); michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: str = JS::ToString(cx, args[i]); michael@0: if (!str) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "load"); michael@0: return false; michael@0: } michael@0: str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative); michael@0: if (!str) { michael@0: JS_ReportError(cx, "unable to resolve path"); michael@0: return false; michael@0: } michael@0: JSAutoByteString filename(cx, str); michael@0: if (!filename) michael@0: return false; michael@0: errno = 0; michael@0: CompileOptions opts(cx); michael@0: opts.setIntroductionType("js shell load") michael@0: .setUTF8(true) michael@0: .setCompileAndGo(true) michael@0: .setNoScriptRval(true); michael@0: if ((compileOnly && !Compile(cx, thisobj, opts, filename.ptr())) || michael@0: !Evaluate(cx, thisobj, opts, filename.ptr())) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Load(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: return LoadScript(cx, argc, vp, false); michael@0: } michael@0: michael@0: static bool michael@0: LoadScriptRelativeToScript(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: return LoadScript(cx, argc, vp, true); michael@0: } michael@0: michael@0: // Populate |options| with the options given by |opts|'s properties. If we michael@0: // need to convert a filename to a C string, let fileNameBytes own the michael@0: // bytes. michael@0: static bool michael@0: ParseCompileOptions(JSContext *cx, CompileOptions &options, HandleObject opts, michael@0: JSAutoByteString &fileNameBytes) michael@0: { michael@0: RootedValue v(cx); michael@0: RootedString s(cx); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "compileAndGo", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: options.setCompileAndGo(ToBoolean(v)); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "noScriptRval", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: options.setNoScriptRval(ToBoolean(v)); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "fileName", &v)) michael@0: return false; michael@0: if (v.isNull()) { michael@0: options.setFile(nullptr); michael@0: } else if (!v.isUndefined()) { michael@0: s = ToString(cx, v); michael@0: if (!s) michael@0: return false; michael@0: char *fileName = fileNameBytes.encodeLatin1(cx, s); michael@0: if (!fileName) michael@0: return false; michael@0: options.setFile(fileName); michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "element", &v)) michael@0: return false; michael@0: if (v.isObject()) michael@0: options.setElement(&v.toObject()); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: s = ToString(cx, v); michael@0: if (!s) michael@0: return false; michael@0: options.setElementAttributeName(s); michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "lineNumber", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: uint32_t u; michael@0: if (!ToUint32(cx, v, &u)) michael@0: return false; michael@0: options.setLine(u); michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "sourceIsLazy", &v)) michael@0: return false; michael@0: if (v.isBoolean()) michael@0: options.setSourceIsLazy(v.toBoolean()); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: class AutoNewContext michael@0: { michael@0: private: michael@0: JSContext *oldcx; michael@0: JSContext *newcx; michael@0: Maybe newRequest; michael@0: Maybe newCompartment; michael@0: michael@0: AutoNewContext(const AutoNewContext &) MOZ_DELETE; michael@0: michael@0: public: michael@0: AutoNewContext() : oldcx(nullptr), newcx(nullptr) {} michael@0: michael@0: bool enter(JSContext *cx) { michael@0: JS_ASSERT(!JS_IsExceptionPending(cx)); michael@0: oldcx = cx; michael@0: newcx = NewContext(JS_GetRuntime(cx)); michael@0: if (!newcx) michael@0: return false; michael@0: JS::ContextOptionsRef(newcx).setDontReportUncaught(true); michael@0: js::SetDefaultObjectForContext(newcx, JS::CurrentGlobalOrNull(cx)); michael@0: michael@0: newRequest.construct(newcx); michael@0: newCompartment.construct(newcx, JS::CurrentGlobalOrNull(cx)); michael@0: return true; michael@0: } michael@0: michael@0: JSContext *get() { return newcx; } michael@0: michael@0: ~AutoNewContext() { michael@0: if (newcx) { michael@0: RootedValue exc(oldcx); michael@0: bool throwing = JS_IsExceptionPending(newcx); michael@0: if (throwing) michael@0: JS_GetPendingException(newcx, &exc); michael@0: newCompartment.destroy(); michael@0: newRequest.destroy(); michael@0: if (throwing) michael@0: JS_SetPendingException(oldcx, exc); michael@0: DestroyContext(newcx, false); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: static const uint32_t CacheEntry_SOURCE = 0; michael@0: static const uint32_t CacheEntry_BYTECODE = 1; michael@0: michael@0: static const JSClass CacheEntry_class = { michael@0: "CacheEntryObject", JSCLASS_HAS_RESERVED_SLOTS(2), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr, /* finalize */ michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: nullptr, /* trace */ michael@0: JSCLASS_NO_INTERNAL_MEMBERS michael@0: }; michael@0: michael@0: static bool michael@0: CacheEntry(JSContext* cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() != 1 || !args[0].isString()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "CacheEntry"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject obj(cx, JS_NewObject(cx, &CacheEntry_class, JS::NullPtr(), JS::NullPtr())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: SetReservedSlot(obj, CacheEntry_SOURCE, args[0]); michael@0: SetReservedSlot(obj, CacheEntry_BYTECODE, UndefinedValue()); michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: CacheEntry_isCacheEntry(JSObject *cache) michael@0: { michael@0: return JS_GetClass(cache) == &CacheEntry_class; michael@0: } michael@0: michael@0: static JSString * michael@0: CacheEntry_getSource(HandleObject cache) michael@0: { michael@0: JS_ASSERT(CacheEntry_isCacheEntry(cache)); michael@0: Value v = JS_GetReservedSlot(cache, CacheEntry_SOURCE); michael@0: if (!v.isString()) michael@0: return nullptr; michael@0: michael@0: return v.toString(); michael@0: } michael@0: michael@0: static uint8_t * michael@0: CacheEntry_getBytecode(HandleObject cache, uint32_t *length) michael@0: { michael@0: JS_ASSERT(CacheEntry_isCacheEntry(cache)); michael@0: Value v = JS_GetReservedSlot(cache, CacheEntry_BYTECODE); michael@0: if (!v.isObject() || !v.toObject().is()) michael@0: return nullptr; michael@0: michael@0: ArrayBufferObject *arrayBuffer = &v.toObject().as(); michael@0: *length = arrayBuffer->byteLength(); michael@0: return arrayBuffer->dataPointer(); michael@0: } michael@0: michael@0: static bool michael@0: CacheEntry_setBytecode(JSContext *cx, HandleObject cache, uint8_t *buffer, uint32_t length) michael@0: { michael@0: JS_ASSERT(CacheEntry_isCacheEntry(cache)); michael@0: Rooted arrayBuffer(cx, ArrayBufferObject::create(cx, length, buffer)); michael@0: michael@0: if (!arrayBuffer || !ArrayBufferObject::ensureNonInline(cx, arrayBuffer)) michael@0: return false; michael@0: michael@0: SetReservedSlot(cache, CacheEntry_BYTECODE, OBJECT_TO_JSVAL(arrayBuffer)); michael@0: return true; michael@0: } michael@0: michael@0: class AutoSaveFrameChain michael@0: { michael@0: JSContext *cx_; michael@0: bool saved_; michael@0: michael@0: public: michael@0: AutoSaveFrameChain(JSContext *cx) michael@0: : cx_(cx), michael@0: saved_(false) michael@0: {} michael@0: michael@0: bool save() { michael@0: if (!JS_SaveFrameChain(cx_)) michael@0: return false; michael@0: saved_ = true; michael@0: return true; michael@0: } michael@0: michael@0: ~AutoSaveFrameChain() { michael@0: if (saved_) michael@0: JS_RestoreFrameChain(cx_); michael@0: } michael@0: }; michael@0: michael@0: static bool michael@0: Evaluate(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1 || args.length() > 2) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, michael@0: "evaluate"); michael@0: return false; michael@0: } michael@0: michael@0: RootedString code(cx, nullptr); michael@0: RootedObject cacheEntry(cx, nullptr); michael@0: if (args[0].isString()) { michael@0: code = args[0].toString(); michael@0: } else if (args[0].isObject() && CacheEntry_isCacheEntry(&args[0].toObject())) { michael@0: cacheEntry = &args[0].toObject(); michael@0: code = CacheEntry_getSource(cacheEntry); michael@0: } michael@0: michael@0: if (!code || (args.length() == 2 && args[1].isPrimitive())) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate"); michael@0: return false; michael@0: } michael@0: michael@0: CompileOptions options(cx); michael@0: JSAutoByteString fileNameBytes; michael@0: bool newContext = false; michael@0: RootedString displayURL(cx); michael@0: RootedString sourceMapURL(cx); michael@0: RootedObject global(cx, nullptr); michael@0: bool catchTermination = false; michael@0: bool saveFrameChain = false; michael@0: bool loadBytecode = false; michael@0: bool saveBytecode = false; michael@0: bool assertEqBytecode = false; michael@0: RootedObject callerGlobal(cx, cx->global()); michael@0: michael@0: options.setIntroductionType("js shell evaluate") michael@0: .setFileAndLine("@evaluate", 1); michael@0: michael@0: global = JS_GetGlobalForObject(cx, &args.callee()); michael@0: if (!global) michael@0: return false; michael@0: michael@0: if (args.length() == 2) { michael@0: RootedObject opts(cx, &args[1].toObject()); michael@0: RootedValue v(cx); michael@0: michael@0: if (!ParseCompileOptions(cx, options, opts, fileNameBytes)) michael@0: return false; michael@0: michael@0: if (!JS_GetProperty(cx, opts, "newContext", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: newContext = ToBoolean(v); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "displayURL", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: displayURL = ToString(cx, v); michael@0: if (!displayURL) michael@0: return false; michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "sourceMapURL", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: sourceMapURL = ToString(cx, v); michael@0: if (!sourceMapURL) michael@0: return false; michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "global", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: if (v.isObject()) { michael@0: global = js::UncheckedUnwrap(&v.toObject()); michael@0: if (!global) michael@0: return false; michael@0: } michael@0: if (!global || !(JS_GetClass(global)->flags & JSCLASS_IS_GLOBAL)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "\"global\" passed to evaluate()", "not a global object"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!JS_GetProperty(cx, opts, "catchTermination", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: catchTermination = ToBoolean(v); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "saveFrameChain", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: saveFrameChain = ToBoolean(v); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "loadBytecode", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: loadBytecode = ToBoolean(v); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "saveBytecode", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: saveBytecode = ToBoolean(v); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) michael@0: assertEqBytecode = ToBoolean(v); michael@0: michael@0: // We cannot load or save the bytecode if we have no object where the michael@0: // bytecode cache is stored. michael@0: if (loadBytecode || saveBytecode) { michael@0: if (!cacheEntry) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, michael@0: "evaluate"); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: size_t codeLength; michael@0: const jschar *codeChars = JS_GetStringCharsAndLength(cx, code, &codeLength); michael@0: if (!codeChars) michael@0: return false; michael@0: michael@0: AutoNewContext ancx; michael@0: if (newContext) { michael@0: if (!ancx.enter(cx)) michael@0: return false; michael@0: cx = ancx.get(); michael@0: } michael@0: michael@0: uint32_t loadLength = 0; michael@0: uint8_t *loadBuffer = nullptr; michael@0: uint32_t saveLength = 0; michael@0: ScopedJSFreePtr saveBuffer; michael@0: michael@0: if (loadBytecode) { michael@0: loadBuffer = CacheEntry_getBytecode(cacheEntry, &loadLength); michael@0: if (!loadBuffer) michael@0: return false; michael@0: } michael@0: michael@0: { michael@0: AutoSaveFrameChain asfc(cx); michael@0: if (saveFrameChain && !asfc.save()) michael@0: return false; michael@0: michael@0: JSAutoCompartment ac(cx, global); michael@0: RootedScript script(cx); michael@0: michael@0: if (!options.wrap(cx, cx->compartment())) michael@0: return false; michael@0: michael@0: { michael@0: JS::AutoSaveContextOptions asco(cx); michael@0: JS::ContextOptionsRef(cx).setNoScriptRval(options.noScriptRval); michael@0: if (saveBytecode) { michael@0: if (!JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_CACHE_SINGLETON_FAILED); michael@0: return false; michael@0: } michael@0: JS::CompartmentOptionsRef(cx).cloneSingletonsOverride().set(true); michael@0: } michael@0: michael@0: if (loadBytecode) { michael@0: script = JS_DecodeScript(cx, loadBuffer, loadLength, options.originPrincipals(cx)); michael@0: } else { michael@0: script = JS::Compile(cx, global, options, codeChars, codeLength); michael@0: } michael@0: michael@0: if (!script) michael@0: return false; michael@0: } michael@0: michael@0: if (displayURL && !script->scriptSource()->hasDisplayURL()) { michael@0: const jschar *durl = JS_GetStringCharsZ(cx, displayURL); michael@0: if (!durl) michael@0: return false; michael@0: if (!script->scriptSource()->setDisplayURL(cx, durl)) michael@0: return false; michael@0: } michael@0: if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) { michael@0: const jschar *smurl = JS_GetStringCharsZ(cx, sourceMapURL); michael@0: if (!smurl) michael@0: return false; michael@0: if (!script->scriptSource()->setSourceMapURL(cx, smurl)) michael@0: return false; michael@0: } michael@0: if (!JS_ExecuteScript(cx, global, script, args.rval())) { michael@0: if (catchTermination && !JS_IsExceptionPending(cx)) { michael@0: JSAutoCompartment ac1(cx, callerGlobal); michael@0: JSString *str = JS_NewStringCopyZ(cx, "terminated"); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: if (saveBytecode) { michael@0: saveBuffer = reinterpret_cast(JS_EncodeScript(cx, script, &saveLength)); michael@0: if (!saveBuffer) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (saveBytecode) { michael@0: // If we are both loading and saving, we assert that we are going to michael@0: // replace the current bytecode by the same stream of bytes. michael@0: if (loadBytecode && assertEqBytecode) { michael@0: if (saveLength != loadLength) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED, michael@0: loadLength, saveLength); michael@0: } else if (!mozilla::PodEqual(loadBuffer, saveBuffer.get(), loadLength)) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_CACHE_EQ_CONTENT_FAILED); michael@0: } michael@0: } michael@0: michael@0: if (!CacheEntry_setBytecode(cx, cacheEntry, saveBuffer, saveLength)) michael@0: return false; michael@0: michael@0: saveBuffer.forget(); michael@0: } michael@0: michael@0: return JS_WrapValue(cx, args.rval()); michael@0: } michael@0: michael@0: static JSString * michael@0: FileAsString(JSContext *cx, const char *pathname) michael@0: { michael@0: FILE *file; michael@0: RootedString str(cx); michael@0: size_t len, cc; michael@0: char *buf; michael@0: michael@0: file = fopen(pathname, "rb"); michael@0: if (!file) { michael@0: JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); michael@0: return nullptr; michael@0: } michael@0: AutoCloseInputFile autoClose(file); michael@0: michael@0: if (fseek(file, 0, SEEK_END) != 0) { michael@0: JS_ReportError(cx, "can't seek end of %s", pathname); michael@0: } else { michael@0: len = ftell(file); michael@0: if (fseek(file, 0, SEEK_SET) != 0) { michael@0: JS_ReportError(cx, "can't seek start of %s", pathname); michael@0: } else { michael@0: buf = (char*) JS_malloc(cx, len + 1); michael@0: if (buf) { michael@0: cc = fread(buf, 1, len, file); michael@0: if (cc != len) { michael@0: JS_ReportError(cx, "can't read %s: %s", pathname, michael@0: (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); michael@0: } else { michael@0: jschar *ucbuf = michael@0: JS::UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf, len), &len).get(); michael@0: if (!ucbuf) { michael@0: JS_ReportError(cx, "Invalid UTF-8 in file '%s'", pathname); michael@0: gExitCode = EXITCODE_RUNTIME_ERROR; michael@0: return nullptr; michael@0: } michael@0: str = JS_NewUCStringCopyN(cx, ucbuf, len); michael@0: free(ucbuf); michael@0: } michael@0: JS_free(cx, buf); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return str; michael@0: } michael@0: michael@0: static JSObject * michael@0: FileAsTypedArray(JSContext *cx, const char *pathname) michael@0: { michael@0: FILE *file = fopen(pathname, "rb"); michael@0: if (!file) { michael@0: JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); michael@0: return nullptr; michael@0: } michael@0: AutoCloseInputFile autoClose(file); michael@0: michael@0: RootedObject obj(cx); michael@0: if (fseek(file, 0, SEEK_END) != 0) { michael@0: JS_ReportError(cx, "can't seek end of %s", pathname); michael@0: } else { michael@0: size_t len = ftell(file); michael@0: if (fseek(file, 0, SEEK_SET) != 0) { michael@0: JS_ReportError(cx, "can't seek start of %s", pathname); michael@0: } else { michael@0: obj = JS_NewUint8Array(cx, len); michael@0: if (!obj) michael@0: return nullptr; michael@0: char *buf = (char *) obj->as().viewData(); michael@0: size_t cc = fread(buf, 1, len, file); michael@0: if (cc != len) { michael@0: JS_ReportError(cx, "can't read %s: %s", pathname, michael@0: (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); michael@0: obj = nullptr; michael@0: } michael@0: } michael@0: } michael@0: return obj; michael@0: } michael@0: michael@0: /* michael@0: * Function to run scripts and return compilation + execution time. Semantics michael@0: * are closely modelled after the equivalent function in WebKit, as this is used michael@0: * to produce benchmark timings by SunSpider. michael@0: */ michael@0: static bool michael@0: Run(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "run"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!thisobj) michael@0: return false; michael@0: michael@0: JSString *str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: args[0].setString(str); michael@0: JSAutoByteString filename(cx, str); michael@0: if (!filename) michael@0: return false; michael@0: michael@0: const jschar *ucbuf = nullptr; michael@0: size_t buflen; michael@0: str = FileAsString(cx, filename.ptr()); michael@0: if (str) michael@0: ucbuf = JS_GetStringCharsAndLength(cx, str, &buflen); michael@0: if (!ucbuf) michael@0: return false; michael@0: michael@0: JS::Anchor a_str(str); michael@0: michael@0: RootedScript script(cx); michael@0: int64_t startClock = PRMJ_Now(); michael@0: { michael@0: JS::AutoSaveContextOptions asco(cx); michael@0: JS::ContextOptionsRef(cx).setNoScriptRval(true); michael@0: michael@0: JS::CompileOptions options(cx); michael@0: options.setIntroductionType("js shell run") michael@0: .setFileAndLine(filename.ptr(), 1) michael@0: .setCompileAndGo(true); michael@0: script = JS_CompileUCScript(cx, thisobj, ucbuf, buflen, options); michael@0: if (!script) michael@0: return false; michael@0: } michael@0: michael@0: if (!JS_ExecuteScript(cx, thisobj, script)) michael@0: return false; michael@0: michael@0: int64_t endClock = PRMJ_Now(); michael@0: michael@0: args.rval().setDouble((endClock - startClock) / double(PRMJ_USEC_PER_MSEC)); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * function readline() michael@0: * Provides a hook for scripts to read a line from stdin. michael@0: */ michael@0: static bool michael@0: ReadLine(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: #define BUFSIZE 256 michael@0: FILE *from = stdin; michael@0: size_t buflength = 0; michael@0: size_t bufsize = BUFSIZE; michael@0: char *buf = (char *) JS_malloc(cx, bufsize); michael@0: if (!buf) michael@0: return false; michael@0: michael@0: bool sawNewline = false; michael@0: size_t gotlength; michael@0: while ((gotlength = js_fgets(buf + buflength, bufsize - buflength, from)) > 0) { michael@0: buflength += gotlength; michael@0: michael@0: /* Are we done? */ michael@0: if (buf[buflength - 1] == '\n') { michael@0: buf[buflength - 1] = '\0'; michael@0: sawNewline = true; michael@0: break; michael@0: } else if (buflength < bufsize - 1) { michael@0: break; michael@0: } michael@0: michael@0: /* Else, grow our buffer for another pass. */ michael@0: char *tmp; michael@0: bufsize *= 2; michael@0: if (bufsize > buflength) { michael@0: tmp = (char *) JS_realloc(cx, buf, bufsize); michael@0: } else { michael@0: JS_ReportOutOfMemory(cx); michael@0: tmp = nullptr; michael@0: } michael@0: michael@0: if (!tmp) { michael@0: JS_free(cx, buf); michael@0: return false; michael@0: } michael@0: michael@0: buf = tmp; michael@0: } michael@0: michael@0: /* Treat the empty string specially. */ michael@0: if (buflength == 0) { michael@0: args.rval().set(feof(from) ? NullValue() : JS_GetEmptyStringValue(cx)); michael@0: JS_free(cx, buf); michael@0: return true; michael@0: } michael@0: michael@0: /* Shrink the buffer to the real size. */ michael@0: char *tmp = static_cast(JS_realloc(cx, buf, buflength)); michael@0: if (!tmp) { michael@0: JS_free(cx, buf); michael@0: return false; michael@0: } michael@0: michael@0: buf = tmp; michael@0: michael@0: /* michael@0: * Turn buf into a JSString. Note that buflength includes the trailing null michael@0: * character. michael@0: */ michael@0: JSString *str = JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength); michael@0: JS_free(cx, buf); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PutStr(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() != 0) { michael@0: JSString *str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: char *bytes = JSStringToUTF8(cx, str); michael@0: if (!bytes) michael@0: return false; michael@0: fputs(bytes, gOutFile); michael@0: JS_free(cx, bytes); michael@0: fflush(gOutFile); michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Now(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC); michael@0: args.rval().setDouble(now); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PrintInternal(JSContext *cx, const CallArgs &args, FILE *file) michael@0: { michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: JSString *str = JS::ToString(cx, args[i]); michael@0: if (!str) michael@0: return false; michael@0: char *bytes = JSStringToUTF8(cx, str); michael@0: if (!bytes) michael@0: return false; michael@0: fprintf(file, "%s%s", i ? " " : "", bytes); michael@0: JS_free(cx, bytes); michael@0: } michael@0: michael@0: fputc('\n', file); michael@0: fflush(file); michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Print(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return PrintInternal(cx, args, gOutFile); michael@0: } michael@0: michael@0: static bool michael@0: PrintErr(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return PrintInternal(cx, args, gErrFile); michael@0: } michael@0: michael@0: static bool michael@0: Help(JSContext *cx, unsigned argc, jsval *vp); michael@0: michael@0: static bool michael@0: Quit(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: #ifdef JS_MORE_DETERMINISTIC michael@0: // Print a message to stderr in more-deterministic builds to help jsfunfuzz michael@0: // find uncatchable-exception bugs. michael@0: fprintf(stderr, "quit called\n"); michael@0: #endif michael@0: michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JS_ConvertArguments(cx, args, "/ i", &gExitCode); michael@0: michael@0: gQuitting = true; michael@0: return false; michael@0: } michael@0: michael@0: static const char * michael@0: ToSource(JSContext *cx, MutableHandleValue vp, JSAutoByteString *bytes) michael@0: { michael@0: JSString *str = JS_ValueToSource(cx, vp); michael@0: if (str) { michael@0: vp.setString(str); michael@0: if (bytes->encodeLatin1(cx, str)) michael@0: return bytes->ptr(); michael@0: } michael@0: JS_ClearPendingException(cx); michael@0: return "<>"; michael@0: } michael@0: michael@0: static bool michael@0: AssertEq(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: (args.length() < 2) michael@0: ? JSSMSG_NOT_ENOUGH_ARGS michael@0: : (args.length() == 3) michael@0: ? JSSMSG_INVALID_ARGS michael@0: : JSSMSG_TOO_MANY_ARGS, michael@0: "assertEq"); michael@0: return false; michael@0: } michael@0: michael@0: bool same; michael@0: if (!JS_SameValue(cx, args[0], args[1], &same)) michael@0: return false; michael@0: if (!same) { michael@0: JSAutoByteString bytes0, bytes1; michael@0: const char *actual = ToSource(cx, args[0], &bytes0); michael@0: const char *expected = ToSource(cx, args[1], &bytes1); michael@0: if (args.length() == 2) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED, michael@0: actual, expected); michael@0: } else { michael@0: JSAutoByteString bytes2(cx, args[2].toString()); michael@0: if (!bytes2) michael@0: return false; michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED_MSG, michael@0: actual, expected, bytes2.ptr()); michael@0: } michael@0: return false; michael@0: } michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static JSScript * michael@0: ValueToScript(JSContext *cx, jsval vArg, JSFunction **funp = nullptr) michael@0: { michael@0: RootedValue v(cx, vArg); michael@0: RootedFunction fun(cx, JS_ValueToFunction(cx, v)); michael@0: if (!fun) michael@0: return nullptr; michael@0: michael@0: // Unwrap bound functions. michael@0: while (fun->isBoundFunction()) { michael@0: JSObject *target = fun->getBoundFunctionTarget(); michael@0: if (target && target->is()) michael@0: fun = &target->as(); michael@0: else michael@0: break; michael@0: } michael@0: michael@0: if (!fun->isInterpreted()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSScript *script = fun->getOrCreateScript(cx); michael@0: if (!script) michael@0: return nullptr; michael@0: michael@0: if (fun && funp) michael@0: *funp = fun; michael@0: michael@0: return script; michael@0: } michael@0: michael@0: static bool michael@0: SetDebug(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0 || !args[0].isBoolean()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_NOT_ENOUGH_ARGS, "setDebug"); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Debug mode can only be set when there is no JS code executing on the michael@0: * stack. Unfortunately, that currently means that this call will fail michael@0: * unless debug mode is already set to what you're trying to set it to. michael@0: * In the future, this restriction may be lifted. michael@0: */ michael@0: michael@0: bool ok = !!JS_SetDebugMode(cx, args[0].toBoolean()); michael@0: if (ok) michael@0: args.rval().setBoolean(true); michael@0: return ok; michael@0: } michael@0: michael@0: static JSScript * michael@0: GetTopScript(JSContext *cx) michael@0: { michael@0: NonBuiltinScriptFrameIter iter(cx); michael@0: return iter.done() ? nullptr : iter.script(); michael@0: } michael@0: michael@0: static bool michael@0: GetScriptAndPCArgs(JSContext *cx, unsigned argc, jsval *argv, MutableHandleScript scriptp, michael@0: int32_t *ip) michael@0: { michael@0: RootedScript script(cx, GetTopScript(cx)); michael@0: *ip = 0; michael@0: if (argc != 0) { michael@0: jsval v = argv[0]; michael@0: unsigned intarg = 0; michael@0: if (!JSVAL_IS_PRIMITIVE(v) && michael@0: JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) { michael@0: script = ValueToScript(cx, v); michael@0: if (!script) michael@0: return false; michael@0: intarg++; michael@0: } michael@0: if (argc > intarg) { michael@0: if (!JS::ToInt32(cx, HandleValue::fromMarkedLocation(&argv[intarg]), ip)) michael@0: return false; michael@0: if ((uint32_t)*ip >= script->length()) { michael@0: JS_ReportError(cx, "Invalid PC"); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: scriptp.set(script); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static JSTrapStatus michael@0: TrapHandler(JSContext *cx, JSScript *, jsbytecode *pc, jsval *rvalArg, michael@0: jsval closure) michael@0: { michael@0: RootedString str(cx, JSVAL_TO_STRING(closure)); michael@0: RootedValue rval(cx, *rvalArg); michael@0: michael@0: ScriptFrameIter iter(cx); michael@0: JS_ASSERT(!iter.done()); michael@0: michael@0: /* Debug-mode currently disables Ion compilation. */ michael@0: JSAbstractFramePtr frame(iter.abstractFramePtr().raw(), iter.pc()); michael@0: RootedScript script(cx, iter.script()); michael@0: michael@0: size_t length; michael@0: const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); michael@0: if (!chars) michael@0: return JSTRAP_ERROR; michael@0: michael@0: if (!frame.evaluateUCInStackFrame(cx, chars, length, michael@0: script->filename(), michael@0: script->lineno(), michael@0: &rval)) michael@0: { michael@0: *rvalArg = rval; michael@0: return JSTRAP_ERROR; michael@0: } michael@0: *rvalArg = rval; michael@0: if (!rval.isUndefined()) michael@0: return JSTRAP_RETURN; michael@0: return JSTRAP_CONTINUE; michael@0: } michael@0: michael@0: static bool michael@0: Trap(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedScript script(cx); michael@0: int32_t i; michael@0: michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_TRAP_USAGE); michael@0: return false; michael@0: } michael@0: argc = args.length() - 1; michael@0: RootedString str(cx, JS::ToString(cx, args[argc])); michael@0: if (!str) michael@0: return false; michael@0: args[argc].setString(str); michael@0: if (!GetScriptAndPCArgs(cx, argc, args.array(), &script, &i)) michael@0: return false; michael@0: args.rval().setUndefined(); michael@0: RootedValue strValue(cx, StringValue(str)); michael@0: return JS_SetTrap(cx, script, script->offsetToPC(i), TrapHandler, strValue); michael@0: } michael@0: michael@0: static bool michael@0: Untrap(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedScript script(cx); michael@0: int32_t i; michael@0: michael@0: if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i)) michael@0: return false; michael@0: JS_ClearTrap(cx, script, script->offsetToPC(i), nullptr, nullptr); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static JSTrapStatus michael@0: DebuggerAndThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, michael@0: void *closure) michael@0: { michael@0: return TrapHandler(cx, script, pc, rval, STRING_TO_JSVAL((JSString *)closure)); michael@0: } michael@0: michael@0: static bool michael@0: SetDebuggerHandler(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_NOT_ENOUGH_ARGS, "setDebuggerHandler"); michael@0: return false; michael@0: } michael@0: michael@0: JSString *str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JS_SetDebuggerHandler(cx->runtime(), DebuggerAndThrowHandler, str); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SetThrowHook(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSString *str; michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_NOT_ENOUGH_ARGS, "setThrowHook"); michael@0: return false; michael@0: } michael@0: michael@0: str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JS_SetThrowHook(cx->runtime(), DebuggerAndThrowHandler, str); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: LineToPC(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_LINE2PC_USAGE); michael@0: return false; michael@0: } michael@0: michael@0: RootedScript script(cx, GetTopScript(cx)); michael@0: int32_t lineArg = 0; michael@0: if (args[0].isObject() && args[0].toObject().is()) { michael@0: script = ValueToScript(cx, args[0]); michael@0: if (!script) michael@0: return false; michael@0: lineArg++; michael@0: } michael@0: michael@0: uint32_t lineno; michael@0: if (!ToUint32(cx, args.get(lineArg), &lineno)) michael@0: return false; michael@0: michael@0: jsbytecode *pc = JS_LineNumberToPC(cx, script, lineno); michael@0: if (!pc) michael@0: return false; michael@0: args.rval().setInt32(script->pcToOffset(pc)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PCToLine(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedScript script(cx); michael@0: int32_t i; michael@0: unsigned lineno; michael@0: michael@0: if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i)) michael@0: return false; michael@0: lineno = JS_PCToLineNumber(cx, script, script->offsetToPC(i)); michael@0: if (!lineno) michael@0: return false; michael@0: args.rval().setInt32(lineno); michael@0: return true; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: static void michael@0: UpdateSwitchTableBounds(JSContext *cx, HandleScript script, unsigned offset, michael@0: unsigned *start, unsigned *end) michael@0: { michael@0: jsbytecode *pc; michael@0: JSOp op; michael@0: ptrdiff_t jmplen; michael@0: int32_t low, high, n; michael@0: michael@0: pc = script->offsetToPC(offset); michael@0: op = JSOp(*pc); michael@0: switch (op) { michael@0: case JSOP_TABLESWITCH: michael@0: jmplen = JUMP_OFFSET_LEN; michael@0: pc += jmplen; michael@0: low = GET_JUMP_OFFSET(pc); michael@0: pc += JUMP_OFFSET_LEN; michael@0: high = GET_JUMP_OFFSET(pc); michael@0: pc += JUMP_OFFSET_LEN; michael@0: n = high - low + 1; michael@0: break; michael@0: michael@0: default: michael@0: /* [condswitch] switch does not have any jump or lookup tables. */ michael@0: JS_ASSERT(op == JSOP_CONDSWITCH); michael@0: return; michael@0: } michael@0: michael@0: *start = script->pcToOffset(pc); michael@0: *end = *start + (unsigned)(n * jmplen); michael@0: } michael@0: michael@0: static void michael@0: SrcNotes(JSContext *cx, HandleScript script, Sprinter *sp) michael@0: { michael@0: Sprint(sp, "\nSource notes:\n"); michael@0: Sprint(sp, "%4s %4s %5s %6s %-8s %s\n", michael@0: "ofs", "line", "pc", "delta", "desc", "args"); michael@0: Sprint(sp, "---- ---- ----- ------ -------- ------\n"); michael@0: unsigned offset = 0; michael@0: unsigned colspan = 0; michael@0: unsigned lineno = script->lineno(); michael@0: jssrcnote *notes = script->notes(); michael@0: unsigned switchTableEnd = 0, switchTableStart = 0; michael@0: for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { michael@0: unsigned delta = SN_DELTA(sn); michael@0: offset += delta; michael@0: SrcNoteType type = (SrcNoteType) SN_TYPE(sn); michael@0: const char *name = js_SrcNoteSpec[type].name; michael@0: Sprint(sp, "%3u: %4u %5u [%4u] %-8s", unsigned(sn - notes), lineno, offset, delta, name); michael@0: switch (type) { michael@0: case SRC_NULL: michael@0: case SRC_IF: michael@0: case SRC_CONTINUE: michael@0: case SRC_BREAK: michael@0: case SRC_BREAK2LABEL: michael@0: case SRC_SWITCHBREAK: michael@0: case SRC_ASSIGNOP: michael@0: case SRC_XDELTA: michael@0: break; michael@0: michael@0: case SRC_COLSPAN: michael@0: colspan = js_GetSrcNoteOffset(sn, 0); michael@0: if (colspan >= SN_COLSPAN_DOMAIN / 2) michael@0: colspan -= SN_COLSPAN_DOMAIN; michael@0: Sprint(sp, "%d", colspan); michael@0: break; michael@0: michael@0: case SRC_SETLINE: michael@0: lineno = js_GetSrcNoteOffset(sn, 0); michael@0: Sprint(sp, " lineno %u", lineno); michael@0: break; michael@0: michael@0: case SRC_NEWLINE: michael@0: ++lineno; michael@0: break; michael@0: michael@0: case SRC_FOR: michael@0: Sprint(sp, " cond %u update %u tail %u", michael@0: unsigned(js_GetSrcNoteOffset(sn, 0)), michael@0: unsigned(js_GetSrcNoteOffset(sn, 1)), michael@0: unsigned(js_GetSrcNoteOffset(sn, 2))); michael@0: break; michael@0: michael@0: case SRC_IF_ELSE: michael@0: Sprint(sp, " else %u", unsigned(js_GetSrcNoteOffset(sn, 0))); michael@0: break; michael@0: michael@0: case SRC_FOR_IN: michael@0: case SRC_FOR_OF: michael@0: Sprint(sp, " closingjump %u", unsigned(js_GetSrcNoteOffset(sn, 0))); michael@0: break; michael@0: michael@0: case SRC_COND: michael@0: case SRC_WHILE: michael@0: case SRC_NEXTCASE: michael@0: Sprint(sp, " offset %u", unsigned(js_GetSrcNoteOffset(sn, 0))); michael@0: break; michael@0: michael@0: case SRC_TABLESWITCH: { michael@0: JSOp op = JSOp(script->code()[offset]); michael@0: JS_ASSERT(op == JSOP_TABLESWITCH); michael@0: Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0))); michael@0: UpdateSwitchTableBounds(cx, script, offset, michael@0: &switchTableStart, &switchTableEnd); michael@0: break; michael@0: } michael@0: case SRC_CONDSWITCH: { michael@0: JSOp op = JSOp(script->code()[offset]); michael@0: JS_ASSERT(op == JSOP_CONDSWITCH); michael@0: Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0))); michael@0: unsigned caseOff = (unsigned) js_GetSrcNoteOffset(sn, 1); michael@0: if (caseOff) michael@0: Sprint(sp, " first case offset %u", caseOff); michael@0: UpdateSwitchTableBounds(cx, script, offset, michael@0: &switchTableStart, &switchTableEnd); michael@0: break; michael@0: } michael@0: michael@0: case SRC_TRY: michael@0: JS_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY); michael@0: Sprint(sp, " offset to jump %u", unsigned(js_GetSrcNoteOffset(sn, 0))); michael@0: break; michael@0: michael@0: default: michael@0: JS_ASSERT(0); michael@0: break; michael@0: } michael@0: Sprint(sp, "\n"); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: Notes(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: RootedScript script (cx, ValueToScript(cx, args[i])); michael@0: if (!script) michael@0: return false; michael@0: michael@0: SrcNotes(cx, script, &sprinter); michael@0: } michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, sprinter.string()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: JS_STATIC_ASSERT(JSTRY_CATCH == 0); michael@0: JS_STATIC_ASSERT(JSTRY_FINALLY == 1); michael@0: JS_STATIC_ASSERT(JSTRY_ITER == 2); michael@0: michael@0: static const char* const TryNoteNames[] = { "catch", "finally", "iter", "loop" }; michael@0: michael@0: static bool michael@0: TryNotes(JSContext *cx, HandleScript script, Sprinter *sp) michael@0: { michael@0: JSTryNote *tn, *tnlimit; michael@0: michael@0: if (!script->hasTrynotes()) michael@0: return true; michael@0: michael@0: tn = script->trynotes()->vector; michael@0: tnlimit = tn + script->trynotes()->length; michael@0: Sprint(sp, "\nException table:\nkind stack start end\n"); michael@0: do { michael@0: JS_ASSERT(tn->kind < ArrayLength(TryNoteNames)); michael@0: Sprint(sp, " %-7s %6u %8u %8u\n", michael@0: TryNoteNames[tn->kind], tn->stackDepth, michael@0: tn->start, tn->start + tn->length); michael@0: } while (++tn != tnlimit); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DisassembleScript(JSContext *cx, HandleScript script, HandleFunction fun, bool lines, michael@0: bool recursive, Sprinter *sp) michael@0: { michael@0: if (fun) { michael@0: Sprint(sp, "flags:"); michael@0: if (fun->isLambda()) michael@0: Sprint(sp, " LAMBDA"); michael@0: if (fun->isHeavyweight()) michael@0: Sprint(sp, " HEAVYWEIGHT"); michael@0: if (fun->isExprClosure()) michael@0: Sprint(sp, " EXPRESSION_CLOSURE"); michael@0: if (fun->isFunctionPrototype()) michael@0: Sprint(sp, " Function.prototype"); michael@0: if (fun->isSelfHostedBuiltin()) michael@0: Sprint(sp, " SELF_HOSTED"); michael@0: if (fun->isSelfHostedConstructor()) michael@0: Sprint(sp, " SELF_HOSTED_CTOR"); michael@0: if (fun->isArrow()) michael@0: Sprint(sp, " ARROW"); michael@0: Sprint(sp, "\n"); michael@0: } michael@0: michael@0: if (!js_Disassemble(cx, script, lines, sp)) michael@0: return false; michael@0: SrcNotes(cx, script, sp); michael@0: TryNotes(cx, script, sp); michael@0: michael@0: if (recursive && script->hasObjects()) { michael@0: ObjectArray *objects = script->objects(); michael@0: for (unsigned i = 0; i != objects->length; ++i) { michael@0: JSObject *obj = objects->vector[i]; michael@0: if (obj->is()) { michael@0: Sprint(sp, "\n"); michael@0: RootedFunction fun(cx, &obj->as()); michael@0: if (fun->isInterpreted()) { michael@0: RootedScript script(cx, fun->getOrCreateScript(cx)); michael@0: if (!script || !DisassembleScript(cx, script, fun, lines, recursive, sp)) michael@0: return false; michael@0: } else { michael@0: Sprint(sp, "[native code]\n"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: struct DisassembleOptionParser { michael@0: unsigned argc; michael@0: jsval *argv; michael@0: bool lines; michael@0: bool recursive; michael@0: michael@0: DisassembleOptionParser(unsigned argc, jsval *argv) michael@0: : argc(argc), argv(argv), lines(false), recursive(false) {} michael@0: michael@0: bool parse(JSContext *cx) { michael@0: /* Read options off early arguments */ michael@0: while (argc > 0 && argv[0].isString()) { michael@0: JSString *str = argv[0].toString(); michael@0: JSFlatString *flatStr = JS_FlattenString(cx, str); michael@0: if (!flatStr) michael@0: return false; michael@0: if (JS_FlatStringEqualsAscii(flatStr, "-l")) michael@0: lines = true; michael@0: else if (JS_FlatStringEqualsAscii(flatStr, "-r")) michael@0: recursive = true; michael@0: else michael@0: break; michael@0: argv++, argc--; michael@0: } michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: static bool michael@0: DisassembleToSprinter(JSContext *cx, unsigned argc, jsval *vp, Sprinter *sprinter) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: DisassembleOptionParser p(args.length(), args.array()); michael@0: if (!p.parse(cx)) michael@0: return false; michael@0: michael@0: if (p.argc == 0) { michael@0: /* Without arguments, disassemble the current script. */ michael@0: RootedScript script(cx, GetTopScript(cx)); michael@0: if (script) { michael@0: JSAutoCompartment ac(cx, script); michael@0: if (!js_Disassemble(cx, script, p.lines, sprinter)) michael@0: return false; michael@0: SrcNotes(cx, script, sprinter); michael@0: TryNotes(cx, script, sprinter); michael@0: } michael@0: } else { michael@0: for (unsigned i = 0; i < p.argc; i++) { michael@0: RootedFunction fun(cx); michael@0: RootedScript script (cx, ValueToScript(cx, p.argv[i], fun.address())); michael@0: if (!script) michael@0: return false; michael@0: if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, sprinter)) michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DisassembleToString(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) michael@0: return false; michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, sprinter.string()); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Disassemble(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) michael@0: return false; michael@0: michael@0: fprintf(stdout, "%s\n", sprinter.string()); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DisassFile(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Support extra options at the start, just like Disassemble. */ michael@0: DisassembleOptionParser p(args.length(), args.array()); michael@0: if (!p.parse(cx)) michael@0: return false; michael@0: michael@0: if (!p.argc) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!thisobj) michael@0: return false; michael@0: michael@0: // We should change DisassembleOptionParser to store CallArgs. michael@0: JSString *str = JS::ToString(cx, HandleValue::fromMarkedLocation(&p.argv[0])); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString filename(cx, str); michael@0: if (!filename) michael@0: return false; michael@0: RootedScript script(cx); michael@0: michael@0: { michael@0: JS::AutoSaveContextOptions asco(cx); michael@0: JS::ContextOptionsRef(cx).setNoScriptRval(true); michael@0: michael@0: CompileOptions options(cx); michael@0: options.setIntroductionType("js shell disFile") michael@0: .setUTF8(true) michael@0: .setFileAndLine(filename.ptr(), 1) michael@0: .setCompileAndGo(true); michael@0: michael@0: script = JS::Compile(cx, thisobj, options, filename.ptr()); michael@0: if (!script) michael@0: return false; michael@0: } michael@0: michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: bool ok = DisassembleScript(cx, script, NullPtr(), p.lines, p.recursive, &sprinter); michael@0: if (ok) michael@0: fprintf(stdout, "%s\n", sprinter.string()); michael@0: if (!ok) michael@0: return false; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DisassWithSrc(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: #define LINE_BUF_LEN 512 michael@0: unsigned len, line1, line2, bupline; michael@0: FILE *file; michael@0: char linebuf[LINE_BUF_LEN]; michael@0: jsbytecode *pc, *end; michael@0: static const char sep[] = ";-------------------------"; michael@0: michael@0: bool ok = true; michael@0: RootedScript script(cx); michael@0: for (unsigned i = 0; ok && i < args.length(); i++) { michael@0: script = ValueToScript(cx, args[i]); michael@0: if (!script) michael@0: return false; michael@0: michael@0: if (!script->filename()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_FILE_SCRIPTS_ONLY); michael@0: return false; michael@0: } michael@0: michael@0: file = fopen(script->filename(), "r"); michael@0: if (!file) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_CANT_OPEN, script->filename(), michael@0: strerror(errno)); michael@0: return false; michael@0: } michael@0: michael@0: pc = script->code(); michael@0: end = script->codeEnd(); michael@0: michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) { michael@0: ok = false; michael@0: goto bail; michael@0: } michael@0: michael@0: /* burn the leading lines */ michael@0: line2 = JS_PCToLineNumber(cx, script, pc); michael@0: for (line1 = 0; line1 < line2 - 1; line1++) { michael@0: char *tmp = fgets(linebuf, LINE_BUF_LEN, file); michael@0: if (!tmp) { michael@0: JS_ReportError(cx, "failed to read %s fully", script->filename()); michael@0: ok = false; michael@0: goto bail; michael@0: } michael@0: } michael@0: michael@0: bupline = 0; michael@0: while (pc < end) { michael@0: line2 = JS_PCToLineNumber(cx, script, pc); michael@0: michael@0: if (line2 < line1) { michael@0: if (bupline != line2) { michael@0: bupline = line2; michael@0: Sprint(&sprinter, "%s %3u: BACKUP\n", sep, line2); michael@0: } michael@0: } else { michael@0: if (bupline && line1 == line2) michael@0: Sprint(&sprinter, "%s %3u: RESTORE\n", sep, line2); michael@0: bupline = 0; michael@0: while (line1 < line2) { michael@0: if (!fgets(linebuf, LINE_BUF_LEN, file)) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: JSSMSG_UNEXPECTED_EOF, michael@0: script->filename()); michael@0: ok = false; michael@0: goto bail; michael@0: } michael@0: line1++; michael@0: Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf); michael@0: } michael@0: } michael@0: michael@0: len = js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter); michael@0: if (!len) { michael@0: ok = false; michael@0: goto bail; michael@0: } michael@0: pc += len; michael@0: } michael@0: michael@0: bail: michael@0: fclose(file); michael@0: } michael@0: args.rval().setUndefined(); michael@0: return ok; michael@0: #undef LINE_BUF_LEN michael@0: } michael@0: michael@0: static bool michael@0: DumpHeap(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: JSAutoByteString fileName; michael@0: if (args.hasDefined(0)) { michael@0: RootedString str(cx, JS::ToString(cx, args[0])); michael@0: if (!str) michael@0: return false; michael@0: michael@0: if (!fileName.encodeLatin1(cx, str)) michael@0: return false; michael@0: } michael@0: michael@0: RootedValue startThing(cx); michael@0: if (args.hasDefined(1)) { michael@0: if (!args[1].isGCThing()) { michael@0: JS_ReportError(cx, "dumpHeap: Second argument not a GC thing!"); michael@0: return false; michael@0: } michael@0: startThing = args[1]; michael@0: } michael@0: michael@0: RootedValue thingToFind(cx); michael@0: if (args.hasDefined(2)) { michael@0: if (!args[2].isGCThing()) { michael@0: JS_ReportError(cx, "dumpHeap: Third argument not a GC thing!"); michael@0: return false; michael@0: } michael@0: thingToFind = args[2]; michael@0: } michael@0: michael@0: size_t maxDepth = size_t(-1); michael@0: if (args.hasDefined(3)) { michael@0: uint32_t depth; michael@0: if (!ToUint32(cx, args[3], &depth)) michael@0: return false; michael@0: maxDepth = depth; michael@0: } michael@0: michael@0: RootedValue thingToIgnore(cx); michael@0: if (args.hasDefined(4)) { michael@0: if (!args[2].isGCThing()) { michael@0: JS_ReportError(cx, "dumpHeap: Fifth argument not a GC thing!"); michael@0: return false; michael@0: } michael@0: thingToIgnore = args[4]; michael@0: } michael@0: michael@0: michael@0: FILE *dumpFile = stdout; michael@0: if (fileName.length()) { michael@0: dumpFile = fopen(fileName.ptr(), "w"); michael@0: if (!dumpFile) { michael@0: JS_ReportError(cx, "dumpHeap: can't open %s: %s\n", michael@0: fileName.ptr(), strerror(errno)); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool ok = JS_DumpHeap(JS_GetRuntime(cx), dumpFile, michael@0: startThing.isUndefined() ? nullptr : startThing.toGCThing(), michael@0: startThing.isUndefined() ? JSTRACE_OBJECT : startThing.get().gcKind(), michael@0: thingToFind.isUndefined() ? nullptr : thingToFind.toGCThing(), michael@0: maxDepth, michael@0: thingToIgnore.isUndefined() ? nullptr : thingToIgnore.toGCThing()); michael@0: michael@0: if (dumpFile != stdout) michael@0: fclose(dumpFile); michael@0: michael@0: if (!ok) michael@0: JS_ReportOutOfMemory(cx); michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: static bool michael@0: DumpObject(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject arg0(cx); michael@0: if (!JS_ConvertArguments(cx, args, "o", arg0.address())) michael@0: return false; michael@0: michael@0: js_DumpObject(arg0); michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: #endif /* DEBUG */ michael@0: michael@0: static bool michael@0: BuildDate(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Intern(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSString *str = JS::ToString(cx, args.get(0)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: size_t length; michael@0: const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: if (!JS_InternUCStringN(cx, chars, length)) michael@0: return false; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Clone(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject parent(cx); michael@0: RootedObject funobj(cx); michael@0: michael@0: if (!args.length()) { michael@0: JS_ReportError(cx, "Invalid arguments to clone"); michael@0: return false; michael@0: } michael@0: michael@0: { michael@0: Maybe ac; michael@0: RootedObject obj(cx, JSVAL_IS_PRIMITIVE(args[0]) ? nullptr : &args[0].toObject()); michael@0: michael@0: if (obj && obj->is()) { michael@0: obj = UncheckedUnwrap(obj); michael@0: ac.construct(cx, obj); michael@0: args[0].setObject(*obj); michael@0: } michael@0: if (obj && obj->is()) { michael@0: funobj = obj; michael@0: } else { michael@0: JSFunction *fun = JS_ValueToFunction(cx, args[0]); michael@0: if (!fun) michael@0: return false; michael@0: funobj = JS_GetFunctionObject(fun); michael@0: } michael@0: } michael@0: if (funobj->compartment() != cx->compartment()) { michael@0: JSFunction *fun = &funobj->as(); michael@0: if (fun->hasScript() && fun->nonLazyScript()->compileAndGo()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, michael@0: "function", "compile-and-go"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (args.length() > 1) { michael@0: if (!JS_ValueToObject(cx, args[1], &parent)) michael@0: return false; michael@0: } else { michael@0: parent = JS_GetParent(&args.callee()); michael@0: } michael@0: michael@0: JSObject *clone = JS_CloneFunctionObject(cx, funobj, parent); michael@0: if (!clone) michael@0: return false; michael@0: args.rval().setObject(*clone); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GetPDA(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject vobj(cx); michael@0: bool ok; michael@0: JSPropertyDescArray pda; michael@0: JSPropertyDesc *pd; michael@0: michael@0: if (!JS_ValueToObject(cx, args.get(0), &vobj)) michael@0: return false; michael@0: if (!vobj) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject aobj(cx, JS_NewArrayObject(cx, 0)); michael@0: if (!aobj) michael@0: return false; michael@0: args.rval().setObject(*aobj); michael@0: michael@0: ok = !!JS_GetPropertyDescArray(cx, vobj, &pda); michael@0: if (!ok) michael@0: return false; michael@0: pd = pda.array; michael@0: michael@0: RootedObject pdobj(cx); michael@0: RootedValue id(cx); michael@0: RootedValue value(cx); michael@0: RootedValue flags(cx); michael@0: RootedValue alias(cx); michael@0: michael@0: for (uint32_t i = 0; i < pda.length; i++, pd++) { michael@0: pdobj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()); michael@0: if (!pdobj) { michael@0: ok = false; michael@0: break; michael@0: } michael@0: michael@0: /* Protect pdobj from GC by setting it as an element of aobj now */ michael@0: ok = !!JS_SetElement(cx, aobj, i, pdobj); michael@0: if (!ok) michael@0: break; michael@0: michael@0: id = pd->id; michael@0: value = pd->value; michael@0: flags.setInt32(pd->flags); michael@0: alias = pd->alias; michael@0: ok = JS_SetProperty(cx, pdobj, "id", id) && michael@0: JS_SetProperty(cx, pdobj, "value", value) && michael@0: JS_SetProperty(cx, pdobj, "flags", flags) && michael@0: JS_SetProperty(cx, pdobj, "alias", alias); michael@0: if (!ok) michael@0: break; michael@0: } michael@0: JS_PutPropertyDescArray(cx, &pda); michael@0: return ok; michael@0: } michael@0: michael@0: static bool michael@0: GetSLX(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedScript script(cx); michael@0: michael@0: script = ValueToScript(cx, args.get(0)); michael@0: if (!script) michael@0: return false; michael@0: args.rval().setInt32(js_GetScriptLineExtent(script)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ThrowError(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: JS_ReportError(cx, "This is an error"); michael@0: return false; michael@0: } michael@0: michael@0: #define LAZY_STANDARD_CLASSES michael@0: michael@0: /* A class for easily testing the inner/outer object callbacks. */ michael@0: typedef struct ComplexObject { michael@0: bool isInner; michael@0: bool frozen; michael@0: JSObject *inner; michael@0: JSObject *outer; michael@0: } ComplexObject; michael@0: michael@0: static bool michael@0: sandbox_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedValue v(cx); michael@0: michael@0: if (!JS_GetProperty(cx, obj, "lazy", &v)) michael@0: return false; michael@0: michael@0: if (!ToBoolean(v)) michael@0: return true; michael@0: michael@0: return JS_EnumerateStandardClasses(cx, obj); michael@0: } michael@0: michael@0: static bool michael@0: sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) michael@0: { michael@0: RootedValue v(cx); michael@0: if (!JS_GetProperty(cx, obj, "lazy", &v)) michael@0: return false; michael@0: michael@0: if (ToBoolean(v)) { michael@0: bool resolved; michael@0: if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) michael@0: return false; michael@0: if (resolved) { michael@0: objp.set(obj); michael@0: return true; michael@0: } michael@0: } michael@0: objp.set(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: static const JSClass sandbox_class = { michael@0: "sandbox", michael@0: JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: sandbox_enumerate, (JSResolveOp)sandbox_resolve, michael@0: JS_ConvertStub, nullptr, michael@0: nullptr, nullptr, nullptr, michael@0: JS_GlobalObjectTraceHook michael@0: }; michael@0: michael@0: static JSObject * michael@0: NewSandbox(JSContext *cx, bool lazy) michael@0: { michael@0: RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, nullptr, michael@0: JS::DontFireOnNewGlobalHook)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: { michael@0: JSAutoCompartment ac(cx, obj); michael@0: if (!lazy && !JS_InitStandardClasses(cx, obj)) michael@0: return nullptr; michael@0: michael@0: RootedValue value(cx, BooleanValue(lazy)); michael@0: if (!JS_SetProperty(cx, obj, "lazy", value)) michael@0: return nullptr; michael@0: } michael@0: michael@0: JS_FireOnNewGlobalObject(cx, obj); michael@0: michael@0: if (!cx->compartment()->wrap(cx, &obj)) michael@0: return nullptr; michael@0: return obj; michael@0: } michael@0: michael@0: static bool michael@0: EvalInContext(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedString str(cx); michael@0: RootedObject sobj(cx); michael@0: if (!JS_ConvertArguments(cx, args, "S / o", str.address(), sobj.address())) michael@0: return false; michael@0: michael@0: size_t srclen; michael@0: const jschar *src = JS_GetStringCharsAndLength(cx, str, &srclen); michael@0: if (!src) michael@0: return false; michael@0: michael@0: bool lazy = false; michael@0: if (srclen == 4) { michael@0: if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') { michael@0: lazy = true; michael@0: srclen = 0; michael@0: } michael@0: } michael@0: michael@0: if (!sobj) { michael@0: sobj = NewSandbox(cx, lazy); michael@0: if (!sobj) michael@0: return false; michael@0: } michael@0: michael@0: if (srclen == 0) { michael@0: args.rval().setObject(*sobj); michael@0: return true; michael@0: } michael@0: michael@0: JS::AutoFilename filename; michael@0: unsigned lineno; michael@0: michael@0: DescribeScriptedCaller(cx, &filename, &lineno); michael@0: { michael@0: Maybe ac; michael@0: unsigned flags; michael@0: JSObject *unwrapped = UncheckedUnwrap(sobj, true, &flags); michael@0: if (flags & Wrapper::CROSS_COMPARTMENT) { michael@0: sobj = unwrapped; michael@0: ac.construct(cx, sobj); michael@0: } michael@0: michael@0: sobj = GetInnerObject(cx, sobj); michael@0: if (!sobj) michael@0: return false; michael@0: if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) { michael@0: JS_ReportError(cx, "Invalid scope argument to evalcx"); michael@0: return false; michael@0: } michael@0: if (!JS_EvaluateUCScript(cx, sobj, src, srclen, michael@0: filename.get(), michael@0: lineno, michael@0: args.rval())) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!cx->compartment()->wrap(cx, args.rval())) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: EvalInFrame(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.get(0).isInt32() || !args.get(1).isString()) { michael@0: JS_ReportError(cx, "Invalid arguments to evalInFrame"); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t upCount = args[0].toInt32(); michael@0: RootedString str(cx, args[1].toString()); michael@0: bool saveCurrent = args.get(2).isBoolean() ? args[2].toBoolean() : false; michael@0: michael@0: /* This is a copy of CheckDebugMode. */ michael@0: if (!JS_GetDebugMode(cx)) { michael@0: JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, michael@0: nullptr, JSMSG_NEED_DEBUG_MODE); michael@0: return false; michael@0: } michael@0: michael@0: /* Debug-mode currently disables Ion compilation. */ michael@0: ScriptFrameIter fi(cx); michael@0: for (uint32_t i = 0; i < upCount; ++i, ++fi) { michael@0: ScriptFrameIter next(fi); michael@0: ++next; michael@0: if (next.done()) michael@0: break; michael@0: } michael@0: michael@0: AutoSaveFrameChain sfc(cx); michael@0: mozilla::Maybe ac; michael@0: if (saveCurrent) { michael@0: if (!sfc.save()) michael@0: return false; michael@0: ac.construct(cx, DefaultObjectForContextOrNull(cx)); michael@0: } michael@0: michael@0: size_t length; michael@0: const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: JSAbstractFramePtr frame(fi.abstractFramePtr().raw(), fi.pc()); michael@0: RootedScript fpscript(cx, frame.script()); michael@0: bool ok = !!frame.evaluateUCInStackFrame(cx, chars, length, michael@0: fpscript->filename(), michael@0: JS_PCToLineNumber(cx, fpscript, michael@0: fi.pc()), michael@0: MutableHandleValue::fromMarkedLocation(vp)); michael@0: return ok; michael@0: } michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: struct WorkerInput michael@0: { michael@0: JSRuntime *runtime; michael@0: jschar *chars; michael@0: size_t length; michael@0: michael@0: WorkerInput(JSRuntime *runtime, jschar *chars, size_t length) michael@0: : runtime(runtime), chars(chars), length(length) michael@0: {} michael@0: michael@0: ~WorkerInput() { michael@0: js_free(chars); michael@0: } michael@0: }; michael@0: michael@0: static void michael@0: WorkerMain(void *arg) michael@0: { michael@0: WorkerInput *input = (WorkerInput *) arg; michael@0: michael@0: JSRuntime *rt = JS_NewRuntime(8L * 1024L * 1024L, michael@0: JS_USE_HELPER_THREADS, michael@0: input->runtime); michael@0: if (!rt) { michael@0: js_delete(input); michael@0: return; michael@0: } michael@0: michael@0: JSContext *cx = NewContext(rt); michael@0: if (!cx) { michael@0: JS_DestroyRuntime(rt); michael@0: js_delete(input); michael@0: return; michael@0: } michael@0: michael@0: do { michael@0: JSAutoRequest ar(cx); michael@0: michael@0: JS::CompartmentOptions compartmentOptions; michael@0: compartmentOptions.setVersion(JSVERSION_LATEST); michael@0: RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr)); michael@0: if (!global) michael@0: break; michael@0: michael@0: JSAutoCompartment ac(cx, global); michael@0: michael@0: JS::CompileOptions options(cx); michael@0: options.setFileAndLine("", 1) michael@0: .setCompileAndGo(true); michael@0: michael@0: RootedScript script(cx, JS::Compile(cx, global, options, michael@0: input->chars, input->length)); michael@0: if (!script) michael@0: break; michael@0: RootedValue result(cx); michael@0: JS_ExecuteScript(cx, global, script, &result); michael@0: } while (0); michael@0: michael@0: DestroyContext(cx, false); michael@0: JS_DestroyRuntime(rt); michael@0: michael@0: js_delete(input); michael@0: } michael@0: michael@0: Vector workerThreads; michael@0: michael@0: static bool michael@0: EvalInWorker(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.get(0).isString()) { michael@0: JS_ReportError(cx, "Invalid arguments to evalInWorker"); michael@0: return false; michael@0: } michael@0: michael@0: if (!args[0].toString()->ensureLinear(cx)) michael@0: return false; michael@0: michael@0: JSLinearString *str = &args[0].toString()->asLinear(); michael@0: michael@0: jschar *chars = (jschar *) js_malloc(str->length() * sizeof(jschar)); michael@0: if (!chars) michael@0: return false; michael@0: PodCopy(chars, str->chars(), str->length()); michael@0: michael@0: WorkerInput *input = js_new(cx->runtime(), chars, str->length()); michael@0: if (!input) michael@0: return false; michael@0: michael@0: PRThread *thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); michael@0: if (!thread || !workerThreads.append(thread)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static bool michael@0: ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.get(0).isObject()) { michael@0: JS_ReportError(cx, "shapeOf: object expected"); michael@0: return false; michael@0: } michael@0: JSObject *obj = &args[0].toObject(); michael@0: args.rval().set(JS_NumberValue(double(uintptr_t(obj->lastProperty()) >> 3))); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * If referent has an own property named id, copy that property to obj[id]. michael@0: * Since obj is native, this isn't totally transparent; properties of a michael@0: * non-native referent may be simplified to data properties. michael@0: */ michael@0: static bool michael@0: CopyProperty(JSContext *cx, HandleObject obj, HandleObject referent, HandleId id, michael@0: MutableHandleObject objp) michael@0: { michael@0: RootedShape shape(cx); michael@0: Rooted desc(cx); michael@0: RootedObject obj2(cx); michael@0: michael@0: objp.set(nullptr); michael@0: if (referent->isNative()) { michael@0: if (!LookupNativeProperty(cx, referent, id, &obj2, &shape)) michael@0: return false; michael@0: if (obj2 != referent) michael@0: return true; michael@0: michael@0: if (shape->hasSlot()) { michael@0: desc.value().set(referent->nativeGetSlot(shape->slot())); michael@0: } else { michael@0: desc.value().setUndefined(); michael@0: } michael@0: michael@0: desc.setAttributes(shape->attributes()); michael@0: desc.setGetter(shape->getter()); michael@0: if (!desc.getter() && !desc.hasGetterObject()) michael@0: desc.setGetter(JS_PropertyStub); michael@0: desc.setSetter(shape->setter()); michael@0: if (!desc.setter() && !desc.hasSetterObject()) michael@0: desc.setSetter(JS_StrictPropertyStub); michael@0: } else if (referent->is()) { michael@0: if (!Proxy::getOwnPropertyDescriptor(cx, referent, id, &desc)) michael@0: return false; michael@0: if (!desc.object()) michael@0: return true; michael@0: } else { michael@0: if (!JSObject::lookupGeneric(cx, referent, id, objp, &shape)) michael@0: return false; michael@0: if (objp != referent) michael@0: return true; michael@0: RootedValue value(cx); michael@0: if (!JSObject::getGeneric(cx, referent, referent, id, &value) || michael@0: !JSObject::getGenericAttributes(cx, referent, id, &desc.attributesRef())) michael@0: { michael@0: return false; michael@0: } michael@0: desc.value().set(value); michael@0: desc.attributesRef() &= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; michael@0: desc.setGetter(JS_PropertyStub); michael@0: desc.setSetter(JS_StrictPropertyStub); michael@0: } michael@0: michael@0: objp.set(obj); michael@0: return DefineNativeProperty(cx, obj, id, desc.value(), desc.getter(), desc.setter(), michael@0: desc.attributes()); michael@0: } michael@0: michael@0: static bool michael@0: resolver_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) michael@0: { michael@0: jsval v = JS_GetReservedSlot(obj, 0); michael@0: Rooted vobj(cx, &v.toObject()); michael@0: return CopyProperty(cx, obj, vobj, id, objp); michael@0: } michael@0: michael@0: static bool michael@0: resolver_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: jsval v = JS_GetReservedSlot(obj, 0); michael@0: RootedObject referent(cx, JSVAL_TO_OBJECT(v)); michael@0: michael@0: AutoIdArray ida(cx, JS_Enumerate(cx, referent)); michael@0: bool ok = !!ida; michael@0: RootedObject ignore(cx); michael@0: for (size_t i = 0; ok && i < ida.length(); i++) { michael@0: Rooted id(cx, ida[i]); michael@0: ok = CopyProperty(cx, obj, referent, id, &ignore); michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: static const JSClass resolver_class = { michael@0: "resolver", michael@0: JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1), michael@0: JS_PropertyStub, JS_DeletePropertyStub, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: resolver_enumerate, (JSResolveOp)resolver_resolve, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: michael@0: static bool michael@0: Resolver(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject referent(cx); michael@0: if (!JS_ValueToObject(cx, args.get(0), &referent)) michael@0: return false; michael@0: if (!referent) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, michael@0: args.get(0).isNull() ? "null" : "undefined", "object"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject proto(cx, nullptr); michael@0: if (!args.get(1).isNullOrUndefined()) { michael@0: if (!JS_ValueToObject(cx, args.get(1), &proto)) michael@0: return false; michael@0: } michael@0: michael@0: RootedObject parent(cx, JS_GetParent(referent)); michael@0: JSObject *result = (args.length() > 1 michael@0: ? JS_NewObjectWithGivenProto michael@0: : JS_NewObject)(cx, &resolver_class, proto, parent); michael@0: if (!result) michael@0: return false; michael@0: michael@0: JS_SetReservedSlot(result, 0, ObjectValue(*referent)); michael@0: args.rval().setObject(*result); michael@0: return true; michael@0: } michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: michael@0: /* michael@0: * Check that t1 comes strictly before t2. The function correctly deals with michael@0: * wrap-around between t2 and t1 assuming that t2 and t1 stays within INT32_MAX michael@0: * from each other. We use MAX_TIMEOUT_INTERVAL to enforce this restriction. michael@0: */ michael@0: static bool michael@0: IsBefore(int64_t t1, int64_t t2) michael@0: { michael@0: return int32_t(t1 - t2) < 0; michael@0: } michael@0: michael@0: static bool michael@0: Sleep_fn(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: int64_t t_ticks; michael@0: michael@0: if (args.length() == 0) { michael@0: t_ticks = 0; michael@0: } else { michael@0: double t_secs; michael@0: michael@0: if (!ToNumber(cx, args[0], &t_secs)) michael@0: return false; michael@0: michael@0: /* NB: The next condition also filter out NaNs. */ michael@0: if (!(t_secs <= MAX_TIMEOUT_INTERVAL)) { michael@0: JS_ReportError(cx, "Excessive sleep interval"); michael@0: return false; michael@0: } michael@0: t_ticks = (t_secs <= 0.0) michael@0: ? 0 michael@0: : int64_t(PRMJ_USEC_PER_SEC * t_secs); michael@0: } michael@0: PR_Lock(gWatchdogLock); michael@0: int64_t to_wakeup = PRMJ_Now() + t_ticks; michael@0: for (;;) { michael@0: PR_WaitCondVar(gSleepWakeup, t_ticks); michael@0: if (gServiceInterrupt) michael@0: break; michael@0: int64_t now = PRMJ_Now(); michael@0: if (!IsBefore(now, to_wakeup)) michael@0: break; michael@0: t_ticks = to_wakeup - now; michael@0: } michael@0: PR_Unlock(gWatchdogLock); michael@0: return !gServiceInterrupt; michael@0: } michael@0: michael@0: static bool michael@0: InitWatchdog(JSRuntime *rt) michael@0: { michael@0: JS_ASSERT(!gWatchdogThread); michael@0: gWatchdogLock = PR_NewLock(); michael@0: if (gWatchdogLock) { michael@0: gWatchdogWakeup = PR_NewCondVar(gWatchdogLock); michael@0: if (gWatchdogWakeup) { michael@0: gSleepWakeup = PR_NewCondVar(gWatchdogLock); michael@0: if (gSleepWakeup) michael@0: return true; michael@0: PR_DestroyCondVar(gWatchdogWakeup); michael@0: } michael@0: PR_DestroyLock(gWatchdogLock); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static void michael@0: KillWatchdog() michael@0: { michael@0: PRThread *thread; michael@0: michael@0: PR_Lock(gWatchdogLock); michael@0: thread = gWatchdogThread; michael@0: if (thread) { michael@0: /* michael@0: * The watchdog thread is running, tell it to terminate waking it up michael@0: * if necessary. michael@0: */ michael@0: gWatchdogThread = nullptr; michael@0: PR_NotifyCondVar(gWatchdogWakeup); michael@0: } michael@0: PR_Unlock(gWatchdogLock); michael@0: if (thread) michael@0: PR_JoinThread(thread); michael@0: PR_DestroyCondVar(gSleepWakeup); michael@0: PR_DestroyCondVar(gWatchdogWakeup); michael@0: PR_DestroyLock(gWatchdogLock); michael@0: } michael@0: michael@0: static void michael@0: WatchdogMain(void *arg) michael@0: { michael@0: PR_SetCurrentThreadName("JS Watchdog"); michael@0: michael@0: JSRuntime *rt = (JSRuntime *) arg; michael@0: michael@0: PR_Lock(gWatchdogLock); michael@0: while (gWatchdogThread) { michael@0: int64_t now = PRMJ_Now(); michael@0: if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) { michael@0: /* michael@0: * The timeout has just expired. Request an interrupt callback michael@0: * outside the lock. michael@0: */ michael@0: gWatchdogHasTimeout = false; michael@0: PR_Unlock(gWatchdogLock); michael@0: CancelExecution(rt); michael@0: PR_Lock(gWatchdogLock); michael@0: michael@0: /* Wake up any threads doing sleep. */ michael@0: PR_NotifyAllCondVar(gSleepWakeup); michael@0: } else { michael@0: if (gWatchdogHasTimeout) { michael@0: /* michael@0: * Time hasn't expired yet. Simulate an interrupt callback michael@0: * which doesn't abort execution. michael@0: */ michael@0: JS_RequestInterruptCallback(rt); michael@0: } michael@0: michael@0: uint64_t sleepDuration = PR_INTERVAL_NO_TIMEOUT; michael@0: if (gWatchdogHasTimeout) michael@0: sleepDuration = PR_TicksPerSecond() / 10; michael@0: mozilla::DebugOnly status = michael@0: PR_WaitCondVar(gWatchdogWakeup, sleepDuration); michael@0: JS_ASSERT(status == PR_SUCCESS); michael@0: } michael@0: } michael@0: PR_Unlock(gWatchdogLock); michael@0: } michael@0: michael@0: static bool michael@0: ScheduleWatchdog(JSRuntime *rt, double t) michael@0: { michael@0: if (t <= 0) { michael@0: PR_Lock(gWatchdogLock); michael@0: gWatchdogHasTimeout = false; michael@0: PR_Unlock(gWatchdogLock); michael@0: return true; michael@0: } michael@0: michael@0: int64_t interval = int64_t(ceil(t * PRMJ_USEC_PER_SEC)); michael@0: int64_t timeout = PRMJ_Now() + interval; michael@0: PR_Lock(gWatchdogLock); michael@0: if (!gWatchdogThread) { michael@0: JS_ASSERT(!gWatchdogHasTimeout); michael@0: gWatchdogThread = PR_CreateThread(PR_USER_THREAD, michael@0: WatchdogMain, michael@0: rt, michael@0: PR_PRIORITY_NORMAL, michael@0: PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, michael@0: 0); michael@0: if (!gWatchdogThread) { michael@0: PR_Unlock(gWatchdogLock); michael@0: return false; michael@0: } michael@0: } else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) { michael@0: PR_NotifyCondVar(gWatchdogWakeup); michael@0: } michael@0: gWatchdogHasTimeout = true; michael@0: gWatchdogTimeout = timeout; michael@0: PR_Unlock(gWatchdogLock); michael@0: return true; michael@0: } michael@0: michael@0: #else /* !JS_THREADSAFE */ michael@0: michael@0: #ifdef XP_WIN michael@0: static HANDLE gTimerHandle = 0; michael@0: michael@0: VOID CALLBACK michael@0: TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) michael@0: { michael@0: CancelExecution((JSRuntime *) lpParameter); michael@0: } michael@0: michael@0: #else michael@0: michael@0: static void michael@0: AlarmHandler(int sig) michael@0: { michael@0: CancelExecution(gRuntime); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: static bool michael@0: InitWatchdog(JSRuntime *rt) michael@0: { michael@0: gRuntime = rt; michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: KillWatchdog() michael@0: { michael@0: ScheduleWatchdog(gRuntime, -1); michael@0: } michael@0: michael@0: static bool michael@0: ScheduleWatchdog(JSRuntime *rt, double t) michael@0: { michael@0: #ifdef XP_WIN michael@0: if (gTimerHandle) { michael@0: DeleteTimerQueueTimer(nullptr, gTimerHandle, nullptr); michael@0: gTimerHandle = 0; michael@0: } michael@0: if (t > 0 && michael@0: !CreateTimerQueueTimer(&gTimerHandle, michael@0: nullptr, michael@0: (WAITORTIMERCALLBACK)TimerCallback, michael@0: rt, michael@0: DWORD(ceil(t * 1000.0)), michael@0: 0, michael@0: WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) { michael@0: gTimerHandle = 0; michael@0: return false; michael@0: } michael@0: #else michael@0: /* FIXME: use setitimer when available for sub-second resolution. */ michael@0: if (t <= 0) { michael@0: alarm(0); michael@0: signal(SIGALRM, nullptr); michael@0: } else { michael@0: signal(SIGALRM, AlarmHandler); /* set the Alarm signal capture */ michael@0: alarm(ceil(t)); michael@0: } michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: #endif /* !JS_THREADSAFE */ michael@0: michael@0: static void michael@0: CancelExecution(JSRuntime *rt) michael@0: { michael@0: gServiceInterrupt = true; michael@0: JS_RequestInterruptCallback(rt); michael@0: michael@0: if (!gInterruptFunc.ref().get().isNull()) { michael@0: static const char msg[] = "Script runs for too long, terminating.\n"; michael@0: #if defined(XP_UNIX) && !defined(JS_THREADSAFE) michael@0: /* It is not safe to call fputs from signals. */ michael@0: /* Dummy assignment avoids GCC warning on "attribute warn_unused_result" */ michael@0: ssize_t dummy = write(2, msg, sizeof(msg) - 1); michael@0: (void)dummy; michael@0: #else michael@0: fputs(msg, stderr); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: SetTimeoutValue(JSContext *cx, double t) michael@0: { michael@0: /* NB: The next condition also filter out NaNs. */ michael@0: if (!(t <= MAX_TIMEOUT_INTERVAL)) { michael@0: JS_ReportError(cx, "Excessive timeout value"); michael@0: return false; michael@0: } michael@0: gTimeoutInterval = t; michael@0: if (!ScheduleWatchdog(cx->runtime(), t)) { michael@0: JS_ReportError(cx, "Failed to create the watchdog"); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Timeout(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: args.rval().setNumber(gTimeoutInterval); michael@0: return true; michael@0: } michael@0: michael@0: if (args.length() > 2) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: double t; michael@0: if (!ToNumber(cx, args[0], &t)) michael@0: return false; michael@0: michael@0: if (args.length() > 1) { michael@0: RootedValue value(cx, args[1]); michael@0: if (!value.isObject() || !value.toObject().is()) { michael@0: JS_ReportError(cx, "Second argument must be a timeout function"); michael@0: return false; michael@0: } michael@0: gInterruptFunc.ref() = value; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return SetTimeoutValue(cx, t); michael@0: } michael@0: michael@0: static bool michael@0: InterruptIf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() != 1) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: if (ToBoolean(args[0])) { michael@0: gServiceInterrupt = true; michael@0: JS_RequestInterruptCallback(cx->runtime()); michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SetInterruptCallback(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() != 1) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: RootedValue value(cx, args[0]); michael@0: if (!value.isObject() || !value.toObject().is()) { michael@0: JS_ReportError(cx, "Argument must be a function"); michael@0: return false; michael@0: } michael@0: gInterruptFunc.ref() = value; michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Elapsed(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: double d = 0.0; michael@0: JSShellContextData *data = GetContextData(cx); michael@0: if (data) michael@0: d = PRMJ_Now() - data->startTime; michael@0: args.rval().setDouble(d); michael@0: return true; michael@0: } michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: Parent(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1) { michael@0: JS_ReportError(cx, "Wrong number of arguments"); michael@0: return false; michael@0: } michael@0: michael@0: Value v = args[0]; michael@0: if (JSVAL_IS_PRIMITIVE(v)) { michael@0: JS_ReportError(cx, "Only objects have parents!"); michael@0: return false; michael@0: } michael@0: michael@0: Rooted parent(cx, JS_GetParent(&v.toObject())); michael@0: args.rval().setObjectOrNull(parent); michael@0: michael@0: /* Outerize if necessary. Embrace the ugliness! */ michael@0: if (parent) { michael@0: if (JSObjectOp op = parent->getClass()->ext.outerObject) michael@0: args.rval().setObjectOrNull(op(cx, parent)); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Compile(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "compile", "0", "s"); michael@0: return false; michael@0: } michael@0: if (!args[0].isString()) { michael@0: const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); michael@0: JS_ReportError(cx, "expected string to compile, got %s", typeName); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: JSString *scriptContents = args[0].toString(); michael@0: JS::AutoSaveContextOptions asco(cx); michael@0: JS::ContextOptionsRef(cx).setNoScriptRval(true); michael@0: JS::CompileOptions options(cx); michael@0: options.setIntroductionType("js shell compile") michael@0: .setFileAndLine("", 1) michael@0: .setCompileAndGo(true); michael@0: bool ok = JS_CompileUCScript(cx, global, JS_GetStringCharsZ(cx, scriptContents), michael@0: JS_GetStringLength(scriptContents), options); michael@0: args.rval().setUndefined(); michael@0: return ok; michael@0: } michael@0: michael@0: static bool michael@0: Parse(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: using namespace js::frontend; michael@0: michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "parse", "0", "s"); michael@0: return false; michael@0: } michael@0: if (!args[0].isString()) { michael@0: const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); michael@0: JS_ReportError(cx, "expected string to parse, got %s", typeName); michael@0: return false; michael@0: } michael@0: michael@0: JSString *scriptContents = args[0].toString(); michael@0: CompileOptions options(cx); michael@0: options.setIntroductionType("js shell parse") michael@0: .setFileAndLine("", 1) michael@0: .setCompileAndGo(false); michael@0: Parser parser(cx, &cx->tempLifoAlloc(), options, michael@0: JS_GetStringCharsZ(cx, scriptContents), michael@0: JS_GetStringLength(scriptContents), michael@0: /* foldConstants = */ true, nullptr, nullptr); michael@0: michael@0: ParseNode *pn = parser.parse(nullptr); michael@0: if (!pn) michael@0: return false; michael@0: #ifdef DEBUG michael@0: DumpParseTree(pn); michael@0: fputc('\n', stderr); michael@0: #endif michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SyntaxParse(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: using namespace js::frontend; michael@0: michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "parse", "0", "s"); michael@0: return false; michael@0: } michael@0: if (!args[0].isString()) { michael@0: const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); michael@0: JS_ReportError(cx, "expected string to parse, got %s", typeName); michael@0: return false; michael@0: } michael@0: michael@0: JSString *scriptContents = args[0].toString(); michael@0: CompileOptions options(cx); michael@0: options.setIntroductionType("js shell syntaxParse") michael@0: .setFileAndLine("", 1) michael@0: .setCompileAndGo(false); michael@0: michael@0: const jschar *chars = JS_GetStringCharsZ(cx, scriptContents); michael@0: size_t length = JS_GetStringLength(scriptContents); michael@0: Parser parser(cx, &cx->tempLifoAlloc(), michael@0: options, chars, length, false, nullptr, nullptr); michael@0: michael@0: bool succeeded = parser.parse(nullptr); michael@0: if (cx->isExceptionPending()) michael@0: return false; michael@0: michael@0: if (!succeeded && !parser.hadAbortedSyntaxParse()) { michael@0: // If no exception is posted, either there was an OOM or a language michael@0: // feature unhandled by the syntax parser was encountered. michael@0: JS_ASSERT(cx->runtime()->hadOutOfMemory); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setBoolean(succeeded); michael@0: return true; michael@0: } michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: michael@0: class OffThreadState { michael@0: public: michael@0: enum State { michael@0: IDLE, /* ready to work; no token, no source */ michael@0: COMPILING, /* working; no token, have source */ michael@0: DONE /* compilation done: have token and source */ michael@0: }; michael@0: michael@0: OffThreadState() : monitor(), state(IDLE), token() { } michael@0: bool init() { return monitor.init(); } michael@0: michael@0: bool startIfIdle(JSContext *cx, JSString *newSource) { michael@0: AutoLockMonitor alm(monitor); michael@0: if (state != IDLE) michael@0: return false; michael@0: michael@0: JS_ASSERT(!token); michael@0: michael@0: source.construct(cx, newSource); michael@0: michael@0: state = COMPILING; michael@0: return true; michael@0: } michael@0: michael@0: void abandon(JSContext *cx) { michael@0: AutoLockMonitor alm(monitor); michael@0: JS_ASSERT(state == COMPILING); michael@0: JS_ASSERT(!token); michael@0: JS_ASSERT(source.ref()); michael@0: michael@0: source.destroy(); michael@0: michael@0: state = IDLE; michael@0: } michael@0: michael@0: void markDone(void *newToken) { michael@0: AutoLockMonitor alm(monitor); michael@0: JS_ASSERT(state == COMPILING); michael@0: JS_ASSERT(!token); michael@0: JS_ASSERT(source.ref()); michael@0: JS_ASSERT(newToken); michael@0: michael@0: token = newToken; michael@0: state = DONE; michael@0: alm.notify(); michael@0: } michael@0: michael@0: void *waitUntilDone(JSContext *cx) { michael@0: AutoLockMonitor alm(monitor); michael@0: if (state == IDLE) michael@0: return nullptr; michael@0: michael@0: if (state == COMPILING) { michael@0: while (state != DONE) michael@0: alm.wait(); michael@0: } michael@0: michael@0: JS_ASSERT(source.ref()); michael@0: source.destroy(); michael@0: michael@0: JS_ASSERT(token); michael@0: void *holdToken = token; michael@0: token = nullptr; michael@0: state = IDLE; michael@0: return holdToken; michael@0: } michael@0: michael@0: private: michael@0: Monitor monitor; michael@0: State state; michael@0: void *token; michael@0: Maybe source; michael@0: }; michael@0: michael@0: static OffThreadState offThreadState; michael@0: michael@0: static void michael@0: OffThreadCompileScriptCallback(void *token, void *callbackData) michael@0: { michael@0: offThreadState.markDone(token); michael@0: } michael@0: michael@0: static bool michael@0: OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "offThreadCompileScript", "0", "s"); michael@0: return false; michael@0: } michael@0: if (!args[0].isString()) { michael@0: const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); michael@0: JS_ReportError(cx, "expected string to parse, got %s", typeName); michael@0: return false; michael@0: } michael@0: michael@0: JSAutoByteString fileNameBytes; michael@0: CompileOptions options(cx); michael@0: options.setIntroductionType("js shell offThreadCompileScript") michael@0: .setFileAndLine("", 1); michael@0: michael@0: if (args.length() >= 2) { michael@0: if (args[1].isPrimitive()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject opts(cx, &args[1].toObject()); michael@0: if (!ParseCompileOptions(cx, options, opts, fileNameBytes)) michael@0: return false; michael@0: } michael@0: michael@0: // These option settings must override whatever the caller requested. michael@0: options.setCompileAndGo(true) michael@0: .setSourceIsLazy(false); michael@0: michael@0: // We assume the caller wants caching if at all possible, ignoring michael@0: // heuristics that make sense for a real browser. michael@0: options.forceAsync = true; michael@0: michael@0: JSString *scriptContents = args[0].toString(); michael@0: const jschar *chars = JS_GetStringCharsZ(cx, scriptContents); michael@0: if (!chars) michael@0: return false; michael@0: size_t length = JS_GetStringLength(scriptContents); michael@0: michael@0: if (!JS::CanCompileOffThread(cx, options, length)) { michael@0: JS_ReportError(cx, "cannot compile code on worker thread"); michael@0: return false; michael@0: } michael@0: michael@0: if (!offThreadState.startIfIdle(cx, scriptContents)) { michael@0: JS_ReportError(cx, "called offThreadCompileScript without calling runOffThreadScript" michael@0: " to receive prior off-thread compilation"); michael@0: return false; michael@0: } michael@0: michael@0: if (!JS::CompileOffThread(cx, options, chars, length, michael@0: OffThreadCompileScriptCallback, nullptr)) michael@0: { michael@0: offThreadState.abandon(cx); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: runOffThreadScript(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: void *token = offThreadState.waitUntilDone(cx); michael@0: if (!token) { michael@0: JS_ReportError(cx, "called runOffThreadScript when no compilation is pending"); michael@0: return false; michael@0: } michael@0: michael@0: RootedScript script(cx, JS::FinishOffThreadScript(cx, cx->runtime(), token)); michael@0: if (!script) michael@0: return false; michael@0: michael@0: return JS_ExecuteScript(cx, cx->global(), script, args.rval()); michael@0: } michael@0: michael@0: #endif // JS_THREADSAFE michael@0: michael@0: struct FreeOnReturn michael@0: { michael@0: JSContext *cx; michael@0: const char *ptr; michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: michael@0: FreeOnReturn(JSContext *cx, const char *ptr = nullptr michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM) michael@0: : cx(cx), ptr(ptr) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: void init(const char *ptr) { michael@0: JS_ASSERT(!this->ptr); michael@0: this->ptr = ptr; michael@0: } michael@0: michael@0: ~FreeOnReturn() { michael@0: JS_free(cx, (void*)ptr); michael@0: } michael@0: }; michael@0: michael@0: static bool michael@0: ReadFile(JSContext *cx, unsigned argc, jsval *vp, bool scriptRelative) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1 || args.length() > 2) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, michael@0: args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, michael@0: "snarf"); michael@0: return false; michael@0: } michael@0: michael@0: if (!args[0].isString() || (args.length() == 2 && !args[1].isString())) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "snarf"); michael@0: return false; michael@0: } michael@0: michael@0: RootedString givenPath(cx, args[0].toString()); michael@0: RootedString str(cx, ResolvePath(cx, givenPath, scriptRelative ? ScriptRelative : RootRelative)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JSAutoByteString filename(cx, str); michael@0: if (!filename) michael@0: return false; michael@0: michael@0: if (args.length() > 1) { michael@0: JSString *opt = JS::ToString(cx, args[1]); michael@0: if (!opt) michael@0: return false; michael@0: bool match; michael@0: if (!JS_StringEqualsAscii(cx, opt, "binary", &match)) michael@0: return false; michael@0: if (match) { michael@0: JSObject *obj; michael@0: if (!(obj = FileAsTypedArray(cx, filename.ptr()))) michael@0: return false; michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (!(str = FileAsString(cx, filename.ptr()))) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Snarf(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: return ReadFile(cx, argc, vp, false); michael@0: } michael@0: michael@0: static bool michael@0: ReadRelativeToScript(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: return ReadFile(cx, argc, vp, true); michael@0: } michael@0: michael@0: static bool michael@0: redirect(JSContext *cx, FILE* fp, HandleString relFilename) michael@0: { michael@0: RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative)); michael@0: if (!filename) michael@0: return false; michael@0: JSAutoByteString filenameABS(cx, filename); michael@0: if (!filenameABS) michael@0: return false; michael@0: if (freopen(filenameABS.ptr(), "wb", fp) == nullptr) { michael@0: JS_ReportError(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno)); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: RedirectOutput(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() < 1 || args.length() > 2) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "redirect"); michael@0: return false; michael@0: } michael@0: michael@0: if (args[0].isString()) { michael@0: RootedString stdoutPath(cx, args[0].toString()); michael@0: if (!stdoutPath) michael@0: return false; michael@0: if (!redirect(cx, stdout, stdoutPath)) michael@0: return false; michael@0: } michael@0: michael@0: if (args.length() > 1 && args[1].isString()) { michael@0: RootedString stderrPath(cx, args[1].toString()); michael@0: if (!stderrPath) michael@0: return false; michael@0: if (!redirect(cx, stderr, stderrPath)) michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: System(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, michael@0: "system"); michael@0: return false; michael@0: } michael@0: michael@0: JSString *str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JSAutoByteString command(cx, str); michael@0: if (!command) michael@0: return false; michael@0: michael@0: int result = system(command.ptr()); michael@0: args.rval().setInt32(result); michael@0: return true; michael@0: } michael@0: michael@0: static int sArgc; michael@0: static char **sArgv; michael@0: michael@0: class AutoCStringVector michael@0: { michael@0: Vector argv_; michael@0: public: michael@0: AutoCStringVector(JSContext *cx) : argv_(cx) {} michael@0: ~AutoCStringVector() { michael@0: for (size_t i = 0; i < argv_.length(); i++) michael@0: js_free(argv_[i]); michael@0: } michael@0: bool append(char *arg) { michael@0: if (!argv_.append(arg)) { michael@0: js_free(arg); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: char* const* get() const { michael@0: return argv_.begin(); michael@0: } michael@0: size_t length() const { michael@0: return argv_.length(); michael@0: } michael@0: char *operator[](size_t i) const { michael@0: return argv_[i]; michael@0: } michael@0: void replace(size_t i, char *arg) { michael@0: js_free(argv_[i]); michael@0: argv_[i] = arg; michael@0: } michael@0: char *back() const { michael@0: return argv_.back(); michael@0: } michael@0: void replaceBack(char *arg) { michael@0: js_free(argv_.back()); michael@0: argv_.back() = arg; michael@0: } michael@0: }; michael@0: michael@0: #if defined(XP_WIN) michael@0: static bool michael@0: EscapeForShell(AutoCStringVector &argv) michael@0: { michael@0: // Windows will break arguments in argv by various spaces, so we wrap each michael@0: // argument in quotes and escape quotes within. Even with quotes, \ will be michael@0: // treated like an escape character, so inflate each \ to \\. michael@0: michael@0: for (size_t i = 0; i < argv.length(); i++) { michael@0: if (!argv[i]) michael@0: continue; michael@0: michael@0: size_t newLen = 3; // quotes before and after and null-terminator michael@0: for (char *p = argv[i]; *p; p++) { michael@0: newLen++; michael@0: if (*p == '\"' || *p == '\\') michael@0: newLen++; michael@0: } michael@0: michael@0: char *escaped = (char *)js_malloc(newLen); michael@0: if (!escaped) michael@0: return false; michael@0: michael@0: char *src = argv[i]; michael@0: char *dst = escaped; michael@0: *dst++ = '\"'; michael@0: while (*src) { michael@0: if (*src == '\"' || *src == '\\') michael@0: *dst++ = '\\'; michael@0: *dst++ = *src++; michael@0: } michael@0: *dst++ = '\"'; michael@0: *dst++ = '\0'; michael@0: JS_ASSERT(escaped + newLen == dst); michael@0: michael@0: argv.replace(i, escaped); michael@0: } michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static Vector sPropagatedFlags; michael@0: michael@0: #ifdef DEBUG michael@0: #if (defined(JS_CPU_X86) || defined(JS_CPU_X64)) && defined(JS_ION) michael@0: static bool michael@0: PropagateFlagToNestedShells(const char *flag) michael@0: { michael@0: return sPropagatedFlags.append(flag); michael@0: } michael@0: #endif michael@0: #endif michael@0: michael@0: static bool michael@0: NestedShell(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: AutoCStringVector argv(cx); michael@0: michael@0: // The first argument to the shell is its path, which we assume is our own michael@0: // argv[0]. michael@0: if (sArgc < 1) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL); michael@0: return false; michael@0: } michael@0: if (!argv.append(strdup(sArgv[0]))) michael@0: return false; michael@0: michael@0: // Propagate selected flags from the current shell michael@0: for (unsigned i = 0; i < sPropagatedFlags.length(); i++) { michael@0: char *cstr = strdup(sPropagatedFlags[i]); michael@0: if (!cstr || !argv.append(cstr)) michael@0: return false; michael@0: } michael@0: michael@0: // The arguments to nestedShell are stringified and append to argv. michael@0: RootedString str(cx); michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: str = ToString(cx, args[i]); michael@0: if (!str || !argv.append(JS_EncodeString(cx, str))) michael@0: return false; michael@0: michael@0: // As a special case, if the caller passes "--js-cache", replace that michael@0: // with "--js-cache=$(jsCacheDir)" michael@0: if (!strcmp(argv.back(), "--js-cache") && jsCacheDir) { michael@0: char *newArg = JS_smprintf("--js-cache=%s", jsCacheDir); michael@0: if (!newArg) michael@0: return false; michael@0: argv.replaceBack(newArg); michael@0: } michael@0: } michael@0: michael@0: // execv assumes argv is null-terminated michael@0: if (!argv.append(nullptr)) michael@0: return false; michael@0: michael@0: int status = 0; michael@0: #if defined(XP_WIN) michael@0: if (!EscapeForShell(argv)) michael@0: return false; michael@0: status = _spawnv(_P_WAIT, sArgv[0], argv.get()); michael@0: #else michael@0: pid_t pid = fork(); michael@0: switch (pid) { michael@0: case -1: michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL); michael@0: return false; michael@0: case 0: michael@0: (void)execv(sArgv[0], argv.get()); michael@0: exit(-1); michael@0: default: { michael@0: while (waitpid(pid, &status, 0) < 0 && errno == EINTR) michael@0: continue; michael@0: break; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (status != 0) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL); michael@0: return false; michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DecompileFunctionSomehow(JSContext *cx, unsigned argc, Value *vp, michael@0: JSString *(*decompiler)(JSContext *, HandleFunction, unsigned)) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1 || !args[0].isObject() || !args[0].toObject().is()) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: RootedFunction fun(cx, &args[0].toObject().as()); michael@0: JSString *result = decompiler(cx, fun, 0); michael@0: if (!result) michael@0: return false; michael@0: args.rval().setString(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DecompileBody(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunctionBody); michael@0: } michael@0: michael@0: static bool michael@0: DecompileFunction(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunction); michael@0: } michael@0: michael@0: static bool michael@0: DecompileThisScript(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: NonBuiltinScriptFrameIter iter(cx); michael@0: if (iter.done()) { michael@0: args.rval().setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: { michael@0: JSAutoCompartment ac(cx, iter.script()); michael@0: michael@0: RootedScript script(cx, iter.script()); michael@0: JSString *result = JS_DecompileScript(cx, script, "test", 0); michael@0: if (!result) michael@0: return false; michael@0: michael@0: args.rval().setString(result); michael@0: } michael@0: michael@0: return JS_WrapValue(cx, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: ThisFilename(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: JS::AutoFilename filename; michael@0: if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) { michael@0: args.rval().setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, filename.get()); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Wrap(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Value v = args.get(0); michael@0: if (JSVAL_IS_PRIMITIVE(v)) { michael@0: args.rval().set(v); michael@0: return true; michael@0: } michael@0: michael@0: RootedObject obj(cx, JSVAL_TO_OBJECT(v)); michael@0: JSObject *wrapped = Wrapper::New(cx, obj, &obj->global(), michael@0: &Wrapper::singleton); michael@0: if (!wrapped) michael@0: return false; michael@0: michael@0: args.rval().setObject(*wrapped); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: WrapWithProto(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: Value obj = UndefinedValue(), proto = UndefinedValue(); michael@0: if (args.length() == 2) { michael@0: obj = args[0]; michael@0: proto = args[1]; michael@0: } michael@0: if (!obj.isObject() || !proto.isObjectOrNull()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, michael@0: "wrapWithProto"); michael@0: return false; michael@0: } michael@0: michael@0: WrapperOptions options(cx); michael@0: options.setProto(proto.toObjectOrNull()); michael@0: options.selectDefaultClass(obj.toObject().isCallable()); michael@0: JSObject *wrapped = Wrapper::New(cx, &obj.toObject(), &obj.toObject().global(), michael@0: &Wrapper::singletonWithPrototype, &options); michael@0: if (!wrapped) michael@0: return false; michael@0: michael@0: args.rval().setObject(*wrapped); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NewGlobal(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: JSPrincipals *principals = nullptr; michael@0: JS::CompartmentOptions options; michael@0: options.setVersion(JSVERSION_LATEST); michael@0: michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 1 && args[0].isObject()) { michael@0: RootedObject opts(cx, &args[0].toObject()); michael@0: RootedValue v(cx); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "sameZoneAs", &v)) michael@0: return false; michael@0: if (v.isObject()) michael@0: options.setSameZoneAs(UncheckedUnwrap(&v.toObject())); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v)) michael@0: return false; michael@0: if (v.isBoolean()) michael@0: options.setInvisibleToDebugger(v.toBoolean()); michael@0: michael@0: if (!JS_GetProperty(cx, opts, "principal", &v)) michael@0: return false; michael@0: if (!v.isUndefined()) { michael@0: uint32_t bits; michael@0: if (!ToUint32(cx, v, &bits)) michael@0: return false; michael@0: principals = cx->new_(bits); michael@0: if (!principals) michael@0: return false; michael@0: JS_HoldPrincipals(principals); michael@0: } michael@0: } michael@0: michael@0: RootedObject global(cx, NewGlobalObject(cx, options, principals)); michael@0: if (principals) michael@0: JS_DropPrincipals(cx->runtime(), principals); michael@0: if (!global) michael@0: return false; michael@0: michael@0: if (!JS_WrapObject(cx, &global)) michael@0: return false; michael@0: michael@0: args.rval().setObject(*global); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (!args.get(0).isBoolean()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, michael@0: "enableStackWalkingAssertion"); michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: cx->stackIterAssertionEnabled = args[0].toBoolean(); michael@0: #endif michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GetMaxArgs(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setInt32(ARGS_LENGTH_MAX); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ObjectEmulatingUndefined(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: static const JSClass cls = { michael@0: "ObjectEmulatingUndefined", michael@0: JSCLASS_EMULATES_UNDEFINED, michael@0: JS_PropertyStub, michael@0: JS_DeletePropertyStub, michael@0: JS_PropertyStub, michael@0: JS_StrictPropertyStub, michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: RootedObject obj(cx, JS_NewObject(cx, &cls, JS::NullPtr(), JS::NullPtr())); michael@0: if (!obj) michael@0: return false; michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GetSelfHostedValue(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() != 1 || !args[0].isString()) { michael@0: JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, michael@0: "getSelfHostedValue"); michael@0: return false; michael@0: } michael@0: RootedAtom srcAtom(cx, ToAtom(cx, args[0])); michael@0: if (!srcAtom) michael@0: return false; michael@0: RootedPropertyName srcName(cx, srcAtom->asPropertyName()); michael@0: return cx->runtime()->cloneSelfHostedValue(cx, srcName, args.rval()); michael@0: } michael@0: michael@0: class ShellSourceHook: public SourceHook { michael@0: // The function we should call to lazily retrieve source code. michael@0: PersistentRootedFunction fun; michael@0: michael@0: public: michael@0: ShellSourceHook(JSContext *cx, JSFunction &fun) : fun(cx, &fun) {} michael@0: michael@0: bool load(JSContext *cx, const char *filename, jschar **src, size_t *length) { michael@0: RootedString str(cx, JS_NewStringCopyZ(cx, filename)); michael@0: if (!str) michael@0: return false; michael@0: RootedValue filenameValue(cx, StringValue(str)); michael@0: michael@0: RootedValue result(cx); michael@0: if (!Call(cx, UndefinedHandleValue, fun, filenameValue, &result)) michael@0: return false; michael@0: michael@0: str = JS::ToString(cx, result); michael@0: if (!str) michael@0: return false; michael@0: michael@0: *length = JS_GetStringLength(str); michael@0: *src = cx->pod_malloc(*length); michael@0: if (!*src) michael@0: return false; michael@0: michael@0: const jschar *chars = JS_GetStringCharsZ(cx, str); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: PodCopy(*src, chars, *length); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: static bool michael@0: WithSourceHook(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject callee(cx, &args.callee()); michael@0: michael@0: if (args.length() != 2) { michael@0: ReportUsageError(cx, callee, "Wrong number of arguments."); michael@0: return false; michael@0: } michael@0: michael@0: if (!args[0].isObject() || !args[0].toObject().is() michael@0: || !args[1].isObject() || !args[1].toObject().is()) { michael@0: ReportUsageError(cx, callee, "First and second arguments must be functions."); michael@0: return false; michael@0: } michael@0: michael@0: ShellSourceHook *hook = new ShellSourceHook(cx, args[0].toObject().as()); michael@0: if (!hook) michael@0: return false; michael@0: michael@0: SourceHook *savedHook = js::ForgetSourceHook(cx->runtime()); michael@0: js::SetSourceHook(cx->runtime(), hook); michael@0: RootedObject fun(cx, &args[1].toObject()); michael@0: bool result = Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), args.rval()); michael@0: js::SetSourceHook(cx->runtime(), savedHook); michael@0: return result; michael@0: } michael@0: michael@0: static bool michael@0: IsCachingEnabled(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setBoolean(jsCachingEnabled && jsCacheAsmJSPath != nullptr); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SetCachingEnabled(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: jsCachingEnabled = ToBoolean(args.get(0)); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: PrintProfilerEvents_Callback(const char *msg) michael@0: { michael@0: fprintf(stderr, "PROFILER EVENT: %s\n", msg); michael@0: } michael@0: michael@0: static bool michael@0: PrintProfilerEvents(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (cx->runtime()->spsProfiler.enabled()) michael@0: js::RegisterRuntimeProfilingEventMarker(cx->runtime(), &PrintProfilerEvents_Callback); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpecWithHelp shell_functions[] = { michael@0: JS_FN_HELP("version", Version, 0, 0, michael@0: "version([number])", michael@0: " Get or force a script compilation version number."), michael@0: michael@0: JS_FN_HELP("options", Options, 0, 0, michael@0: "options([option ...])", michael@0: " Get or toggle JavaScript options."), michael@0: michael@0: JS_FN_HELP("load", Load, 1, 0, michael@0: "load(['foo.js' ...])", michael@0: " Load files named by string arguments. Filename is relative to the\n" michael@0: " current working directory."), michael@0: michael@0: JS_FN_HELP("loadRelativeToScript", LoadScriptRelativeToScript, 1, 0, michael@0: "loadRelativeToScript(['foo.js' ...])", michael@0: " Load files named by string arguments. Filename is relative to the\n" michael@0: " calling script."), michael@0: michael@0: JS_FN_HELP("evaluate", Evaluate, 2, 0, michael@0: "evaluate(code[, options])", michael@0: " Evaluate code as though it were the contents of a file.\n" michael@0: " options is an optional object that may have these properties:\n" michael@0: " compileAndGo: use the compile-and-go compiler option (default: true)\n" michael@0: " noScriptRval: use the no-script-rval compiler option (default: false)\n" michael@0: " fileName: filename for error messages and debug info\n" michael@0: " lineNumber: starting line number for error messages and debug info\n" michael@0: " global: global in which to execute the code\n" michael@0: " newContext: if true, create and use a new cx (default: false)\n" michael@0: " saveFrameChain: if true, save the frame chain before evaluating code\n" michael@0: " and restore it afterwards\n" michael@0: " catchTermination: if true, catch termination (failure without\n" michael@0: " an exception value, as for slow scripts or out-of-memory)\n" michael@0: " and return 'terminated'\n" michael@0: " element: if present with value |v|, convert |v| to an object |o| and\n" michael@0: " mark the source as being attached to the DOM element |o|. If the\n" michael@0: " property is omitted or |v| is null, don't attribute the source to\n" michael@0: " any DOM element.\n" michael@0: " elementAttributeName: if present and not undefined, the name of\n" michael@0: " property of 'element' that holds this code. This is what\n" michael@0: " Debugger.Source.prototype.elementAttributeName returns.\n" michael@0: " sourceMapURL: if present with value |v|, convert |v| to a string, and\n" michael@0: " provide that as the code's source map URL. If omitted, attach no\n" michael@0: " source map URL to the code (although the code may provide one itself,\n" michael@0: " via a //#sourceMappingURL comment).\n" michael@0: " sourceIsLazy: if present and true, indicates that, after compilation, \n" michael@0: "script source should not be cached by the JS engine and should be \n" michael@0: "lazily loaded from the embedding as-needed.\n" michael@0: " loadBytecode: if true, and if the source is a CacheEntryObject,\n" michael@0: " the bytecode would be loaded and decoded from the cache entry instead\n" michael@0: " of being parsed, then it would be executed as usual.\n" michael@0: " saveBytecode: if true, and if the source is a CacheEntryObject,\n" michael@0: " the bytecode would be encoded and saved into the cache entry after\n" michael@0: " the script execution.\n" michael@0: " assertEqBytecode: if true, and if both loadBytecode and saveBytecode are \n" michael@0: " true, then the loaded bytecode and the encoded bytecode are compared.\n" michael@0: " and an assertion is raised if they differ.\n" michael@0: ), michael@0: michael@0: JS_FN_HELP("run", Run, 1, 0, michael@0: "run('foo.js')", michael@0: " Run the file named by the first argument, returning the number of\n" michael@0: " of milliseconds spent compiling and executing it."), michael@0: michael@0: JS_FN_HELP("readline", ReadLine, 0, 0, michael@0: "readline()", michael@0: " Read a single line from stdin."), michael@0: michael@0: JS_FN_HELP("print", Print, 0, 0, michael@0: "print([exp ...])", michael@0: " Evaluate and print expressions to stdout."), michael@0: michael@0: JS_FN_HELP("printErr", PrintErr, 0, 0, michael@0: "printErr([exp ...])", michael@0: " Evaluate and print expressions to stderr."), michael@0: michael@0: JS_FN_HELP("putstr", PutStr, 0, 0, michael@0: "putstr([exp])", michael@0: " Evaluate and print expression without newline."), michael@0: michael@0: JS_FN_HELP("dateNow", Now, 0, 0, michael@0: "dateNow()", michael@0: " Return the current time with sub-ms precision."), michael@0: michael@0: JS_FN_HELP("help", Help, 0, 0, michael@0: "help([name ...])", michael@0: " Display usage and help messages."), michael@0: michael@0: JS_FN_HELP("quit", Quit, 0, 0, michael@0: "quit()", michael@0: " Quit the shell."), michael@0: michael@0: JS_FN_HELP("assertEq", AssertEq, 2, 0, michael@0: "assertEq(actual, expected[, msg])", michael@0: " Throw if the first two arguments are not the same (both +0 or both -0,\n" michael@0: " both NaN, or non-zero and ===)."), michael@0: michael@0: JS_FN_HELP("setDebug", SetDebug, 1, 0, michael@0: "setDebug(debug)", michael@0: " Set debug mode."), michael@0: michael@0: JS_FN_HELP("setDebuggerHandler", SetDebuggerHandler, 1, 0, michael@0: "setDebuggerHandler(f)", michael@0: " Set handler for debugger keyword to f."), michael@0: michael@0: JS_FN_HELP("throwError", ThrowError, 0, 0, michael@0: "throwError()", michael@0: " Throw an error from JS_ReportError."), michael@0: michael@0: #ifdef DEBUG michael@0: JS_FN_HELP("disassemble", DisassembleToString, 1, 0, michael@0: "disassemble([fun])", michael@0: " Return the disassembly for the given function."), michael@0: michael@0: JS_FN_HELP("dis", Disassemble, 1, 0, michael@0: "dis([fun])", michael@0: " Disassemble functions into bytecodes."), michael@0: michael@0: JS_FN_HELP("disfile", DisassFile, 1, 0, michael@0: "disfile('foo.js')", michael@0: " Disassemble script file into bytecodes.\n" michael@0: " dis and disfile take these options as preceeding string arguments:\n" michael@0: " \"-r\" (disassemble recursively)\n" michael@0: " \"-l\" (show line numbers)"), michael@0: michael@0: JS_FN_HELP("dissrc", DisassWithSrc, 1, 0, michael@0: "dissrc([fun])", michael@0: " Disassemble functions with source lines."), michael@0: michael@0: JS_FN_HELP("dumpObject", DumpObject, 1, 0, michael@0: "dumpObject()", michael@0: " Dump an internal representation of an object."), michael@0: michael@0: JS_FN_HELP("notes", Notes, 1, 0, michael@0: "notes([fun])", michael@0: " Show source notes for functions."), michael@0: michael@0: JS_FN_HELP("findReferences", FindReferences, 1, 0, michael@0: "findReferences(target)", michael@0: " Walk the entire heap, looking for references to |target|, and return a\n" michael@0: " \"references object\" describing what we found.\n" michael@0: "\n" michael@0: " Each property of the references object describes one kind of reference. The\n" michael@0: " property's name is the label supplied to MarkObject, JS_CALL_TRACER, or what\n" michael@0: " have you, prefixed with \"edge: \" to avoid collisions with system properties\n" michael@0: " (like \"toString\" and \"__proto__\"). The property's value is an array of things\n" michael@0: " that refer to |thing| via that kind of reference. Ordinary references from\n" michael@0: " one object to another are named after the property name (with the \"edge: \"\n" michael@0: " prefix).\n" michael@0: "\n" michael@0: " Garbage collection roots appear as references from 'null'. We use the name\n" michael@0: " given to the root (with the \"edge: \" prefix) as the name of the reference.\n" michael@0: "\n" michael@0: " Note that the references object does record references from objects that are\n" michael@0: " only reachable via |thing| itself, not just the references reachable\n" michael@0: " themselves from roots that keep |thing| from being collected. (We could make\n" michael@0: " this distinction if it is useful.)\n" michael@0: "\n" michael@0: " If any references are found by the conservative scanner, the references\n" michael@0: " object will have a property named \"edge: machine stack\"; the referrers will\n" michael@0: " be 'null', because they are roots."), michael@0: michael@0: #endif michael@0: JS_FN_HELP("build", BuildDate, 0, 0, michael@0: "build()", michael@0: " Show build date and time."), michael@0: michael@0: JS_FN_HELP("intern", Intern, 1, 0, michael@0: "intern(str)", michael@0: " Internalize str in the atom table."), michael@0: michael@0: JS_FN_HELP("getpda", GetPDA, 1, 0, michael@0: "getpda(obj)", michael@0: " Get the property descriptors for obj."), michael@0: michael@0: JS_FN_HELP("getslx", GetSLX, 1, 0, michael@0: "getslx(obj)", michael@0: " Get script line extent."), michael@0: michael@0: JS_FN_HELP("evalcx", EvalInContext, 1, 0, michael@0: "evalcx(s[, o])", michael@0: " Evaluate s in optional sandbox object o.\n" michael@0: " if (s == '' && !o) return new o with eager standard classes\n" michael@0: " if (s == 'lazy' && !o) return new o with lazy standard classes"), michael@0: michael@0: JS_FN_HELP("evalInFrame", EvalInFrame, 2, 0, michael@0: "evalInFrame(n,str,save)", michael@0: " Evaluate 'str' in the nth up frame.\n" michael@0: " If 'save' (default false), save the frame chain."), michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: JS_FN_HELP("evalInWorker", EvalInWorker, 1, 0, michael@0: "evalInWorker(str)", michael@0: " Evaluate 'str' in a separate thread with its own runtime.\n"), michael@0: #endif michael@0: michael@0: JS_FN_HELP("shapeOf", ShapeOf, 1, 0, michael@0: "shapeOf(obj)", michael@0: " Get the shape of obj (an implementation detail)."), michael@0: michael@0: JS_FN_HELP("resolver", Resolver, 1, 0, michael@0: "resolver(src[, proto])", michael@0: " Create object with resolve hook that copies properties\n" michael@0: " from src. If proto is omitted, use Object.prototype."), michael@0: michael@0: #ifdef DEBUG michael@0: JS_FN_HELP("arrayInfo", js_ArrayInfo, 1, 0, michael@0: "arrayInfo(a1, a2, ...)", michael@0: " Report statistics about arrays."), michael@0: #endif michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: JS_FN_HELP("sleep", Sleep_fn, 1, 0, michael@0: "sleep(dt)", michael@0: " Sleep for dt seconds."), michael@0: #endif michael@0: michael@0: JS_FN_HELP("snarf", Snarf, 1, 0, michael@0: "snarf(filename, [\"binary\"])", michael@0: " Read filename into returned string. Filename is relative to the current\n" michael@0: " working directory."), michael@0: michael@0: JS_FN_HELP("read", Snarf, 1, 0, michael@0: "read(filename, [\"binary\"])", michael@0: " Synonym for snarf."), michael@0: michael@0: JS_FN_HELP("readRelativeToScript", ReadRelativeToScript, 1, 0, michael@0: "readRelativeToScript(filename, [\"binary\"])", michael@0: " Read filename into returned string. Filename is relative to the directory\n" michael@0: " containing the current script."), michael@0: michael@0: JS_FN_HELP("compile", Compile, 1, 0, michael@0: "compile(code)", michael@0: " Compiles a string to bytecode, potentially throwing."), michael@0: michael@0: JS_FN_HELP("parse", Parse, 1, 0, michael@0: "parse(code)", michael@0: " Parses a string, potentially throwing."), michael@0: michael@0: JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0, michael@0: "syntaxParse(code)", michael@0: " Check the syntax of a string, returning success value"), michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0, michael@0: "offThreadCompileScript(code[, options])", michael@0: " Compile |code| on a helper thread. To wait for the compilation to finish\n" michael@0: " and run the code, call |runOffThreadScript|. If present, |options| may\n" michael@0: " have properties saying how the code should be compiled:\n" michael@0: " noScriptRval: use the no-script-rval compiler option (default: false)\n" michael@0: " fileName: filename for error messages and debug info\n" michael@0: " lineNumber: starting line number for error messages and debug info\n" michael@0: " element: if present with value |v|, convert |v| to an object |o| and\n" michael@0: " mark the source as being attached to the DOM element |o|. If the\n" michael@0: " property is omitted or |v| is null, don't attribute the source to\n" michael@0: " any DOM element.\n" michael@0: " elementAttributeName: if present and not undefined, the name of\n" michael@0: " property of 'element' that holds this code. This is what\n" michael@0: " Debugger.Source.prototype.elementAttributeName returns.\n"), michael@0: michael@0: JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0, michael@0: "runOffThreadScript()", michael@0: " Wait for off-thread compilation to complete. If an error occurred,\n" michael@0: " throw the appropriate exception; otherwise, run the script and return\n" michael@0: " its value."), michael@0: michael@0: #endif michael@0: michael@0: JS_FN_HELP("timeout", Timeout, 1, 0, michael@0: "timeout([seconds], [func])", michael@0: " Get/Set the limit in seconds for the execution time for the current context.\n" michael@0: " A negative value (default) means that the execution time is unlimited.\n" michael@0: " If a second argument is provided, it will be invoked when the timer elapses.\n" michael@0: " Calling this function will replace any callback set by |setInterruptCallback|.\n"), michael@0: michael@0: JS_FN_HELP("interruptIf", InterruptIf, 1, 0, michael@0: "interruptIf(cond)", michael@0: " Requests interrupt callback if cond is true. If a callback function is set via\n" michael@0: " |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."), michael@0: michael@0: JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0, michael@0: "setInterruptCallback(func)", michael@0: " Sets func as the interrupt callback function.\n" michael@0: " Calling this function will replace any callback set by |timeout|.\n"), michael@0: michael@0: JS_FN_HELP("elapsed", Elapsed, 0, 0, michael@0: "elapsed()", michael@0: " Execution time elapsed for the current context."), michael@0: michael@0: JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0, michael@0: "decompileFunction(func)", michael@0: " Decompile a function."), michael@0: michael@0: JS_FN_HELP("decompileBody", DecompileBody, 1, 0, michael@0: "decompileBody(func)", michael@0: " Decompile a function's body."), michael@0: michael@0: JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0, michael@0: "decompileThis()", michael@0: " Decompile the currently executing script."), michael@0: michael@0: JS_FN_HELP("thisFilename", ThisFilename, 0, 0, michael@0: "thisFilename()", michael@0: " Return the filename of the current script"), michael@0: michael@0: JS_FN_HELP("wrap", Wrap, 1, 0, michael@0: "wrap(obj)", michael@0: " Wrap an object into a noop wrapper."), michael@0: michael@0: JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0, michael@0: "wrapWithProto(obj)", michael@0: " Wrap an object into a noop wrapper with prototype semantics."), michael@0: michael@0: JS_FN_HELP("newGlobal", NewGlobal, 1, 0, michael@0: "newGlobal([options])", michael@0: " Return a new global object in a new compartment. If options\n" michael@0: " is given, it may have any of the following properties:\n" michael@0: " sameZoneAs: the compartment will be in the same zone as the given object (defaults to a new zone)\n" michael@0: " invisibleToDebugger: the global will be invisible to the debugger (default false)\n" michael@0: " principal: if present, its value converted to a number must be an\n" michael@0: " integer that fits in 32 bits; use that as the new compartment's\n" michael@0: " principal. Shell principals are toys, meant only for testing; one\n" michael@0: " shell principal subsumes another if its set bits are a superset of\n" michael@0: " the other's. Thus, a principal of 0 subsumes nothing, while a\n" michael@0: " principals of ~0 subsumes all other principals. The absence of a\n" michael@0: " principal is treated as if its bits were 0xffff, for subsumption\n" michael@0: " purposes. If this property is omitted, supply no principal."), michael@0: michael@0: JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0, michael@0: "createMappedArrayBuffer(filename, [offset, [size]])", michael@0: " Create an array buffer that mmaps the given file."), michael@0: michael@0: JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0, michael@0: "enableStackWalkingAssertion(enabled)", michael@0: " Enables or disables a particularly expensive assertion in stack-walking\n" michael@0: " code. If your test isn't ridiculously thorough, such that performing this\n" michael@0: " assertion increases test duration by an order of magnitude, you shouldn't\n" michael@0: " use this."), michael@0: michael@0: JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0, michael@0: "getMaxArgs()", michael@0: " Return the maximum number of supported args for a call."), michael@0: michael@0: JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0, michael@0: "objectEmulatingUndefined()", michael@0: " Return a new object obj for which typeof obj === \"undefined\", obj == null\n" michael@0: " and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"), michael@0: michael@0: JS_FN_HELP("isCachingEnabled", IsCachingEnabled, 0, 0, michael@0: "isCachingEnabled()", michael@0: " Return whether JS caching is enabled."), michael@0: michael@0: JS_FN_HELP("setCachingEnabled", SetCachingEnabled, 1, 0, michael@0: "setCachingEnabled(b)", michael@0: " Enable or disable JS caching."), michael@0: michael@0: JS_FN_HELP("cacheEntry", CacheEntry, 1, 0, michael@0: "cacheEntry(code)", michael@0: " Return a new opaque object which emulates a cache entry of a script. This\n" michael@0: " object encapsulates the code and its cached content. The cache entry is filled\n" michael@0: " and read by the \"evaluate\" function by using it in place of the source, and\n" michael@0: " by setting \"saveBytecode\" and \"loadBytecode\" options."), michael@0: michael@0: JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0, michael@0: "printProfilerEvents()", michael@0: " Register a callback with the profiler that prints javascript profiler events\n" michael@0: " to stderr. Callback is only registered if profiling is enabled."), michael@0: michael@0: JS_FS_HELP_END michael@0: }; michael@0: michael@0: static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = { michael@0: JS_FN_HELP("clone", Clone, 1, 0, michael@0: "clone(fun[, scope])", michael@0: " Clone function object."), michael@0: michael@0: JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0, michael@0: "getSelfHostedValue()", michael@0: " Get a self-hosted value by its name. Note that these values don't get \n" michael@0: " cached, so repeatedly getting the same value creates multiple distinct clones."), michael@0: michael@0: #ifdef DEBUG michael@0: JS_FN_HELP("dumpHeap", DumpHeap, 0, 0, michael@0: "dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])", michael@0: " Interface to JS_DumpHeap with output sent to file."), michael@0: #endif michael@0: michael@0: JS_FN_HELP("parent", Parent, 1, 0, michael@0: "parent(obj)", michael@0: " Returns the parent of obj."), michael@0: michael@0: JS_FN_HELP("line2pc", LineToPC, 0, 0, michael@0: "line2pc([fun,] line)", michael@0: " Map line number to PC."), michael@0: michael@0: JS_FN_HELP("pc2line", PCToLine, 0, 0, michael@0: "pc2line(fun[, pc])", michael@0: " Map PC to line number."), michael@0: michael@0: JS_FN_HELP("redirect", RedirectOutput, 2, 0, michael@0: "redirect(stdoutFilename[, stderrFilename])", michael@0: " Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n" michael@0: " redirecting. Filenames are relative to the current working directory."), michael@0: michael@0: JS_FN_HELP("setThrowHook", SetThrowHook, 1, 0, michael@0: "setThrowHook(f)", michael@0: " Set throw hook to f."), michael@0: michael@0: JS_FN_HELP("system", System, 1, 0, michael@0: "system(command)", michael@0: " Execute command on the current host, returning result code."), michael@0: michael@0: JS_FN_HELP("nestedShell", NestedShell, 0, 0, michael@0: "nestedShell(shellArgs...)", michael@0: " Execute the given code in a new JS shell process, passing this nested shell\n" michael@0: " the arguments passed to nestedShell. argv[0] of the nested shell will be argv[0]\n" michael@0: " of the current shell (which is assumed to be the actual path to the shell.\n" michael@0: " arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n" michael@0: " be argv[2], etc."), michael@0: michael@0: JS_FN_HELP("trap", Trap, 3, 0, michael@0: "trap([fun, [pc,]] exp)", michael@0: " Trap bytecode execution."), michael@0: michael@0: JS_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0, michael@0: "assertFloat32(value, isFloat32)", michael@0: " In IonMonkey only, asserts that value has (resp. hasn't) the MIRType_Float32 if isFloat32 is true (resp. false)."), michael@0: michael@0: JS_FN_HELP("untrap", Untrap, 2, 0, michael@0: "untrap(fun[, pc])", michael@0: " Remove a trap."), michael@0: michael@0: JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0, michael@0: "withSourceHook(hook, fun)", michael@0: " Set this JS runtime's lazy source retrieval hook (that is, the hook\n" michael@0: " used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n" michael@0: " |hook|; call |fun| with no arguments; and then restore the runtime's\n" michael@0: " original hook. Return or throw whatever |fun| did. |hook| gets\n" michael@0: " passed the requested code's URL, and should return a string.\n" michael@0: "\n" michael@0: " Notes:\n" michael@0: "\n" michael@0: " 1) SpiderMonkey may assert if the returned code isn't close enough\n" michael@0: " to the script's real code, so this function is not fuzzer-safe.\n" michael@0: "\n" michael@0: " 2) The runtime can have only one source retrieval hook active at a\n" michael@0: " time. If |fun| is not careful, |hook| could be asked to retrieve the\n" michael@0: " source code for compilations that occurred long before it was set,\n" michael@0: " and that it knows nothing about. The reverse applies as well: the\n" michael@0: " original hook, that we reinstate after the call to |fun| completes,\n" michael@0: " might be asked for the source code of compilations that |fun|\n" michael@0: " performed, and which, presumably, only |hook| knows how to find.\n"), michael@0: michael@0: JS_FS_HELP_END michael@0: }; michael@0: michael@0: #ifdef MOZ_PROFILING michael@0: # define PROFILING_FUNCTION_COUNT 5 michael@0: # ifdef MOZ_CALLGRIND michael@0: # define CALLGRIND_FUNCTION_COUNT 3 michael@0: # else michael@0: # define CALLGRIND_FUNCTION_COUNT 0 michael@0: # endif michael@0: # ifdef MOZ_VTUNE michael@0: # define VTUNE_FUNCTION_COUNT 4 michael@0: # else michael@0: # define VTUNE_FUNCTION_COUNT 0 michael@0: # endif michael@0: # define EXTERNAL_FUNCTION_COUNT (PROFILING_FUNCTION_COUNT + CALLGRIND_FUNCTION_COUNT + VTUNE_FUNCTION_COUNT) michael@0: #else michael@0: # define EXTERNAL_FUNCTION_COUNT 0 michael@0: #endif michael@0: michael@0: #undef PROFILING_FUNCTION_COUNT michael@0: #undef CALLGRIND_FUNCTION_COUNT michael@0: #undef VTUNE_FUNCTION_COUNT michael@0: #undef EXTERNAL_FUNCTION_COUNT michael@0: michael@0: static bool michael@0: PrintHelpString(JSContext *cx, jsval v) michael@0: { michael@0: JSString *str = JSVAL_TO_STRING(v); michael@0: JS::Anchor a_str(str); michael@0: const jschar *chars = JS_GetStringCharsZ(cx, str); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: for (const jschar *p = chars; *p; p++) michael@0: fprintf(gOutFile, "%c", char(*p)); michael@0: michael@0: fprintf(gOutFile, "\n"); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PrintHelp(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedValue usage(cx); michael@0: if (!JS_LookupProperty(cx, obj, "usage", &usage)) michael@0: return false; michael@0: RootedValue help(cx); michael@0: if (!JS_LookupProperty(cx, obj, "help", &help)) michael@0: return false; michael@0: michael@0: if (JSVAL_IS_VOID(usage) || JSVAL_IS_VOID(help)) michael@0: return true; michael@0: michael@0: return PrintHelpString(cx, usage) && PrintHelpString(cx, help); michael@0: } michael@0: michael@0: static bool michael@0: Help(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: fprintf(gOutFile, "%s\n", JS_GetImplementationVersion()); michael@0: michael@0: RootedObject obj(cx); michael@0: if (args.length() == 0) { michael@0: RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: AutoIdArray ida(cx, JS_Enumerate(cx, global)); michael@0: if (!ida) michael@0: return false; michael@0: michael@0: for (size_t i = 0; i < ida.length(); i++) { michael@0: RootedValue v(cx); michael@0: RootedId id(cx, ida[i]); michael@0: if (!JS_LookupPropertyById(cx, global, id, &v)) michael@0: return false; michael@0: if (JSVAL_IS_PRIMITIVE(v)) { michael@0: JS_ReportError(cx, "primitive arg"); michael@0: return false; michael@0: } michael@0: obj = JSVAL_TO_OBJECT(v); michael@0: if (!PrintHelp(cx, obj)) michael@0: return false; michael@0: } michael@0: } else { michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: if (args[i].isPrimitive()) { michael@0: JS_ReportError(cx, "primitive arg"); michael@0: return false; michael@0: } michael@0: obj = args[i].toObjectOrNull(); michael@0: if (!PrintHelp(cx, obj)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { michael@0: #define MSG_DEF(name, number, count, exception, format) \ michael@0: { format, count, JSEXN_ERR } , michael@0: #include "jsshell.msg" michael@0: #undef MSG_DEF michael@0: }; michael@0: michael@0: static const JSErrorFormatString * michael@0: my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber) michael@0: { michael@0: if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) michael@0: return nullptr; michael@0: michael@0: return &jsShell_ErrorFormatString[errorNumber]; michael@0: } michael@0: michael@0: static void michael@0: my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) michael@0: { michael@0: gGotError = PrintError(cx, gErrFile, message, report, reportWarnings); michael@0: if (!JSREPORT_IS_WARNING(report->flags)) { michael@0: if (report->errorNumber == JSMSG_OUT_OF_MEMORY) { michael@0: gExitCode = EXITCODE_OUT_OF_MEMORY; michael@0: } else { michael@0: gExitCode = EXITCODE_RUNTIME_ERROR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: my_OOMCallback(JSContext *cx) michael@0: { michael@0: // If a script is running, the engine is about to throw the string "out of michael@0: // memory", which may or may not be caught. Otherwise the engine will just michael@0: // unwind and return null/false, with no exception set. michael@0: if (!JS_IsRunning(cx)) michael@0: gGotError = true; michael@0: } michael@0: michael@0: static bool michael@0: global_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: #ifdef LAZY_STANDARD_CLASSES michael@0: return JS_EnumerateStandardClasses(cx, obj); michael@0: #else michael@0: return true; michael@0: #endif michael@0: } michael@0: michael@0: static bool michael@0: global_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) michael@0: { michael@0: #ifdef LAZY_STANDARD_CLASSES michael@0: bool resolved; michael@0: michael@0: if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) michael@0: return false; michael@0: if (resolved) { michael@0: objp.set(obj); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static const JSClass global_class = { michael@0: "global", JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: global_enumerate, (JSResolveOp) global_resolve, michael@0: JS_ConvertStub, nullptr, michael@0: nullptr, nullptr, nullptr, michael@0: JS_GlobalObjectTraceHook michael@0: }; michael@0: michael@0: static bool michael@0: env_setProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: /* XXX porting may be easy, but these don't seem to supply setenv by default */ michael@0: #if !defined SOLARIS michael@0: int rv; michael@0: michael@0: RootedValue idvalue(cx, IdToValue(id)); michael@0: RootedString idstring(cx, ToString(cx, idvalue)); michael@0: JSAutoByteString idstr; michael@0: if (!idstr.encodeLatin1(cx, idstring)) michael@0: return false; michael@0: michael@0: RootedString value(cx, ToString(cx, vp)); michael@0: if (!value) michael@0: return false; michael@0: JSAutoByteString valstr; michael@0: if (!valstr.encodeLatin1(cx, value)) michael@0: return false; michael@0: michael@0: #if defined XP_WIN || defined HPUX || defined OSF1 michael@0: { michael@0: char *waste = JS_smprintf("%s=%s", idstr.ptr(), valstr.ptr()); michael@0: if (!waste) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: rv = putenv(waste); michael@0: #ifdef XP_WIN michael@0: /* michael@0: * HPUX9 at least still has the bad old non-copying putenv. michael@0: * michael@0: * Per mail from , OSF1 also has a putenv michael@0: * that will crash if you pass it an auto char array (so it must place michael@0: * its argument directly in the char *environ[] array). michael@0: */ michael@0: JS_smprintf_free(waste); michael@0: #endif michael@0: } michael@0: #else michael@0: rv = setenv(idstr.ptr(), valstr.ptr(), 1); michael@0: #endif michael@0: if (rv < 0) { michael@0: JS_ReportError(cx, "can't set env variable %s to %s", idstr.ptr(), valstr.ptr()); michael@0: return false; michael@0: } michael@0: vp.set(StringValue(value)); michael@0: #endif /* !defined SOLARIS */ michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: env_enumerate(JSContext *cx, HandleObject obj) michael@0: { michael@0: static bool reflected; michael@0: char **evp, *name, *value; michael@0: RootedString valstr(cx); michael@0: bool ok; michael@0: michael@0: if (reflected) michael@0: return true; michael@0: michael@0: for (evp = (char **)JS_GetPrivate(obj); (name = *evp) != nullptr; evp++) { michael@0: value = strchr(name, '='); michael@0: if (!value) michael@0: continue; michael@0: *value++ = '\0'; michael@0: valstr = JS_NewStringCopyZ(cx, value); michael@0: ok = valstr && JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE); michael@0: value[-1] = '='; michael@0: if (!ok) michael@0: return false; michael@0: } michael@0: michael@0: reflected = true; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: env_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) michael@0: { michael@0: RootedValue idvalue(cx, IdToValue(id)); michael@0: RootedString idstring(cx, ToString(cx, idvalue)); michael@0: JSAutoByteString idstr; michael@0: if (!idstr.encodeLatin1(cx, idstring)) michael@0: return false; michael@0: michael@0: const char *name = idstr.ptr(); michael@0: const char *value = getenv(name); michael@0: if (value) { michael@0: RootedString valstr(cx, JS_NewStringCopyZ(cx, value)); michael@0: if (!valstr) michael@0: return false; michael@0: if (!JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE)) michael@0: return false; michael@0: objp.set(obj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static const JSClass env_class = { michael@0: "environment", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, michael@0: JS_PropertyStub, JS_DeletePropertyStub, michael@0: JS_PropertyStub, env_setProperty, michael@0: env_enumerate, (JSResolveOp) env_resolve, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: /* michael@0: * Define a FakeDOMObject constructor. It returns an object with a getter, michael@0: * setter and method with attached JitInfo. This object can be used to test michael@0: * IonMonkey DOM optimizations in the shell. michael@0: */ michael@0: static uint32_t DOM_OBJECT_SLOT = 0; michael@0: michael@0: static bool michael@0: dom_genericGetter(JSContext* cx, unsigned argc, JS::Value *vp); michael@0: michael@0: static bool michael@0: dom_genericSetter(JSContext* cx, unsigned argc, JS::Value *vp); michael@0: michael@0: static bool michael@0: dom_genericMethod(JSContext *cx, unsigned argc, JS::Value *vp); michael@0: michael@0: #ifdef DEBUG michael@0: static const JSClass *GetDomClass(); michael@0: #endif michael@0: michael@0: static bool michael@0: dom_get_x(JSContext* cx, HandleObject obj, void *self, JSJitGetterCallArgs args) michael@0: { michael@0: JS_ASSERT(JS_GetClass(obj) == GetDomClass()); michael@0: JS_ASSERT(self == (void *)0x1234); michael@0: args.rval().set(JS_NumberValue(double(3.14))); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: dom_set_x(JSContext* cx, HandleObject obj, void *self, JSJitSetterCallArgs args) michael@0: { michael@0: JS_ASSERT(JS_GetClass(obj) == GetDomClass()); michael@0: JS_ASSERT(self == (void *)0x1234); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: dom_doFoo(JSContext* cx, HandleObject obj, void *self, const JSJitMethodCallArgs& args) michael@0: { michael@0: JS_ASSERT(JS_GetClass(obj) == GetDomClass()); michael@0: JS_ASSERT(self == (void *)0x1234); michael@0: michael@0: /* Just return args.length(). */ michael@0: args.rval().setInt32(args.length()); michael@0: return true; michael@0: } michael@0: michael@0: static const JSJitInfo dom_x_getterinfo = { michael@0: { (JSJitGetterOp)dom_get_x }, michael@0: 0, /* protoID */ michael@0: 0, /* depth */ michael@0: JSJitInfo::AliasNone, /* aliasSet */ michael@0: JSJitInfo::Getter, michael@0: JSVAL_TYPE_UNKNOWN, /* returnType */ michael@0: true, /* isInfallible. False in setters. */ michael@0: true, /* isMovable */ michael@0: false, /* isInSlot */ michael@0: false, /* isTypedMethod */ michael@0: 0 /* slotIndex */ michael@0: }; michael@0: michael@0: static const JSJitInfo dom_x_setterinfo = { michael@0: { (JSJitGetterOp)dom_set_x }, michael@0: 0, /* protoID */ michael@0: 0, /* depth */ michael@0: JSJitInfo::Setter, michael@0: JSJitInfo::AliasEverything, /* aliasSet */ michael@0: JSVAL_TYPE_UNKNOWN, /* returnType */ michael@0: false, /* isInfallible. False in setters. */ michael@0: false, /* isMovable. */ michael@0: false, /* isInSlot */ michael@0: false, /* isTypedMethod */ michael@0: 0 /* slotIndex */ michael@0: }; michael@0: michael@0: static const JSJitInfo doFoo_methodinfo = { michael@0: { (JSJitGetterOp)dom_doFoo }, michael@0: 0, /* protoID */ michael@0: 0, /* depth */ michael@0: JSJitInfo::Method, michael@0: JSJitInfo::AliasEverything, /* aliasSet */ michael@0: JSVAL_TYPE_UNKNOWN, /* returnType */ michael@0: false, /* isInfallible. False in setters. */ michael@0: false, /* isMovable */ michael@0: false, /* isInSlot */ michael@0: false, /* isTypedMethod */ michael@0: 0 /* slotIndex */ michael@0: }; michael@0: michael@0: static const JSPropertySpec dom_props[] = { michael@0: {"x", michael@0: JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS, michael@0: { { (JSPropertyOp)dom_genericGetter, &dom_x_getterinfo } }, michael@0: { { (JSStrictPropertyOp)dom_genericSetter, &dom_x_setterinfo } } michael@0: }, michael@0: JS_PS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec dom_methods[] = { michael@0: JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3, JSPROP_ENUMERATE), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const JSClass dom_class = { michael@0: "FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr, /* finalize */ michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: nullptr, /* trace */ michael@0: JSCLASS_NO_INTERNAL_MEMBERS michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: static const JSClass *GetDomClass() { michael@0: return &dom_class; michael@0: } michael@0: #endif michael@0: michael@0: static bool michael@0: dom_genericGetter(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (JS_GetClass(obj) != &dom_class) { michael@0: args.rval().set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); michael@0: michael@0: const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); michael@0: MOZ_ASSERT(info->type() == JSJitInfo::Getter); michael@0: JSJitGetterOp getter = info->getter; michael@0: return getter(cx, obj, val.toPrivate(), JSJitGetterCallArgs(args)); michael@0: } michael@0: michael@0: static bool michael@0: dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JS_ASSERT(args.length() == 1); michael@0: michael@0: if (JS_GetClass(obj) != &dom_class) { michael@0: args.rval().set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); michael@0: michael@0: const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); michael@0: MOZ_ASSERT(info->type() == JSJitInfo::Setter); michael@0: JSJitSetterOp setter = info->setter; michael@0: if (!setter(cx, obj, val.toPrivate(), JSJitSetterCallArgs(args))) michael@0: return false; michael@0: args.rval().set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: dom_genericMethod(JSContext* cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (JS_GetClass(obj) != &dom_class) { michael@0: args.rval().set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); michael@0: michael@0: const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); michael@0: MOZ_ASSERT(info->type() == JSJitInfo::Method); michael@0: JSJitMethodOp method = info->method; michael@0: return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args)); michael@0: } michael@0: michael@0: static void michael@0: InitDOMObject(HandleObject obj) michael@0: { michael@0: /* Fow now just initialize to a constant we can check. */ michael@0: SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL((void *)0x1234)); michael@0: } michael@0: michael@0: static bool michael@0: dom_constructor(JSContext* cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: RootedObject callee(cx, &args.callee()); michael@0: RootedValue protov(cx); michael@0: if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov)) michael@0: return false; michael@0: michael@0: if (!protov.isObject()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "FakeDOMObject"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject proto(cx, &protov.toObject()); michael@0: RootedObject domObj(cx, JS_NewObject(cx, &dom_class, proto, JS::NullPtr())); michael@0: if (!domObj) michael@0: return false; michael@0: michael@0: InitDOMObject(domObj); michael@0: michael@0: args.rval().setObject(*domObj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: InstanceClassHasProtoAtDepth(JSObject *protoObject, uint32_t protoID, uint32_t depth) michael@0: { michael@0: /* There's only a single (fake) DOM object in the shell, so just return true. */ michael@0: return true; michael@0: } michael@0: michael@0: class ScopedFileDesc michael@0: { michael@0: intptr_t fd_; michael@0: public: michael@0: enum LockType { READ_LOCK, WRITE_LOCK }; michael@0: ScopedFileDesc(int fd, LockType lockType) michael@0: : fd_(fd) michael@0: { michael@0: if (fd == -1) michael@0: return; michael@0: if (!jsCacheOpened.compareExchange(false, true)) { michael@0: close(fd_); michael@0: fd_ = -1; michael@0: return; michael@0: } michael@0: } michael@0: ~ScopedFileDesc() { michael@0: if (fd_ == -1) michael@0: return; michael@0: JS_ASSERT(jsCacheOpened == true); michael@0: jsCacheOpened = false; michael@0: close(fd_); michael@0: } michael@0: operator intptr_t() const { michael@0: return fd_; michael@0: } michael@0: intptr_t forget() { michael@0: intptr_t ret = fd_; michael@0: fd_ = -1; michael@0: return ret; michael@0: } michael@0: }; michael@0: michael@0: // To guard against corrupted cache files generated by previous crashes, write michael@0: // asmJSCacheCookie to the first uint32_t of the file only after the file is michael@0: // fully serialized and flushed to disk. michael@0: static const uint32_t asmJSCacheCookie = 0xabbadaba; michael@0: michael@0: static bool michael@0: ShellOpenAsmJSCacheEntryForRead(HandleObject global, const jschar *begin, const jschar *limit, michael@0: size_t *serializedSizeOut, const uint8_t **memoryOut, michael@0: intptr_t *handleOut) michael@0: { michael@0: if (!jsCachingEnabled || !jsCacheAsmJSPath) michael@0: return false; michael@0: michael@0: ScopedFileDesc fd(open(jsCacheAsmJSPath, O_RDWR), ScopedFileDesc::READ_LOCK); michael@0: if (fd == -1) michael@0: return false; michael@0: michael@0: // Get the size and make sure we can dereference at least one uint32_t. michael@0: off_t off = lseek(fd, 0, SEEK_END); michael@0: if (off == -1 || off < (off_t)sizeof(uint32_t)) michael@0: return false; michael@0: michael@0: // Map the file into memory. michael@0: void *memory; michael@0: #ifdef XP_WIN michael@0: HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd); michael@0: HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr); michael@0: if (!fileMapping) michael@0: return false; michael@0: michael@0: memory = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); michael@0: CloseHandle(fileMapping); michael@0: if (!memory) michael@0: return false; michael@0: #else michael@0: memory = mmap(nullptr, off, PROT_READ, MAP_SHARED, fd, 0); michael@0: if (memory == MAP_FAILED) michael@0: return false; michael@0: #endif michael@0: michael@0: // Perform check described by asmJSCacheCookie comment. michael@0: if (*(uint32_t *)memory != asmJSCacheCookie) { michael@0: #ifdef XP_WIN michael@0: UnmapViewOfFile(memory); michael@0: #else michael@0: munmap(memory, off); michael@0: #endif michael@0: return false; michael@0: } michael@0: michael@0: // The embedding added the cookie so strip it off of the buffer returned to michael@0: // the JS engine. michael@0: *serializedSizeOut = off - sizeof(uint32_t); michael@0: *memoryOut = (uint8_t *)memory + sizeof(uint32_t); michael@0: *handleOut = fd.forget(); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: ShellCloseAsmJSCacheEntryForRead(HandleObject global, size_t serializedSize, const uint8_t *memory, michael@0: intptr_t handle) michael@0: { michael@0: // Undo the cookie adjustment done when opening the file. michael@0: memory -= sizeof(uint32_t); michael@0: serializedSize += sizeof(uint32_t); michael@0: michael@0: // Release the memory mapping and file. michael@0: #ifdef XP_WIN michael@0: UnmapViewOfFile(const_cast(memory)); michael@0: #else michael@0: munmap(const_cast(memory), serializedSize); michael@0: #endif michael@0: michael@0: JS_ASSERT(jsCacheOpened == true); michael@0: jsCacheOpened = false; michael@0: close(handle); michael@0: } michael@0: michael@0: static bool michael@0: ShellOpenAsmJSCacheEntryForWrite(HandleObject global, bool installed, michael@0: const jschar *begin, const jschar *end, michael@0: size_t serializedSize, uint8_t **memoryOut, intptr_t *handleOut) michael@0: { michael@0: if (!jsCachingEnabled || !jsCacheAsmJSPath) michael@0: return false; michael@0: michael@0: // Create the cache directory if it doesn't already exist. michael@0: struct stat dirStat; michael@0: if (stat(jsCacheDir, &dirStat) == 0) { michael@0: if (!(dirStat.st_mode & S_IFDIR)) michael@0: return false; michael@0: } else { michael@0: #ifdef XP_WIN michael@0: if (mkdir(jsCacheDir) != 0) michael@0: return false; michael@0: #else michael@0: if (mkdir(jsCacheDir, 0777) != 0) michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: ScopedFileDesc fd(open(jsCacheAsmJSPath, O_CREAT|O_RDWR, 0660), ScopedFileDesc::WRITE_LOCK); michael@0: if (fd == -1) michael@0: return false; michael@0: michael@0: // Include extra space for the asmJSCacheCookie. michael@0: serializedSize += sizeof(uint32_t); michael@0: michael@0: // Resize the file to the appropriate size after zeroing their contents. michael@0: #ifdef XP_WIN michael@0: if (chsize(fd, 0)) michael@0: return false; michael@0: if (chsize(fd, serializedSize)) michael@0: return false; michael@0: #else michael@0: if (ftruncate(fd, 0)) michael@0: return false; michael@0: if (ftruncate(fd, serializedSize)) michael@0: return false; michael@0: #endif michael@0: michael@0: // Map the file into memory. michael@0: void *memory; michael@0: #ifdef XP_WIN michael@0: HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd); michael@0: HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr); michael@0: if (!fileMapping) michael@0: return false; michael@0: michael@0: memory = MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0); michael@0: CloseHandle(fileMapping); michael@0: if (!memory) michael@0: return false; michael@0: #else michael@0: memory = mmap(nullptr, serializedSize, PROT_WRITE, MAP_SHARED, fd, 0); michael@0: if (memory == MAP_FAILED) michael@0: return false; michael@0: #endif michael@0: michael@0: // The embedding added the cookie so strip it off of the buffer returned to michael@0: // the JS engine. The asmJSCacheCookie will be written on close, below. michael@0: JS_ASSERT(*(uint32_t *)memory == 0); michael@0: *memoryOut = (uint8_t *)memory + sizeof(uint32_t); michael@0: *handleOut = fd.forget(); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: ShellCloseAsmJSCacheEntryForWrite(HandleObject global, size_t serializedSize, uint8_t *memory, michael@0: intptr_t handle) michael@0: { michael@0: // Undo the cookie adjustment done when opening the file. michael@0: memory -= sizeof(uint32_t); michael@0: serializedSize += sizeof(uint32_t); michael@0: michael@0: // Write the magic cookie value after flushing the entire cache entry. michael@0: #ifdef XP_WIN michael@0: FlushViewOfFile(memory, serializedSize); michael@0: FlushFileBuffers(HANDLE(_get_osfhandle(handle))); michael@0: #else michael@0: msync(memory, serializedSize, MS_SYNC); michael@0: #endif michael@0: michael@0: JS_ASSERT(*(uint32_t *)memory == 0); michael@0: *(uint32_t *)memory = asmJSCacheCookie; michael@0: michael@0: // Free the memory mapping and file. michael@0: #ifdef XP_WIN michael@0: UnmapViewOfFile(const_cast(memory)); michael@0: #else michael@0: munmap(memory, serializedSize); michael@0: #endif michael@0: michael@0: JS_ASSERT(jsCacheOpened == true); michael@0: jsCacheOpened = false; michael@0: close(handle); michael@0: } michael@0: michael@0: static bool michael@0: ShellBuildId(JS::BuildIdCharVector *buildId) michael@0: { michael@0: // The browser embeds the date into the buildid and the buildid is embedded michael@0: // in the binary, so every 'make' necessarily builds a new firefox binary. michael@0: // Fortunately, the actual firefox executable is tiny -- all the code is in michael@0: // libxul.so and other shared modules -- so this isn't a big deal. Not so michael@0: // for the statically-linked JS shell. To avoid recompmiling js.cpp and michael@0: // re-linking 'js' on every 'make', we use a constant buildid and rely on michael@0: // the shell user to manually clear the cache (deleting the dir passed to michael@0: // --js-cache) between cache-breaking updates. Note: jit_tests.py does this michael@0: // on every run). michael@0: const char buildid[] = "JS-shell"; michael@0: return buildId->append(buildid, sizeof(buildid)); michael@0: } michael@0: michael@0: static JS::AsmJSCacheOps asmJSCacheOps = { michael@0: ShellOpenAsmJSCacheEntryForRead, michael@0: ShellCloseAsmJSCacheEntryForRead, michael@0: ShellOpenAsmJSCacheEntryForWrite, michael@0: ShellCloseAsmJSCacheEntryForWrite, michael@0: ShellBuildId michael@0: }; michael@0: michael@0: /* michael@0: * Avoid a reentrancy hazard. michael@0: * michael@0: * The non-JS_THREADSAFE shell uses a signal handler to implement timeout(). michael@0: * The JS engine is not really reentrant, but JS_RequestInterruptCallback michael@0: * is mostly safe--the only danger is that we might interrupt JS_NewContext or michael@0: * JS_DestroyContext while the context list is being modified. Therefore we michael@0: * disable the signal handler around calls to those functions. michael@0: */ michael@0: #ifdef JS_THREADSAFE michael@0: # define WITH_SIGNALS_DISABLED(x) x michael@0: #else michael@0: # define WITH_SIGNALS_DISABLED(x) \ michael@0: JS_BEGIN_MACRO \ michael@0: ScheduleWatchdog(gRuntime, -1); \ michael@0: x; \ michael@0: ScheduleWatchdog(gRuntime, gTimeoutInterval); \ michael@0: JS_END_MACRO michael@0: #endif michael@0: michael@0: static JSContext * michael@0: NewContext(JSRuntime *rt) michael@0: { michael@0: JSContext *cx; michael@0: WITH_SIGNALS_DISABLED(cx = JS_NewContext(rt, gStackChunkSize)); michael@0: if (!cx) michael@0: return nullptr; michael@0: michael@0: JSShellContextData *data = NewContextData(); michael@0: if (!data) { michael@0: DestroyContext(cx, false); michael@0: return nullptr; michael@0: } michael@0: michael@0: JS_SetContextPrivate(cx, data); michael@0: JS_SetErrorReporter(cx, my_ErrorReporter); michael@0: return cx; michael@0: } michael@0: michael@0: static void michael@0: DestroyContext(JSContext *cx, bool withGC) michael@0: { michael@0: JSShellContextData *data = GetContextData(cx); michael@0: JS_SetContextPrivate(cx, nullptr); michael@0: free(data); michael@0: WITH_SIGNALS_DISABLED(withGC ? JS_DestroyContext(cx) : JS_DestroyContextNoGC(cx)); michael@0: } michael@0: michael@0: static JSObject * michael@0: NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options, michael@0: JSPrincipals *principals) michael@0: { michael@0: RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, principals, michael@0: JS::DontFireOnNewGlobalHook, options)); michael@0: if (!glob) michael@0: return nullptr; michael@0: michael@0: { michael@0: JSAutoCompartment ac(cx, glob); michael@0: michael@0: #ifndef LAZY_STANDARD_CLASSES michael@0: if (!JS_InitStandardClasses(cx, glob)) michael@0: return nullptr; michael@0: #endif michael@0: michael@0: #ifdef JS_HAS_CTYPES michael@0: if (!JS_InitCTypesClass(cx, glob)) michael@0: return nullptr; michael@0: #endif michael@0: if (!JS_InitReflect(cx, glob)) michael@0: return nullptr; michael@0: if (!JS_DefineDebuggerObject(cx, glob)) michael@0: return nullptr; michael@0: if (!JS::RegisterPerfMeasurement(cx, glob)) michael@0: return nullptr; michael@0: if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) || michael@0: !JS_DefineProfilingFunctions(cx, glob)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe)) michael@0: return nullptr; michael@0: michael@0: if (!fuzzingSafe && !JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions)) michael@0: return nullptr; michael@0: michael@0: /* Initialize FakeDOMObject. */ michael@0: static const js::DOMCallbacks DOMcallbacks = { michael@0: InstanceClassHasProtoAtDepth michael@0: }; michael@0: SetDOMCallbacks(cx->runtime(), &DOMcallbacks); michael@0: michael@0: RootedObject domProto(cx, JS_InitClass(cx, glob, js::NullPtr(), &dom_class, dom_constructor, michael@0: 0, dom_props, dom_methods, nullptr, nullptr)); michael@0: if (!domProto) michael@0: return nullptr; michael@0: michael@0: /* Initialize FakeDOMObject.prototype */ michael@0: InitDOMObject(domProto); michael@0: } michael@0: michael@0: JS_FireOnNewGlobalObject(cx, glob); michael@0: michael@0: return glob; michael@0: } michael@0: michael@0: static bool michael@0: BindScriptArgs(JSContext *cx, JSObject *obj_, OptionParser *op) michael@0: { michael@0: RootedObject obj(cx, obj_); michael@0: michael@0: MultiStringRange msr = op->getMultiStringArg("scriptArgs"); michael@0: RootedObject scriptArgs(cx); michael@0: scriptArgs = JS_NewArrayObject(cx, 0); michael@0: if (!scriptArgs) michael@0: return false; michael@0: michael@0: if (!JS_DefineProperty(cx, obj, "scriptArgs", scriptArgs, 0)) michael@0: return false; michael@0: michael@0: for (size_t i = 0; !msr.empty(); msr.popFront(), ++i) { michael@0: const char *scriptArg = msr.front(); michael@0: JSString *str = JS_NewStringCopyZ(cx, scriptArg); michael@0: if (!str || michael@0: !JS_DefineElement(cx, scriptArgs, i, STRING_TO_JSVAL(str), nullptr, nullptr, michael@0: JSPROP_ENUMERATE)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // This function is currently only called from "#if defined(JS_ION)" chunks, michael@0: // so we're guarding the function definition with an #ifdef, too, to avoid michael@0: // build warning for unused function in non-ion-enabled builds: michael@0: #if defined(JS_ION) michael@0: static bool michael@0: OptionFailure(const char *option, const char *str) michael@0: { michael@0: fprintf(stderr, "Unrecognized option for %s: %s\n", option, str); michael@0: return false; michael@0: } michael@0: #endif /* JS_ION */ michael@0: michael@0: static int michael@0: ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op) michael@0: { michael@0: RootedObject obj(cx, obj_); michael@0: michael@0: if (op->getBoolOption('s')) michael@0: JS::ContextOptionsRef(cx).toggleExtraWarnings(); michael@0: michael@0: if (op->getBoolOption('d')) { michael@0: JS_SetRuntimeDebugMode(JS_GetRuntime(cx), true); michael@0: JS_SetDebugMode(cx, true); michael@0: } michael@0: michael@0: /* |scriptArgs| gets bound on the global before any code is run. */ michael@0: if (!BindScriptArgs(cx, obj, op)) michael@0: return EXIT_FAILURE; michael@0: michael@0: MultiStringRange filePaths = op->getMultiStringOption('f'); michael@0: MultiStringRange codeChunks = op->getMultiStringOption('e'); michael@0: michael@0: if (filePaths.empty() && codeChunks.empty() && !op->getStringArg("script")) { michael@0: Process(cx, obj, nullptr, true); /* Interactive. */ michael@0: return gExitCode; michael@0: } michael@0: michael@0: while (!filePaths.empty() || !codeChunks.empty()) { michael@0: size_t fpArgno = filePaths.empty() ? -1 : filePaths.argno(); michael@0: size_t ccArgno = codeChunks.empty() ? -1 : codeChunks.argno(); michael@0: if (fpArgno < ccArgno) { michael@0: char *path = filePaths.front(); michael@0: Process(cx, obj, path, false); michael@0: if (gExitCode) michael@0: return gExitCode; michael@0: filePaths.popFront(); michael@0: } else { michael@0: const char *code = codeChunks.front(); michael@0: RootedValue rval(cx); michael@0: if (!JS_EvaluateScript(cx, obj, code, strlen(code), "-e", 1, &rval)) michael@0: return gExitCode ? gExitCode : EXITCODE_RUNTIME_ERROR; michael@0: codeChunks.popFront(); michael@0: } michael@0: } michael@0: michael@0: /* The |script| argument is processed after all options. */ michael@0: if (const char *path = op->getStringArg("script")) { michael@0: Process(cx, obj, path, false); michael@0: if (gExitCode) michael@0: return gExitCode; michael@0: } michael@0: michael@0: if (op->getBoolOption('i')) michael@0: Process(cx, obj, nullptr, true); michael@0: michael@0: return gExitCode ? gExitCode : EXIT_SUCCESS; michael@0: } michael@0: michael@0: static bool michael@0: SetRuntimeOptions(JSRuntime *rt, const OptionParser &op) michael@0: { michael@0: #if defined(JS_ION) michael@0: bool enableBaseline = !op.getBoolOption("no-baseline"); michael@0: bool enableIon = !op.getBoolOption("no-ion"); michael@0: bool enableAsmJS = !op.getBoolOption("no-asmjs"); michael@0: michael@0: JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline) michael@0: .setIon(enableIon) michael@0: .setAsmJS(enableAsmJS); michael@0: michael@0: if (const char *str = op.getStringOption("ion-gvn")) { michael@0: if (strcmp(str, "off") == 0) { michael@0: jit::js_JitOptions.disableGvn = true; michael@0: } else if (strcmp(str, "pessimistic") == 0) { michael@0: jit::js_JitOptions.forceGvnKind = true; michael@0: jit::js_JitOptions.forcedGvnKind = jit::GVN_Pessimistic; michael@0: } else if (strcmp(str, "optimistic") == 0) { michael@0: jit::js_JitOptions.forceGvnKind = true; michael@0: jit::js_JitOptions.forcedGvnKind = jit::GVN_Optimistic; michael@0: } else { michael@0: return OptionFailure("ion-gvn", str); michael@0: } michael@0: } michael@0: michael@0: if (const char *str = op.getStringOption("ion-licm")) { michael@0: if (strcmp(str, "on") == 0) michael@0: jit::js_JitOptions.disableLicm = false; michael@0: else if (strcmp(str, "off") == 0) michael@0: jit::js_JitOptions.disableLicm = true; michael@0: else michael@0: return OptionFailure("ion-licm", str); michael@0: } michael@0: michael@0: if (const char *str = op.getStringOption("ion-edgecase-analysis")) { michael@0: if (strcmp(str, "on") == 0) michael@0: jit::js_JitOptions.disableEdgeCaseAnalysis = false; michael@0: else if (strcmp(str, "off") == 0) michael@0: jit::js_JitOptions.disableEdgeCaseAnalysis = true; michael@0: else michael@0: return OptionFailure("ion-edgecase-analysis", str); michael@0: } michael@0: michael@0: if (const char *str = op.getStringOption("ion-range-analysis")) { michael@0: if (strcmp(str, "on") == 0) michael@0: jit::js_JitOptions.disableRangeAnalysis = false; michael@0: else if (strcmp(str, "off") == 0) michael@0: jit::js_JitOptions.disableRangeAnalysis = true; michael@0: else michael@0: return OptionFailure("ion-range-analysis", str); michael@0: } michael@0: michael@0: if (op.getBoolOption("ion-check-range-analysis")) michael@0: jit::js_JitOptions.checkRangeAnalysis = true; michael@0: michael@0: if (const char *str = op.getStringOption("ion-inlining")) { michael@0: if (strcmp(str, "on") == 0) michael@0: jit::js_JitOptions.disableInlining = false; michael@0: else if (strcmp(str, "off") == 0) michael@0: jit::js_JitOptions.disableInlining = true; michael@0: else michael@0: return OptionFailure("ion-inlining", str); michael@0: } michael@0: michael@0: if (const char *str = op.getStringOption("ion-osr")) { michael@0: if (strcmp(str, "on") == 0) michael@0: jit::js_JitOptions.osr = true; michael@0: else if (strcmp(str, "off") == 0) michael@0: jit::js_JitOptions.osr = false; michael@0: else michael@0: return OptionFailure("ion-osr", str); michael@0: } michael@0: michael@0: if (const char *str = op.getStringOption("ion-limit-script-size")) { michael@0: if (strcmp(str, "on") == 0) michael@0: jit::js_JitOptions.limitScriptSize = true; michael@0: else if (strcmp(str, "off") == 0) michael@0: jit::js_JitOptions.limitScriptSize = false; michael@0: else michael@0: return OptionFailure("ion-limit-script-size", str); michael@0: } michael@0: michael@0: int32_t useCount = op.getIntOption("ion-uses-before-compile"); michael@0: if (useCount >= 0) michael@0: jit::js_JitOptions.setUsesBeforeCompile(useCount); michael@0: michael@0: useCount = op.getIntOption("baseline-uses-before-compile"); michael@0: if (useCount >= 0) michael@0: jit::js_JitOptions.baselineUsesBeforeCompile = useCount; michael@0: michael@0: if (op.getBoolOption("baseline-eager")) michael@0: jit::js_JitOptions.baselineUsesBeforeCompile = 0; michael@0: michael@0: if (const char *str = op.getStringOption("ion-regalloc")) { michael@0: if (strcmp(str, "lsra") == 0) { michael@0: jit::js_JitOptions.forceRegisterAllocator = true; michael@0: jit::js_JitOptions.forcedRegisterAllocator = jit::RegisterAllocator_LSRA; michael@0: } else if (strcmp(str, "backtracking") == 0) { michael@0: jit::js_JitOptions.forceRegisterAllocator = true; michael@0: jit::js_JitOptions.forcedRegisterAllocator = jit::RegisterAllocator_Backtracking; michael@0: } else if (strcmp(str, "stupid") == 0) { michael@0: jit::js_JitOptions.forceRegisterAllocator = true; michael@0: jit::js_JitOptions.forcedRegisterAllocator = jit::RegisterAllocator_Stupid; michael@0: } else { michael@0: return OptionFailure("ion-regalloc", str); michael@0: } michael@0: } michael@0: michael@0: if (op.getBoolOption("ion-eager")) michael@0: jit::js_JitOptions.setEagerCompilation(); michael@0: michael@0: if (op.getBoolOption("ion-compile-try-catch")) michael@0: jit::js_JitOptions.compileTryCatch = true; michael@0: michael@0: bool parallelCompilation = true; michael@0: if (const char *str = op.getStringOption("ion-parallel-compile")) { michael@0: if (strcmp(str, "off") == 0) michael@0: parallelCompilation = false; michael@0: else if (strcmp(str, "on") != 0) michael@0: return OptionFailure("ion-parallel-compile", str); michael@0: } michael@0: #ifdef JS_THREADSAFE michael@0: rt->setParallelIonCompilationEnabled(parallelCompilation); michael@0: #endif michael@0: michael@0: #endif // JS_ION michael@0: michael@0: #ifdef JS_ARM_SIMULATOR michael@0: if (op.getBoolOption("arm-sim-icache-checks")) michael@0: jit::Simulator::ICacheCheckingEnabled = true; michael@0: michael@0: int32_t stopAt = op.getIntOption("arm-sim-stop-at"); michael@0: if (stopAt >= 0) michael@0: jit::Simulator::StopSimAt = stopAt; michael@0: #endif michael@0: michael@0: reportWarnings = op.getBoolOption('w'); michael@0: compileOnly = op.getBoolOption('c'); michael@0: printTiming = op.getBoolOption('b'); michael@0: rt->profilingScripts = enableDisassemblyDumps = op.getBoolOption('D'); michael@0: michael@0: jsCacheDir = op.getStringOption("js-cache"); michael@0: if (jsCacheDir) { michael@0: if (op.getBoolOption("js-cache-per-process")) michael@0: jsCacheDir = JS_smprintf("%s/%u", jsCacheDir, (unsigned)getpid()); michael@0: jsCacheAsmJSPath = JS_smprintf("%s/asmjs.cache", jsCacheDir); michael@0: } michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: int32_t threadCount = op.getIntOption("thread-count"); michael@0: if (threadCount >= 0) michael@0: SetFakeCPUCount(threadCount); michael@0: #endif /* JS_THREADSAFE */ michael@0: michael@0: #ifdef DEBUG michael@0: dumpEntrainedVariables = op.getBoolOption("dump-entrained-variables"); michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static int michael@0: Shell(JSContext *cx, OptionParser *op, char **envp) michael@0: { michael@0: JSAutoRequest ar(cx); michael@0: michael@0: if (op->getBoolOption("fuzzing-safe")) michael@0: fuzzingSafe = true; michael@0: else michael@0: fuzzingSafe = (getenv("MOZ_FUZZING_SAFE") && getenv("MOZ_FUZZING_SAFE")[0] != '0'); michael@0: michael@0: RootedObject glob(cx); michael@0: JS::CompartmentOptions options; michael@0: options.setVersion(JSVERSION_LATEST); michael@0: glob = NewGlobalObject(cx, options, nullptr); michael@0: if (!glob) michael@0: return 1; michael@0: michael@0: JSAutoCompartment ac(cx, glob); michael@0: js::SetDefaultObjectForContext(cx, glob); michael@0: michael@0: JSObject *envobj = JS_DefineObject(cx, glob, "environment", &env_class, nullptr, 0); michael@0: if (!envobj) michael@0: return 1; michael@0: JS_SetPrivate(envobj, envp); michael@0: michael@0: int result = ProcessArgs(cx, glob, op); michael@0: michael@0: if (enableDisassemblyDumps) michael@0: JS_DumpCompartmentPCCounts(cx); michael@0: michael@0: if (op->getBoolOption("js-cache-per-process")) { michael@0: if (jsCacheAsmJSPath) michael@0: unlink(jsCacheAsmJSPath); michael@0: if (jsCacheDir) michael@0: rmdir(jsCacheDir); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: static void michael@0: MaybeOverrideOutFileFromEnv(const char* const envVar, michael@0: FILE* defaultOut, michael@0: FILE** outFile) michael@0: { michael@0: const char* outPath = getenv(envVar); michael@0: if (!outPath || !*outPath || !(*outFile = fopen(outPath, "w"))) { michael@0: *outFile = defaultOut; michael@0: } michael@0: } michael@0: michael@0: /* Pretend we can always preserve wrappers for dummy DOM objects. */ michael@0: static bool michael@0: DummyPreserveWrapperCallback(JSContext *cx, JSObject *obj) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: int michael@0: main(int argc, char **argv, char **envp) michael@0: { michael@0: sArgc = argc; michael@0: sArgv = argv; michael@0: michael@0: JSRuntime *rt; michael@0: JSContext *cx; michael@0: int result; michael@0: #ifdef XP_WIN michael@0: { michael@0: const char *crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG"); michael@0: if (crash_option && strncmp(crash_option, "1", 1)) { michael@0: DWORD oldmode = SetErrorMode(SEM_NOGPFAULTERRORBOX); michael@0: SetErrorMode(oldmode | SEM_NOGPFAULTERRORBOX); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #ifdef HAVE_SETLOCALE michael@0: setlocale(LC_ALL, ""); michael@0: #endif michael@0: michael@0: MaybeOverrideOutFileFromEnv("JS_STDERR", stderr, &gErrFile); michael@0: MaybeOverrideOutFileFromEnv("JS_STDOUT", stdout, &gOutFile); michael@0: michael@0: OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]"); michael@0: michael@0: op.setDescription("The SpiderMonkey shell provides a command line interface to the " michael@0: "JavaScript engine. Code and file options provided via the command line are " michael@0: "run left to right. If provided, the optional script argument is run after " michael@0: "all options have been processed. Just-In-Time compilation modes may be enabled via " michael@0: "command line options."); michael@0: op.setDescriptionWidth(72); michael@0: op.setHelpWidth(80); michael@0: op.setVersion(JS_GetImplementationVersion()); michael@0: michael@0: if (!op.addMultiStringOption('f', "file", "PATH", "File path to run") michael@0: || !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run") michael@0: || !op.addBoolOption('i', "shell", "Enter prompt after running code") michael@0: || !op.addBoolOption('m', "jm", "No-op (still used by fuzzers)") michael@0: || !op.addBoolOption('\0', "no-jm", "No-op (still used by fuzzers)") michael@0: || !op.addBoolOption('c', "compileonly", "Only compile, don't run (syntax checking mode)") michael@0: || !op.addBoolOption('w', "warnings", "Emit warnings") michael@0: || !op.addBoolOption('W', "nowarnings", "Don't emit warnings") michael@0: || !op.addBoolOption('s', "strict", "Check strictness") michael@0: || !op.addBoolOption('d', "debugjit", "Enable runtime debug mode for method JIT code") michael@0: || !op.addBoolOption('a', "always-mjit", "No-op (still used by fuzzers)") michael@0: || !op.addBoolOption('D', "dump-bytecode", "Dump bytecode with exec count for all scripts") michael@0: || !op.addBoolOption('b', "print-timing", "Print sub-ms runtime for each file that's run") michael@0: || !op.addStringOption('\0', "js-cache", "[path]", michael@0: "Enable the JS cache by specifying the path of the directory to use " michael@0: "to hold cache files") michael@0: || !op.addBoolOption('\0', "js-cache-per-process", michael@0: "Generate a separate cache sub-directory for this process inside " michael@0: "the cache directory specified by --js-cache. This cache directory " michael@0: "will be removed when the js shell exits. This is useful for running " michael@0: "tests in parallel.") michael@0: #ifdef DEBUG michael@0: || !op.addBoolOption('O', "print-alloc", "Print the number of allocations at exit") michael@0: #endif michael@0: || !op.addOptionalStringArg("script", "A script to execute (after all options)") michael@0: || !op.addOptionalMultiStringArg("scriptArgs", michael@0: "String arguments to bind as |scriptArgs| in the " michael@0: "shell's global") michael@0: #ifdef JS_THREADSAFE michael@0: || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads " michael@0: "(default: # of cores - 1)", -1) michael@0: #endif michael@0: || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)") michael@0: || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey") michael@0: || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") michael@0: || !op.addStringOption('\0', "ion-gvn", "[mode]", michael@0: "Specify Ion global value numbering:\n" michael@0: " off: disable GVN\n" michael@0: " pessimistic: use pessimistic GVN\n" michael@0: " optimistic: (default) use optimistic GVN") michael@0: || !op.addStringOption('\0', "ion-licm", "on/off", michael@0: "Loop invariant code motion (default: on, off to disable)") michael@0: || !op.addStringOption('\0', "ion-edgecase-analysis", "on/off", michael@0: "Find edge cases where Ion can avoid bailouts (default: on, off to disable)") michael@0: || !op.addStringOption('\0', "ion-range-analysis", "on/off", michael@0: "Range analysis (default: on, off to disable)") michael@0: || !op.addBoolOption('\0', "ion-check-range-analysis", michael@0: "Range analysis checking") michael@0: || !op.addStringOption('\0', "ion-inlining", "on/off", michael@0: "Inline methods where possible (default: on, off to disable)") michael@0: || !op.addStringOption('\0', "ion-osr", "on/off", michael@0: "On-Stack Replacement (default: on, off to disable)") michael@0: || !op.addStringOption('\0', "ion-limit-script-size", "on/off", michael@0: "Don't compile very large scripts (default: on, off to disable)") michael@0: || !op.addIntOption('\0', "ion-uses-before-compile", "COUNT", michael@0: "Wait for COUNT calls or iterations before compiling " michael@0: "(default: 1000)", -1) michael@0: || !op.addStringOption('\0', "ion-regalloc", "[mode]", michael@0: "Specify Ion register allocation:\n" michael@0: " lsra: Linear Scan register allocation (default)\n" michael@0: " backtracking: Priority based backtracking register allocation\n" michael@0: " stupid: Simple block local register allocation") michael@0: || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods (implies --baseline-eager)") michael@0: || !op.addBoolOption('\0', "ion-compile-try-catch", "Ion-compile try-catch statements") michael@0: || !op.addStringOption('\0', "ion-parallel-compile", "on/off", michael@0: "Compile scripts off thread (default: off)") michael@0: || !op.addBoolOption('\0', "baseline", "Enable baseline compiler (default)") michael@0: || !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler") michael@0: || !op.addBoolOption('\0', "baseline-eager", "Always baseline-compile methods") michael@0: || !op.addIntOption('\0', "baseline-uses-before-compile", "COUNT", michael@0: "Wait for COUNT calls or iterations before baseline-compiling " michael@0: "(default: 10)", -1) michael@0: || !op.addBoolOption('\0', "no-fpu", "Pretend CPU does not support floating-point operations " michael@0: "to test JIT codegen (no-op on platforms other than x86).") michael@0: || !op.addBoolOption('\0', "no-sse3", "Pretend CPU does not support SSE3 instructions and above " michael@0: "to test JIT codegen (no-op on platforms other than x86 and x64).") michael@0: || !op.addBoolOption('\0', "no-sse4", "Pretend CPU does not support SSE4 instructions" michael@0: "to test JIT codegen (no-op on platforms other than x86 and x64).") michael@0: || !op.addBoolOption('\0', "fuzzing-safe", "Don't expose functions that aren't safe for " michael@0: "fuzzers to call") michael@0: #ifdef DEBUG michael@0: || !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are " michael@0: "unnecessarily entrained by inner functions") michael@0: #endif michael@0: #ifdef JSGC_GENERATIONAL michael@0: || !op.addBoolOption('\0', "no-ggc", "Disable Generational GC") michael@0: #endif michael@0: || !op.addIntOption('\0', "available-memory", "SIZE", michael@0: "Select GC settings based on available memory (MB)", 0) michael@0: #ifdef JS_ARM_SIMULATOR michael@0: || !op.addBoolOption('\0', "arm-sim-icache-checks", "Enable icache flush checks in the ARM " michael@0: "simulator.") michael@0: || !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER", "Stop the ARM simulator after the given " michael@0: "NUMBER of instructions.", -1) michael@0: #endif michael@0: ) michael@0: { michael@0: return EXIT_FAILURE; michael@0: } michael@0: michael@0: op.setArgTerminatesOptions("script", true); michael@0: op.setArgCapturesRest("scriptArgs"); michael@0: michael@0: switch (op.parseArgs(argc, argv)) { michael@0: case OptionParser::ParseHelp: michael@0: return EXIT_SUCCESS; michael@0: case OptionParser::ParseError: michael@0: op.printHelp(argv[0]); michael@0: return EXIT_FAILURE; michael@0: case OptionParser::Fail: michael@0: return EXIT_FAILURE; michael@0: case OptionParser::Okay: michael@0: break; michael@0: } michael@0: michael@0: if (op.getHelpOption()) michael@0: return EXIT_SUCCESS; michael@0: michael@0: #ifdef DEBUG michael@0: /* michael@0: * Process OOM options as early as possible so that we can observe as many michael@0: * allocations as possible. michael@0: */ michael@0: OOM_printAllocationCount = op.getBoolOption('O'); michael@0: michael@0: #if defined(JS_CODEGEN_X86) && defined(JS_ION) michael@0: if (op.getBoolOption("no-fpu")) michael@0: JSC::MacroAssembler::SetFloatingPointDisabled(); michael@0: #endif michael@0: michael@0: #if (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)) && defined(JS_ION) michael@0: if (op.getBoolOption("no-sse3")) { michael@0: JSC::MacroAssembler::SetSSE3Disabled(); michael@0: PropagateFlagToNestedShells("--no-sse3"); michael@0: } michael@0: if (op.getBoolOption("no-sse4")) { michael@0: JSC::MacroAssembler::SetSSE4Disabled(); michael@0: PropagateFlagToNestedShells("--no-sse4"); michael@0: } michael@0: #endif michael@0: michael@0: #endif // DEBUG michael@0: michael@0: // Start the engine. michael@0: if (!JS_Init()) michael@0: return 1; michael@0: michael@0: /* Use the same parameters as the browser in xpcjsruntime.cpp. */ michael@0: rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS); michael@0: if (!rt) michael@0: return 1; michael@0: michael@0: JS::SetOutOfMemoryCallback(rt, my_OOMCallback); michael@0: if (!SetRuntimeOptions(rt, op)) michael@0: return 1; michael@0: michael@0: gInterruptFunc.construct(rt, NullValue()); michael@0: michael@0: JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff); michael@0: #ifdef JSGC_GENERATIONAL michael@0: Maybe noggc; michael@0: if (op.getBoolOption("no-ggc")) michael@0: noggc.construct(rt); michael@0: #endif michael@0: michael@0: size_t availMem = op.getIntOption("available-memory"); michael@0: if (availMem > 0) michael@0: JS_SetGCParametersBasedOnAvailableMemory(rt, availMem); michael@0: michael@0: JS_SetTrustedPrincipals(rt, &ShellPrincipals::fullyTrusted); michael@0: JS_SetSecurityCallbacks(rt, &ShellPrincipals::securityCallbacks); michael@0: JS_InitDestroyPrincipalsCallback(rt, ShellPrincipals::destroy); michael@0: michael@0: JS_SetInterruptCallback(rt, ShellInterruptCallback); michael@0: JS::SetAsmJSCacheOps(rt, &asmJSCacheOps); michael@0: michael@0: JS_SetNativeStackQuota(rt, gMaxStackSize); michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: if (!offThreadState.init()) michael@0: return 1; michael@0: #endif michael@0: michael@0: if (!InitWatchdog(rt)) michael@0: return 1; michael@0: michael@0: cx = NewContext(rt); michael@0: if (!cx) michael@0: return 1; michael@0: michael@0: JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); michael@0: JS_SetGCParameterForThread(cx, JSGC_MAX_CODE_CACHE_BYTES, 16 * 1024 * 1024); michael@0: michael@0: js::SetPreserveWrapperCallback(rt, DummyPreserveWrapperCallback); michael@0: michael@0: result = Shell(cx, &op, envp); michael@0: michael@0: #ifdef DEBUG michael@0: if (OOM_printAllocationCount) michael@0: printf("OOM max count: %u\n", OOM_counter); michael@0: #endif michael@0: michael@0: DestroyContext(cx, true); michael@0: michael@0: KillWatchdog(); michael@0: michael@0: gInterruptFunc.destroy(); michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: for (size_t i = 0; i < workerThreads.length(); i++) michael@0: PR_JoinThread(workerThreads[i]); michael@0: #endif michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (!noggc.empty()) michael@0: noggc.destroy(); michael@0: #endif michael@0: michael@0: JS_DestroyRuntime(rt); michael@0: JS_ShutDown(); michael@0: return result; michael@0: }