| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* JS shell. */ |
| |
| #include <errno.h> |
| #include <locale.h> |
| #include <math.h> |
| #if !defined(STARBOARD) |
| #include <signal.h> |
| #endif |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/GuardObjects.h" |
| #include "mozilla/Util.h" |
| |
| #include "jstypes.h" |
| #include "jsutil.h" |
| #include "jsprf.h" |
| #include "jsapi.h" |
| #include "jsarray.h" |
| #include "jsatom.h" |
| #include "jscntxt.h" |
| #include "jsdate.h" |
| #include "jsdbgapi.h" |
| #include "jsfun.h" |
| #include "jsgc.h" |
| #include "jsiter.h" |
| #include "jslock.h" |
| #include "jsnum.h" |
| #include "jsobj.h" |
| #include "json.h" |
| #include "jsreflect.h" |
| #include "jsscript.h" |
| #include "jstypedarray.h" |
| #include "jstypedarrayinlines.h" |
| #include "jsworkers.h" |
| #include "jswrapper.h" |
| #include "jsperf.h" |
| |
| #include "builtin/TestingFunctions.h" |
| #include "frontend/BytecodeEmitter.h" |
| #include "frontend/Parser.h" |
| #include "vm/Shape.h" |
| |
| #include "prmjtime.h" |
| |
| #include "jsoptparse.h" |
| #include "jsheaptools.h" |
| |
| #include "jsinferinlines.h" |
| #include "jsscriptinlines.h" |
| #include "jit/Ion.h" |
| |
| #include "vm/Interpreter-inl.h" |
| |
| #ifdef XP_UNIX |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #endif |
| |
| #if defined(XP_WIN) || defined(XP_OS2) |
| #include <io.h> /* for isatty() */ |
| #endif |
| |
| #if defined(STARBOARD) |
| #include "starboard/configuration.h" |
| #define PATH_MAX (SB_FILE_MAX_PATH + 1) |
| #elif defined(XP_WIN) |
| # include <io.h> |
| # include <direct.h> |
| # include "jswin.h" |
| # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR) |
| #else |
| # include <libgen.h> |
| #endif |
| |
| #if JS_TRACE_LOGGING |
| #include "TraceLogging.h" |
| #endif |
| |
| #if defined(DEBUG) && defined(STARBOARD) |
| // On Starboard platforms, DEBUG will get #undef'd when we #include zlib.h |
| #define STARBOARD_DEBUG |
| #endif |
| |
| #ifdef USE_ZLIB |
| #include "zlib.h" |
| #endif |
| |
| #include "starboard/client_porting/wrap_main/wrap_main.h" |
| #include "starboard/memory.h" |
| |
| using namespace js; |
| using namespace js::cli; |
| |
| using mozilla::ArrayLength; |
| using mozilla::Maybe; |
| |
| typedef enum JSShellExitCode { |
| EXITCODE_RUNTIME_ERROR = 3, |
| EXITCODE_FILE_NOT_FOUND = 4, |
| EXITCODE_OUT_OF_MEMORY = 5, |
| EXITCODE_TIMEOUT = 6 |
| } JSShellExitCode; |
| |
| size_t gStackChunkSize = 8192; |
| |
| #if defined(STARBOARD) |
| size_t CalculateStackQuota() { |
| void* stack_high; |
| void* stack_low; |
| SbMemoryGetStackBounds(&stack_high, &stack_low); |
| return 3 * (reinterpret_cast<intptr_t>(stack_high) - reinterpret_cast<intptr_t>(stack_low)) / 4; |
| } |
| #else |
| /* |
| * Note: This limit should match the stack limit set by the browser in |
| * js/xpconnect/src/XPCJSRuntime.cpp |
| */ |
| #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN)) |
| size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; |
| #else |
| size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; |
| #endif |
| #endif |
| |
| #ifdef JS_THREADSAFE |
| #if defined(STARBOARD) |
| static PRTLSIndex gStackBaseThreadIndex; |
| #else |
| static unsigned gStackBaseThreadIndex; |
| #endif // defined(STARBOARD) |
| #else |
| static uintptr_t gStackBase; |
| #endif |
| |
| /* |
| * Limit the timeout to 30 minutes to prevent an overflow on platfoms |
| * that represent the time internally in microseconds using 32-bit int. |
| */ |
| static double MAX_TIMEOUT_INTERVAL = 1800.0; |
| static double gTimeoutInterval = -1.0; |
| static volatile bool gTimedOut = false; |
| static JS::Value gTimeoutFunc; |
| |
| static bool enableTypeInference = true; |
| static bool enableDisassemblyDumps = false; |
| static bool enableIon = true; |
| static bool enableBaseline = true; |
| static bool enableAsmJS = true; |
| |
| static bool printTiming = false; |
| |
| static JSBool |
| SetTimeoutValue(JSContext *cx, double t); |
| |
| static bool |
| InitWatchdog(JSRuntime *rt); |
| |
| static void |
| KillWatchdog(); |
| |
| static bool |
| ScheduleWatchdog(JSRuntime *rt, double t); |
| |
| static void |
| CancelExecution(JSRuntime *rt); |
| |
| /* |
| * Watchdog thread state. |
| */ |
| #ifdef JS_THREADSAFE |
| |
| static PRLock *gWatchdogLock = NULL; |
| static PRCondVar *gWatchdogWakeup = NULL; |
| static PRThread *gWatchdogThread = NULL; |
| static bool gWatchdogHasTimeout = false; |
| static int64_t gWatchdogTimeout = 0; |
| |
| static PRCondVar *gSleepWakeup = NULL; |
| |
| #else |
| |
| static JSRuntime *gRuntime = NULL; |
| |
| #endif |
| |
| int gExitCode = 0; |
| bool gQuitting = false; |
| bool gGotError = false; |
| FILE *gErrFile = NULL; |
| FILE *gOutFile = NULL; |
| |
| static bool reportWarnings = true; |
| static bool compileOnly = false; |
| static bool fuzzingSafe = false; |
| |
| #if defined(DEBUG) || defined(STARBOARD_DEBUG) |
| static bool OOM_printAllocationCount = false; |
| #endif |
| |
| typedef enum JSShellErrNum { |
| #define MSG_DEF(name, number, count, exception, format) \ |
| name = number, |
| #include "jsshell.msg" |
| #undef MSG_DEF |
| JSShellErr_Limit |
| } JSShellErrNum; |
| |
| static JSContext * |
| NewContext(JSRuntime *rt); |
| |
| static void |
| DestroyContext(JSContext *cx, bool withGC); |
| |
| static const JSErrorFormatString * |
| my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber); |
| |
| #ifdef EDITLINE |
| JS_BEGIN_EXTERN_C |
| extern JS_EXPORT_API(char *) readline(const char *prompt); |
| extern JS_EXPORT_API(void) add_history(char *line); |
| JS_END_EXTERN_C |
| #endif |
| |
| static void |
| ReportException(JSContext *cx) |
| { |
| if (JS_IsExceptionPending(cx)) { |
| if (!JS_ReportPendingException(cx)) |
| JS_ClearPendingException(cx); |
| } |
| } |
| |
| class ToStringHelper |
| { |
| public: |
| ToStringHelper(JSContext *aCx, HandleValue v, bool aThrow = false) |
| : cx(aCx), mStr(cx, JS_ValueToString(cx, v)) |
| { |
| if (!aThrow && !mStr) |
| ReportException(cx); |
| } |
| ToStringHelper(JSContext *aCx, HandleId id, bool aThrow = false) |
| : cx(aCx), mStr(cx, JS_ValueToString(cx, IdToValue(id))) |
| { |
| if (!aThrow && !mStr) |
| ReportException(cx); |
| } |
| bool threw() { return !mStr; } |
| jsval getJSVal() { return STRING_TO_JSVAL(mStr); } |
| const char *getBytes() { |
| if (mStr && (mBytes.ptr() || mBytes.encodeLatin1(cx, mStr))) |
| return mBytes.ptr(); |
| return "(error converting value)"; |
| } |
| private: |
| JSContext *cx; |
| RootedString mStr; // Objects of this class are always stack-allocated. |
| JSAutoByteString mBytes; |
| }; |
| |
| static char * |
| GetLine(FILE *file, const char * prompt) |
| { |
| size_t size; |
| char *buffer; |
| #ifdef EDITLINE |
| /* |
| * Use readline only if file is stdin, because there's no way to specify |
| * another handle. Are other filehandles interactive? |
| */ |
| if (file == stdin) { |
| char *linep = readline(prompt); |
| /* |
| * We set it to zero to avoid complaining about inappropriate ioctl |
| * for device in the case of EOF. Looks like errno == 251 if line is |
| * finished with EOF and errno == 25 (EINVAL on Mac) if there is |
| * nothing left to read. |
| */ |
| if (errno == 251 || errno == 25 || errno == EINVAL) |
| errno = 0; |
| if (!linep) |
| return NULL; |
| if (linep[0] != '\0') |
| add_history(linep); |
| return linep; |
| } |
| #endif |
| size_t len = 0; |
| if (*prompt != '\0') { |
| fprintf(gOutFile, "%s", prompt); |
| fflush(gOutFile); |
| } |
| size = 80; |
| buffer = (char *) js_malloc(size); |
| if (!buffer) |
| return NULL; |
| char *current = buffer; |
| while (fgets(current, size - len, file)) { |
| len += strlen(current); |
| char *t = buffer + len - 1; |
| if (*t == '\n') { |
| /* Line was read. We remove '\n' and exit. */ |
| *t = '\0'; |
| return buffer; |
| } |
| if (len + 1 == size) { |
| size = size * 2; |
| char *tmp = (char *) js_realloc(buffer, size); |
| if (!tmp) { |
| js_free(buffer); |
| return NULL; |
| } |
| buffer = tmp; |
| } |
| current = buffer + len; |
| } |
| if (len && !ferror(file)) |
| return buffer; |
| js_free(buffer); |
| return NULL; |
| } |
| |
| static char * |
| JSStringToUTF8(JSContext *cx, JSString *str) |
| { |
| JSLinearString *linear = str->ensureLinear(cx); |
| if (!linear) |
| return NULL; |
| |
| return TwoByteCharsToNewUTF8CharsZ(cx, linear->range()).c_str(); |
| } |
| |
| /* |
| * State to store as JSContext private. |
| * |
| * We declare such timestamp as volatile as they are updated in the operation |
| * callback without taking any locks. Any possible race can only lead to more |
| * frequent callback calls. This is safe as the callback does everything based |
| * on timing. |
| */ |
| struct JSShellContextData { |
| volatile int64_t startTime; |
| }; |
| |
| static JSShellContextData * |
| NewContextData() |
| { |
| /* Prevent creation of new contexts after we have been canceled. */ |
| if (gTimedOut) |
| return NULL; |
| |
| JSShellContextData *data = (JSShellContextData *) |
| js_calloc(sizeof(JSShellContextData), 1); |
| if (!data) |
| return NULL; |
| data->startTime = PRMJ_Now(); |
| return data; |
| } |
| |
| static inline JSShellContextData * |
| GetContextData(JSContext *cx) |
| { |
| JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx); |
| |
| JS_ASSERT(data); |
| return data; |
| } |
| |
| static JSBool |
| ShellOperationCallback(JSContext *cx) |
| { |
| if (!gTimedOut) |
| return true; |
| |
| JS_ClearPendingException(cx); |
| |
| bool result; |
| if (!gTimeoutFunc.isNull()) { |
| RootedValue returnedValue(cx); |
| if (!JS_CallFunctionValue(cx, NULL, gTimeoutFunc, 0, NULL, returnedValue.address())) |
| return false; |
| if (returnedValue.isBoolean()) |
| result = returnedValue.toBoolean(); |
| else |
| result = false; |
| } else { |
| result = false; |
| } |
| |
| if (!result && gExitCode == 0) |
| gExitCode = EXITCODE_TIMEOUT; |
| |
| return result; |
| } |
| |
| static void |
| SetContextOptions(JSContext *cx) |
| { |
| JS_SetOperationCallback(cx, ShellOperationCallback); |
| } |
| |
| /* |
| * Some UTF-8 files, notably those written using Notepad, have a Unicode |
| * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order |
| * is meaningless for UTF-8) but causes a syntax error unless we skip it. |
| */ |
| static void |
| SkipUTF8BOM(FILE* file) |
| { |
| int ch1 = fgetc(file); |
| int ch2 = fgetc(file); |
| int ch3 = fgetc(file); |
| |
| // Skip the BOM |
| if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) |
| return; |
| |
| // No BOM - revert |
| if (ch3 != EOF) |
| ungetc(ch3, file); |
| if (ch2 != EOF) |
| ungetc(ch2, file); |
| if (ch1 != EOF) |
| ungetc(ch1, file); |
| } |
| |
| static void |
| #if defined(STARBOARD) |
| RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, SbFile file, |
| bool compileOnly) |
| { |
| // We may not handle lines starting with # correctly without |
| // adding more here. |
| #else |
| RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, FILE *file, bool compileOnly) |
| { |
| SkipUTF8BOM(file); |
| |
| // To support the UNIX #! shell hack, gobble the first line if it starts |
| // with '#'. |
| int ch = fgetc(file); |
| if (ch == '#') { |
| while ((ch = fgetc(file)) != EOF) { |
| if (ch == '\n' || ch == '\r') |
| break; |
| } |
| } |
| ungetc(ch, file); |
| #endif |
| |
| int64_t t1 = PRMJ_Now(); |
| uint32_t oldopts = JS_GetOptions(cx); |
| gGotError = false; |
| JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); |
| CompileOptions options(cx); |
| options.setUTF8(true) |
| .setFileAndLine(filename, 1); |
| |
| RootedScript script(cx); |
| script = JS::Compile(cx, obj, options, file); |
| JS_SetOptions(cx, oldopts); |
| JS_ASSERT_IF(!script, gGotError); |
| if (script && !compileOnly) { |
| if (!JS_ExecuteScript(cx, obj, script, NULL)) { |
| if (!gQuitting && !gTimedOut) |
| gExitCode = EXITCODE_RUNTIME_ERROR; |
| } |
| int64_t t2 = PRMJ_Now() - t1; |
| if (printTiming) |
| printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC); |
| } |
| } |
| |
| static bool |
| EvalAndPrint(JSContext *cx, Handle<JSObject*> global, const char *bytes, size_t length, |
| int lineno, bool compileOnly, FILE *out) |
| { |
| // Eval. |
| JS::CompileOptions options(cx); |
| options.utf8 = true; |
| options.compileAndGo = true; |
| options.filename = "typein"; |
| options.lineno = lineno; |
| RootedScript script(cx); |
| script = JS::Compile(cx, global, options, bytes, length); |
| if (!script) |
| return false; |
| if (compileOnly) |
| return true; |
| RootedValue result(cx); |
| if (!JS_ExecuteScript(cx, global, script, result.address())) |
| return false; |
| |
| if (!result.isUndefined()) { |
| // Print. |
| RootedString str(cx); |
| str = JS_ValueToSource(cx, result); |
| if (!str) |
| return false; |
| |
| char *utf8chars = JSStringToUTF8(cx, str); |
| if (!utf8chars) |
| return false; |
| fprintf(out, "%s\n", utf8chars); |
| JS_free(cx, utf8chars); |
| } |
| return true; |
| } |
| |
| static void |
| ReadEvalPrintLoop(JSContext *cx, Handle<JSObject*> global, FILE *in, FILE *out, bool compileOnly) |
| { |
| int lineno = 1; |
| bool hitEOF = false; |
| |
| do { |
| /* |
| * Accumulate lines until we get a 'compilable unit' - one that either |
| * generates an error (before running out of source) or that compiles |
| * cleanly. This should be whenever we get a complete statement that |
| * coincides with the end of a line. |
| */ |
| int startline = lineno; |
| typedef Vector<char, 32, ContextAllocPolicy> CharBuffer; |
| CharBuffer buffer(cx); |
| do { |
| ScheduleWatchdog(cx->runtime(), -1); |
| gTimedOut = false; |
| errno = 0; |
| |
| char *line = GetLine(in, startline == lineno ? "js> " : ""); |
| if (!line) { |
| if (errno) { |
| JS_ReportError(cx, strerror(errno)); |
| return; |
| } |
| hitEOF = true; |
| break; |
| } |
| |
| if (!buffer.append(line, strlen(line)) || !buffer.append('\n')) |
| return; |
| |
| lineno++; |
| if (!ScheduleWatchdog(cx->runtime(), gTimeoutInterval)) { |
| hitEOF = true; |
| break; |
| } |
| } while (!JS_BufferIsCompilableUnit(cx, global, buffer.begin(), buffer.length())); |
| |
| if (hitEOF && buffer.empty()) |
| break; |
| |
| if (!EvalAndPrint(cx, global, buffer.begin(), buffer.length(), startline, compileOnly, out)) |
| { |
| // Catch the error, report it, and keep going. |
| JS_ReportPendingException(cx); |
| } |
| } while (!hitEOF && !gQuitting); |
| |
| fprintf(out, "\n"); |
| } |
| |
| class AutoCloseInputFile |
| { |
| private: |
| FILE *f_; |
| public: |
| explicit AutoCloseInputFile(FILE *f) : f_(f) {} |
| ~AutoCloseInputFile() { |
| if (f_ && f_ != stdin) |
| fclose(f_); |
| } |
| }; |
| |
| #if defined(STARBOARD) |
| static void |
| Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY) |
| { |
| // No interactive mode for Starboard : stdin not guaranteed. |
| if (forceTTY) { |
| return; |
| } |
| RootedObject obj(cx, obj_); |
| SbFile file = SbFileOpen(filename, kSbFileOpenOnly | kSbFileRead, NULL, |
| NULL); |
| if (!SbFileIsValid(file)) { |
| SbFileClose(file); |
| return; |
| } |
| |
| SetContextOptions(cx); |
| RunFile(cx, obj, filename, file, compileOnly); |
| SbFileClose(file); |
| } |
| #else |
| static void |
| Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY) |
| { |
| RootedObject obj(cx, obj_); |
| FILE *file; |
| if (forceTTY || !filename || strcmp(filename, "-") == 0) { |
| file = stdin; |
| } else { |
| file = fopen(filename, "r"); |
| if (!file) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_CANT_OPEN, filename, strerror(errno)); |
| gExitCode = EXITCODE_FILE_NOT_FOUND; |
| return; |
| } |
| } |
| AutoCloseInputFile autoClose(file); |
| SetContextOptions(cx); |
| if (!forceTTY && !isatty(fileno(file))) { |
| // It's not interactive - just execute it. |
| RunFile(cx, obj, filename, file, compileOnly); |
| } else { |
| // It's an interactive filehandle; drop into read-eval-print loop. |
| ReadEvalPrintLoop(cx, obj, file, gOutFile, compileOnly); |
| } |
| } |
| #endif // STARBOARD |
| |
| /* |
| * JSContext option name to flag map. The option names are in alphabetical |
| * order for better reporting. |
| */ |
| static const struct JSOption { |
| const char *name; |
| uint32_t flag; |
| } js_options[] = { |
| {"strict", JSOPTION_EXTRA_WARNINGS}, |
| {"typeinfer", JSOPTION_TYPE_INFERENCE}, |
| {"werror", JSOPTION_WERROR}, |
| {"strict_mode", JSOPTION_STRICT_MODE}, |
| }; |
| |
| static uint32_t |
| MapContextOptionNameToFlag(JSContext* cx, const char* name) |
| { |
| for (size_t i = 0; i < ArrayLength(js_options); ++i) { |
| if (strcmp(name, js_options[i].name) == 0) |
| return js_options[i].flag; |
| } |
| |
| char* msg = JS_sprintf_append(NULL, |
| "unknown option name '%s'." |
| " The valid names are ", name); |
| for (size_t i = 0; i < ArrayLength(js_options); ++i) { |
| if (!msg) |
| break; |
| msg = JS_sprintf_append(msg, "%s%s", js_options[i].name, |
| (i + 2 < ArrayLength(js_options) |
| ? ", " |
| : i + 2 == ArrayLength(js_options) |
| ? " and " |
| : ".")); |
| } |
| if (!msg) { |
| JS_ReportOutOfMemory(cx); |
| } else { |
| JS_ReportError(cx, msg); |
| JS_xprintf_free(msg); |
| } |
| return 0; |
| } |
| |
| extern JSClass global_class; |
| |
| static JSBool |
| Version(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JSVersion origVersion = JS_GetVersion(cx); |
| if (args.length() == 0 || JSVAL_IS_VOID(args[0])) { |
| /* Get version. */ |
| args.rval().setInt32(origVersion); |
| } else { |
| /* Set version. */ |
| int32_t v = -1; |
| if (args[0].isInt32()) { |
| v = args[0].toInt32(); |
| } else if (args[0].isDouble()) { |
| double fv = args[0].toDouble(); |
| if (int32_t(fv) == fv) |
| v = int32_t(fv); |
| } |
| if (v < 0 || v > JSVERSION_LATEST) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "version"); |
| return false; |
| } |
| JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v)); |
| args.rval().setInt32(origVersion); |
| } |
| return true; |
| } |
| |
| static JSScript * |
| GetTopScript(JSContext *cx) |
| { |
| RootedScript script(cx); |
| JS_DescribeScriptedCaller(cx, script.address(), NULL); |
| return script; |
| } |
| |
| /* |
| * Resolve a (possibly) relative filename to an absolute path. If |
| * |scriptRelative| is true, then the result will be relative to the directory |
| * containing the currently-running script, or the current working directory if |
| * the currently-running script is "-e" (namely, you're using it from the |
| * command line.) Otherwise, it will be relative to the current working |
| * directory. |
| */ |
| static JSString * |
| ResolvePath(JSContext *cx, HandleString filenameStr, bool scriptRelative) |
| { |
| JSAutoByteString filename(cx, filenameStr); |
| if (!filename) |
| return NULL; |
| |
| const char *pathname = filename.ptr(); |
| if (pathname[0] == '/') |
| return filenameStr; |
| #ifdef XP_WIN |
| // Various forms of absolute paths per http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx |
| // "\..." |
| if (pathname[0] == '\\') |
| return filenameStr; |
| // "C:\..." |
| if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\') |
| return filenameStr; |
| // "\\..." |
| if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\') |
| return filenameStr; |
| #endif |
| |
| /* Get the currently executing script's name. */ |
| RootedScript script(cx, GetTopScript(cx)); |
| if (!script->filename()) |
| return NULL; |
| if (strcmp(script->filename(), "-e") == 0 || strcmp(script->filename(), "typein") == 0) |
| scriptRelative = false; |
| |
| static char buffer[PATH_MAX+1]; |
| if (scriptRelative) { |
| #if defined(STARBOARD) |
| // We do not expect relative path scripts to work in Starboard. |
| return NULL; |
| #elif defined(XP_WIN) |
| // The docs say it can return EINVAL, but the compiler says it's void |
| _splitpath(script->filename(), NULL, buffer, NULL, NULL); |
| #else |
| strncpy(buffer, script->filename(), PATH_MAX+1); |
| if (buffer[PATH_MAX] != '\0') |
| return NULL; |
| // dirname(buffer) might return buffer, or it might return a |
| // statically-allocated string |
| memmove(buffer, dirname(buffer), strlen(buffer) + 1); |
| #endif |
| } else { |
| #if defined(STARBOARD) |
| // For Starboard, the tests scripts are in |
| // content/dir_source_root/mozjs/tests |
| // so we let that play the role of the cwd. |
| bool result = SbSystemGetPath(kSbSystemPathSourceDirectory, |
| buffer, PATH_MAX + 1); |
| if (!result) { |
| return NULL; |
| } |
| // Append subdirectory /mozjs/tests. |
| SbStringConcat(buffer, "/mozjs/tests", PATH_MAX + 1); |
| #else |
| const char *cwd = getcwd(buffer, PATH_MAX); |
| if (!cwd) |
| return NULL; |
| #endif |
| } |
| |
| size_t len = strlen(buffer); |
| buffer[len] = '/'; |
| strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1)); |
| if (buffer[PATH_MAX] != '\0') |
| return NULL; |
| |
| return JS_NewStringCopyZ(cx, buffer); |
| } |
| |
| static JSBool |
| Options(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| uint32_t flag; |
| JSString *str; |
| char *names; |
| bool found; |
| |
| uint32_t optset = 0; |
| for (unsigned i = 0; i < args.length(); i++) { |
| str = JS_ValueToString(cx, args[i]); |
| if (!str) |
| return false; |
| args[i] = STRING_TO_JSVAL(str); |
| JSAutoByteString opt(cx, str); |
| if (!opt) |
| return false; |
| flag = MapContextOptionNameToFlag(cx, opt.ptr()); |
| if (!flag) |
| return false; |
| optset |= flag; |
| } |
| optset = JS_ToggleOptions(cx, optset); |
| |
| names = NULL; |
| found = false; |
| for (size_t i = 0; i < ArrayLength(js_options); i++) { |
| if (js_options[i].flag & optset) { |
| found = true; |
| names = JS_sprintf_append(names, "%s%s", |
| names ? "," : "", js_options[i].name); |
| if (!names) |
| break; |
| } |
| } |
| if (!found) |
| names = js_strdup((char*)""); |
| if (!names) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| str = JS_NewStringCopyZ(cx, names); |
| JS_xprintf_free(names); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static JSBool |
| LoadScript(JSContext *cx, unsigned argc, jsval *vp, bool scriptRelative) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!thisobj) |
| return false; |
| |
| RootedString str(cx); |
| for (unsigned i = 0; i < args.length(); i++) { |
| str = JS_ValueToString(cx, args[i]); |
| if (!str) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "load"); |
| return false; |
| } |
| str = ResolvePath(cx, str, scriptRelative); |
| if (!str) { |
| JS_ReportError(cx, "unable to resolve path"); |
| return false; |
| } |
| JSAutoByteString filename(cx, str); |
| if (!filename) |
| return false; |
| errno = 0; |
| CompileOptions opts(cx); |
| opts.setUTF8(true).setCompileAndGo(true).setNoScriptRval(true); |
| if ((compileOnly && !Compile(cx, thisobj, opts, filename.ptr())) || |
| !Evaluate(cx, thisobj, opts, filename.ptr(), NULL)) |
| { |
| return false; |
| } |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| Load(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| return LoadScript(cx, argc, vp, false); |
| } |
| |
| static JSBool |
| LoadScriptRelativeToScript(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| return LoadScript(cx, argc, vp, true); |
| } |
| |
| class AutoNewContext |
| { |
| private: |
| JSContext *oldcx; |
| JSContext *newcx; |
| Maybe<JSAutoRequest> newRequest; |
| Maybe<AutoCompartment> newCompartment; |
| |
| AutoNewContext(const AutoNewContext &) MOZ_DELETE; |
| |
| public: |
| AutoNewContext() : oldcx(NULL), newcx(NULL) {} |
| |
| bool enter(JSContext *cx) { |
| JS_ASSERT(!JS_IsExceptionPending(cx)); |
| oldcx = cx; |
| newcx = NewContext(JS_GetRuntime(cx)); |
| if (!newcx) |
| return false; |
| JS_SetOptions(newcx, JS_GetOptions(newcx) | JSOPTION_DONT_REPORT_UNCAUGHT); |
| JS_SetGlobalObject(newcx, JS_GetGlobalForScopeChain(cx)); |
| |
| newRequest.construct(newcx); |
| newCompartment.construct(newcx, JS_GetGlobalForScopeChain(cx)); |
| return true; |
| } |
| |
| JSContext *get() { return newcx; } |
| |
| ~AutoNewContext() { |
| if (newcx) { |
| RootedValue exc(oldcx); |
| bool throwing = JS_IsExceptionPending(newcx); |
| if (throwing) |
| JS_GetPendingException(newcx, exc.address()); |
| newCompartment.destroy(); |
| newRequest.destroy(); |
| if (throwing) |
| JS_SetPendingException(oldcx, exc); |
| DestroyContext(newcx, false); |
| } |
| } |
| }; |
| |
| class AutoSaveFrameChain |
| { |
| JSContext *cx_; |
| bool saved_; |
| |
| public: |
| AutoSaveFrameChain(JSContext *cx) |
| : cx_(cx), |
| saved_(false) |
| {} |
| |
| bool save() { |
| if (!JS_SaveFrameChain(cx_)) |
| return false; |
| saved_ = true; |
| return true; |
| } |
| |
| ~AutoSaveFrameChain() { |
| if (saved_) |
| JS_RestoreFrameChain(cx_); |
| } |
| }; |
| |
| static JSBool |
| Evaluate(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() < 1 || args.length() > 2) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
| "evaluate"); |
| return false; |
| } |
| if (!args[0].isString() || (args.length() == 2 && args[1].isPrimitive())) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "evaluate"); |
| return false; |
| } |
| |
| bool newContext = false; |
| bool compileAndGo = true; |
| bool noScriptRval = false; |
| const char *fileName = "@evaluate"; |
| RootedObject element(cx); |
| JSAutoByteString fileNameBytes; |
| RootedString sourceMapURL(cx); |
| unsigned lineNumber = 1; |
| RootedObject global(cx, NULL); |
| bool catchTermination = false; |
| bool saveFrameChain = false; |
| RootedObject callerGlobal(cx, cx->global()); |
| |
| global = JS_GetGlobalForObject(cx, &args.callee()); |
| if (!global) |
| return false; |
| |
| if (args.length() == 2) { |
| RootedObject opts(cx, &args[1].toObject()); |
| RootedValue v(cx); |
| |
| if (!JS_GetProperty(cx, opts, "newContext", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| JSBool b; |
| if (!JS_ValueToBoolean(cx, v, &b)) |
| return false; |
| newContext = b; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "compileAndGo", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| JSBool b; |
| if (!JS_ValueToBoolean(cx, v, &b)) |
| return false; |
| compileAndGo = b; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "noScriptRval", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| JSBool b; |
| if (!JS_ValueToBoolean(cx, v, &b)) |
| return false; |
| noScriptRval = b; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "fileName", v.address())) |
| return false; |
| if (JSVAL_IS_NULL(v)) { |
| fileName = NULL; |
| } else if (!JSVAL_IS_VOID(v)) { |
| JSString *s = JS_ValueToString(cx, v); |
| if (!s) |
| return false; |
| fileName = fileNameBytes.encodeLatin1(cx, s); |
| if (!fileName) |
| return false; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "element", v.address())) |
| return false; |
| if (!JSVAL_IS_PRIMITIVE(v)) |
| element = JSVAL_TO_OBJECT(v); |
| |
| if (!JS_GetProperty(cx, opts, "sourceMapURL", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| sourceMapURL = JS_ValueToString(cx, v); |
| if (!sourceMapURL) |
| return false; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "lineNumber", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| uint32_t u; |
| if (!JS_ValueToECMAUint32(cx, v, &u)) |
| return false; |
| lineNumber = u; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "global", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| global = JSVAL_IS_PRIMITIVE(v) ? NULL : JSVAL_TO_OBJECT(v); |
| if (global) { |
| global = js::UncheckedUnwrap(global); |
| if (!global) |
| return false; |
| } |
| if (!global || !(JS_GetClass(global)->flags & JSCLASS_IS_GLOBAL)) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE, |
| "\"global\" passed to evaluate()", "not a global object"); |
| return false; |
| } |
| } |
| |
| if (!JS_GetProperty(cx, opts, "catchTermination", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| JSBool b; |
| if (!JS_ValueToBoolean(cx, v, &b)) |
| return false; |
| catchTermination = b; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "saveFrameChain", v.address())) |
| return false; |
| if (!JSVAL_IS_VOID(v)) { |
| JSBool b; |
| if (!JS_ValueToBoolean(cx, v, &b)) |
| return false; |
| saveFrameChain = b; |
| } |
| } |
| |
| RootedString code(cx, args[0].toString()); |
| |
| size_t codeLength; |
| const jschar *codeChars = JS_GetStringCharsAndLength(cx, code, &codeLength); |
| if (!codeChars) |
| return false; |
| |
| AutoNewContext ancx; |
| if (newContext) { |
| if (!ancx.enter(cx)) |
| return false; |
| cx = ancx.get(); |
| } |
| |
| { |
| AutoSaveFrameChain asfc(cx); |
| if (saveFrameChain && !asfc.save()) |
| return false; |
| |
| JSAutoCompartment ac(cx, global); |
| uint32_t oldopts = JS_GetOptions(cx); |
| uint32_t opts = oldopts & ~(JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); |
| if (compileAndGo) |
| opts |= JSOPTION_COMPILE_N_GO; |
| if (noScriptRval) |
| opts |= JSOPTION_NO_SCRIPT_RVAL; |
| |
| JS_SetOptions(cx, opts); |
| CompileOptions options(cx); |
| options.setFileAndLine(fileName, lineNumber); |
| options.setElement(element); |
| RootedScript script(cx, JS::Compile(cx, global, options, codeChars, codeLength)); |
| JS_SetOptions(cx, oldopts); |
| if (!script) |
| return false; |
| |
| if (sourceMapURL && !script->scriptSource()->hasSourceMap()) { |
| const jschar *smurl = JS_GetStringCharsZ(cx, sourceMapURL); |
| if (!smurl) |
| return false; |
| jschar *smurl_copy = js_strdup(cx, smurl); |
| if (!smurl_copy || !script->scriptSource()->setSourceMap(cx, smurl_copy, script->filename())) |
| return false; |
| } |
| if (!JS_ExecuteScript(cx, global, script, vp)) { |
| if (catchTermination && !JS_IsExceptionPending(cx)) { |
| JSAutoCompartment ac1(cx, callerGlobal); |
| JSString *str = JS_NewStringCopyZ(cx, "terminated"); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| return JS_WrapValue(cx, vp); |
| } |
| |
| static JSString * |
| FileAsString(JSContext *cx, const char *pathname) |
| { |
| FILE *file; |
| RootedString str(cx); |
| size_t len, cc; |
| char *buf; |
| |
| file = fopen(pathname, "rb"); |
| if (!file) { |
| JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); |
| return NULL; |
| } |
| |
| if (fseek(file, 0, SEEK_END) != 0) { |
| JS_ReportError(cx, "can't seek end of %s", pathname); |
| } else { |
| len = ftell(file); |
| if (fseek(file, 0, SEEK_SET) != 0) { |
| JS_ReportError(cx, "can't seek start of %s", pathname); |
| } else { |
| buf = (char*) JS_malloc(cx, len + 1); |
| if (buf) { |
| cc = fread(buf, 1, len, file); |
| if (cc != len) { |
| JS_ReportError(cx, "can't read %s: %s", pathname, |
| (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); |
| } else { |
| jschar *ucbuf; |
| size_t uclen; |
| |
| len = (size_t)cc; |
| |
| if (!InflateUTF8StringToBuffer(cx, buf, len, NULL, &uclen)) { |
| JS_ReportError(cx, "Invalid UTF-8 in file '%s'", pathname); |
| gExitCode = EXITCODE_RUNTIME_ERROR; |
| return NULL; |
| } |
| |
| ucbuf = (jschar*)js_malloc(uclen * sizeof(jschar)); |
| InflateUTF8StringToBuffer(cx, buf, len, ucbuf, &uclen); |
| str = JS_NewUCStringCopyN(cx, ucbuf, uclen); |
| js_free(ucbuf); |
| } |
| JS_free(cx, buf); |
| } |
| } |
| } |
| fclose(file); |
| |
| return str; |
| } |
| |
| static JSObject * |
| FileAsTypedArray(JSContext *cx, const char *pathname) |
| { |
| FILE *file = fopen(pathname, "rb"); |
| if (!file) { |
| JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); |
| return NULL; |
| } |
| |
| RootedObject obj(cx); |
| if (fseek(file, 0, SEEK_END) != 0) { |
| JS_ReportError(cx, "can't seek end of %s", pathname); |
| } else { |
| size_t len = ftell(file); |
| if (fseek(file, 0, SEEK_SET) != 0) { |
| JS_ReportError(cx, "can't seek start of %s", pathname); |
| } else { |
| obj = JS_NewUint8Array(cx, len); |
| if (!obj) |
| return NULL; |
| char *buf = (char *) TypedArray::viewData(obj); |
| size_t cc = fread(buf, 1, len, file); |
| if (cc != len) { |
| JS_ReportError(cx, "can't read %s: %s", pathname, |
| (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); |
| obj = NULL; |
| } |
| } |
| } |
| fclose(file); |
| return obj; |
| } |
| |
| /* |
| * Function to run scripts and return compilation + execution time. Semantics |
| * are closely modelled after the equivalent function in WebKit, as this is used |
| * to produce benchmark timings by SunSpider. |
| */ |
| static JSBool |
| Run(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "run"); |
| return false; |
| } |
| |
| RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!thisobj) |
| return false; |
| |
| JSString *str = JS_ValueToString(cx, args[0]); |
| if (!str) |
| return false; |
| args[0] = STRING_TO_JSVAL(str); |
| JSAutoByteString filename(cx, str); |
| if (!filename) |
| return false; |
| |
| const jschar *ucbuf = NULL; |
| size_t buflen; |
| str = FileAsString(cx, filename.ptr()); |
| if (str) |
| ucbuf = JS_GetStringCharsAndLength(cx, str, &buflen); |
| if (!ucbuf) |
| return false; |
| |
| JS::Anchor<JSString *> a_str(str); |
| uint32_t oldopts = JS_GetOptions(cx); |
| JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); |
| |
| int64_t startClock = PRMJ_Now(); |
| RootedScript script(cx, JS_CompileUCScript(cx, thisobj, ucbuf, buflen, filename.ptr(), 1)); |
| JS_SetOptions(cx, oldopts); |
| if (!script || !JS_ExecuteScript(cx, thisobj, script, NULL)) |
| return false; |
| |
| int64_t endClock = PRMJ_Now(); |
| args.rval().setDouble((endClock - startClock) / double(PRMJ_USEC_PER_MSEC)); |
| return true; |
| } |
| |
| /* |
| * function readline() |
| * Provides a hook for scripts to read a line from stdin. |
| */ |
| static JSBool |
| ReadLine(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| #define BUFSIZE 256 |
| FILE *from = stdin; |
| size_t buflength = 0; |
| size_t bufsize = BUFSIZE; |
| char *buf = (char *) JS_malloc(cx, bufsize); |
| if (!buf) |
| return false; |
| |
| bool sawNewline = false; |
| size_t gotlength; |
| while ((gotlength = js_fgets(buf + buflength, bufsize - buflength, from)) > 0) { |
| buflength += gotlength; |
| |
| /* Are we done? */ |
| if (buf[buflength - 1] == '\n') { |
| buf[buflength - 1] = '\0'; |
| sawNewline = true; |
| break; |
| } else if (buflength < bufsize - 1) { |
| break; |
| } |
| |
| /* Else, grow our buffer for another pass. */ |
| char *tmp; |
| bufsize *= 2; |
| if (bufsize > buflength) { |
| tmp = (char *) JS_realloc(cx, buf, bufsize); |
| } else { |
| JS_ReportOutOfMemory(cx); |
| tmp = NULL; |
| } |
| |
| if (!tmp) { |
| JS_free(cx, buf); |
| return false; |
| } |
| |
| buf = tmp; |
| } |
| |
| /* Treat the empty string specially. */ |
| if (buflength == 0) { |
| args.rval().set(feof(from) ? NullValue() : JS_GetEmptyStringValue(cx)); |
| JS_free(cx, buf); |
| return true; |
| } |
| |
| /* Shrink the buffer to the real size. */ |
| char *tmp = static_cast<char*>(JS_realloc(cx, buf, buflength)); |
| if (!tmp) { |
| JS_free(cx, buf); |
| return false; |
| } |
| |
| buf = tmp; |
| |
| /* |
| * Turn buf into a JSString. Note that buflength includes the trailing null |
| * character. |
| */ |
| JSString *str = JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength); |
| JS_free(cx, buf); |
| if (!str) |
| return false; |
| |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static JSBool |
| PutStr(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 0) { |
| JSString *str = JS_ValueToString(cx, args[0]); |
| if (!str) |
| return false; |
| char *bytes = JSStringToUTF8(cx, str); |
| if (!bytes) |
| return false; |
| fputs(bytes, gOutFile); |
| JS_free(cx, bytes); |
| fflush(gOutFile); |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| Now(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC); |
| args.rval().setDouble(now); |
| return true; |
| } |
| |
| static JSBool |
| PrintInternal(JSContext *cx, const CallArgs &args, FILE *file) |
| { |
| for (unsigned i = 0; i < args.length(); i++) { |
| JSString *str = JS_ValueToString(cx, args[i]); |
| if (!str) |
| return false; |
| char *bytes = JSStringToUTF8(cx, str); |
| if (!bytes) |
| return false; |
| fprintf(file, "%s%s", i ? " " : "", bytes); |
| #if JS_TRACE_LOGGING |
| TraceLog(TraceLogging::defaultLogger(), bytes); |
| #endif |
| JS_free(cx, bytes); |
| } |
| |
| fputc('\n', file); |
| fflush(file); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| Print(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return PrintInternal(cx, args, gOutFile); |
| } |
| |
| static JSBool |
| PrintErr(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return PrintInternal(cx, args, gErrFile); |
| } |
| |
| static JSBool |
| Help(JSContext *cx, unsigned argc, jsval *vp); |
| |
| static JSBool |
| Quit(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JS_ConvertArguments(cx, args.length(), args.array(), "/ i", &gExitCode); |
| |
| gQuitting = true; |
| return false; |
| } |
| |
| static const char * |
| ToSource(JSContext *cx, jsval *vp, JSAutoByteString *bytes) |
| { |
| JSString *str = JS_ValueToSource(cx, *vp); |
| if (str) { |
| *vp = STRING_TO_JSVAL(str); |
| if (bytes->encodeLatin1(cx, str)) |
| return bytes->ptr(); |
| } |
| JS_ClearPendingException(cx); |
| return "<<error converting value to string>>"; |
| } |
| |
| static JSBool |
| AssertEq(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| (args.length() < 2) |
| ? JSSMSG_NOT_ENOUGH_ARGS |
| : (args.length() == 3) |
| ? JSSMSG_INVALID_ARGS |
| : JSSMSG_TOO_MANY_ARGS, |
| "assertEq"); |
| return false; |
| } |
| |
| JSBool same; |
| if (!JS_SameValue(cx, args[0], args[1], &same)) |
| return false; |
| if (!same) { |
| JSAutoByteString bytes0, bytes1; |
| const char *actual = ToSource(cx, &args[0], &bytes0); |
| const char *expected = ToSource(cx, &args[1], &bytes1); |
| if (args.length() == 2) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_ASSERT_EQ_FAILED, |
| actual, expected); |
| } else { |
| JSAutoByteString bytes2(cx, args[2].toString()); |
| if (!bytes2) |
| return false; |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_ASSERT_EQ_FAILED_MSG, |
| actual, expected, bytes2.ptr()); |
| } |
| return false; |
| } |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSScript * |
| ValueToScript(JSContext *cx, jsval v, JSFunction **funp = NULL) |
| { |
| RootedFunction fun(cx, JS_ValueToFunction(cx, v)); |
| if (!fun) |
| return NULL; |
| |
| // Unwrap bound functions. |
| while (fun->isBoundFunction()) { |
| JSObject *target = fun->getBoundFunctionTarget(); |
| if (target && target->is<JSFunction>()) |
| fun = &target->as<JSFunction>(); |
| else |
| break; |
| } |
| |
| if (!fun->isInterpreted()) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_SCRIPTS_ONLY); |
| return NULL; |
| } |
| |
| JSScript *script = fun->getOrCreateScript(cx); |
| if (!script) |
| return NULL; |
| |
| if (fun && funp) |
| *funp = fun; |
| |
| return script; |
| } |
| |
| static JSBool |
| SetDebug(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() == 0 || !args[0].isBoolean()) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_NOT_ENOUGH_ARGS, "setDebug"); |
| return false; |
| } |
| |
| /* |
| * Debug mode can only be set when there is no JS code executing on the |
| * stack. Unfortunately, that currently means that this call will fail |
| * unless debug mode is already set to what you're trying to set it to. |
| * In the future, this restriction may be lifted. |
| */ |
| |
| bool ok = !!JS_SetDebugMode(cx, args[0].toBoolean()); |
| if (ok) |
| args.rval().setBoolean(true); |
| return ok; |
| } |
| |
| static JSBool |
| GetScriptAndPCArgs(JSContext *cx, unsigned argc, jsval *argv, MutableHandleScript scriptp, |
| int32_t *ip) |
| { |
| RootedScript script(cx, GetTopScript(cx)); |
| *ip = 0; |
| if (argc != 0) { |
| jsval v = argv[0]; |
| unsigned intarg = 0; |
| if (!JSVAL_IS_PRIMITIVE(v) && |
| JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) { |
| script = ValueToScript(cx, v); |
| if (!script) |
| return false; |
| intarg++; |
| } |
| if (argc > intarg) { |
| if (!JS_ValueToInt32(cx, argv[intarg], ip)) |
| return false; |
| if ((uint32_t)*ip >= script->length) { |
| JS_ReportError(cx, "Invalid PC"); |
| return false; |
| } |
| } |
| } |
| |
| scriptp.set(script); |
| |
| return true; |
| } |
| |
| static JSTrapStatus |
| TrapHandler(JSContext *cx, JSScript *, jsbytecode *pc, jsval *rvalArg, |
| jsval closure) |
| { |
| JSString *str = JSVAL_TO_STRING(closure); |
| RootedValue rval(cx, *rvalArg); |
| |
| ScriptFrameIter iter(cx); |
| JS_ASSERT(!iter.done()); |
| |
| /* Debug-mode currently disables Ion compilation. */ |
| JSAbstractFramePtr frame(Jsvalify(iter.abstractFramePtr())); |
| RootedScript script(cx, iter.script()); |
| |
| size_t length; |
| const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); |
| if (!chars) |
| return JSTRAP_ERROR; |
| |
| if (!frame.evaluateUCInStackFrame(cx, chars, length, |
| script->filename(), |
| script->lineno, |
| &rval)) |
| { |
| *rvalArg = rval; |
| return JSTRAP_ERROR; |
| } |
| *rvalArg = rval; |
| if (!rval.isUndefined()) |
| return JSTRAP_RETURN; |
| return JSTRAP_CONTINUE; |
| } |
| |
| static JSBool |
| Trap(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedScript script(cx); |
| int32_t i; |
| |
| if (args.length() == 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE); |
| return false; |
| } |
| argc = args.length() - 1; |
| RootedString str(cx, JS_ValueToString(cx, args[argc])); |
| if (!str) |
| return false; |
| args[argc] = STRING_TO_JSVAL(str); |
| if (!GetScriptAndPCArgs(cx, argc, args.array(), &script, &i)) |
| return false; |
| if (uint32_t(i) >= script->length) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE); |
| return false; |
| } |
| args.rval().setUndefined(); |
| return JS_SetTrap(cx, script, script->code + i, TrapHandler, STRING_TO_JSVAL(str)); |
| } |
| |
| static JSBool |
| Untrap(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedScript script(cx); |
| int32_t i; |
| |
| if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i)) |
| return false; |
| JS_ClearTrap(cx, script, script->code + i, NULL, NULL); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSTrapStatus |
| DebuggerAndThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, |
| void *closure) |
| { |
| return TrapHandler(cx, script, pc, rval, STRING_TO_JSVAL((JSString *)closure)); |
| } |
| |
| static JSBool |
| SetDebuggerHandler(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() == 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_NOT_ENOUGH_ARGS, "setDebuggerHandler"); |
| return false; |
| } |
| |
| JSString *str = JS_ValueToString(cx, args[0]); |
| if (!str) |
| return false; |
| |
| JS_SetDebuggerHandler(cx->runtime(), DebuggerAndThrowHandler, str); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| SetThrowHook(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JSString *str; |
| if (args.length() == 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_NOT_ENOUGH_ARGS, "setThrowHook"); |
| return false; |
| } |
| |
| str = JS_ValueToString(cx, args[0]); |
| if (!str) |
| return false; |
| |
| JS_SetThrowHook(cx->runtime(), DebuggerAndThrowHandler, str); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| LineToPC(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedScript script(cx); |
| int32_t lineArg = 0; |
| uint32_t lineno; |
| jsbytecode *pc; |
| |
| if (args.length() == 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_LINE2PC_USAGE); |
| return false; |
| } |
| script = GetTopScript(cx); |
| jsval v = args[0]; |
| if (!JSVAL_IS_PRIMITIVE(v) && |
| JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) |
| { |
| script = ValueToScript(cx, v); |
| if (!script) |
| return false; |
| lineArg++; |
| } |
| if (!JS_ValueToECMAUint32(cx, args[lineArg], &lineno)) |
| return false; |
| pc = JS_LineNumberToPC(cx, script, lineno); |
| if (!pc) |
| return false; |
| args.rval().setInt32(pc - script->code); |
| return true; |
| } |
| |
| static JSBool |
| PCToLine(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedScript script(cx); |
| int32_t i; |
| unsigned lineno; |
| |
| if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i)) |
| return false; |
| lineno = JS_PCToLineNumber(cx, script, script->code + i); |
| if (!lineno) |
| return false; |
| args.rval().setInt32(lineno); |
| return true; |
| } |
| |
| #if defined(DEBUG) || defined(STARBOARD_DEBUG) |
| |
| static void |
| UpdateSwitchTableBounds(JSContext *cx, HandleScript script, unsigned offset, |
| unsigned *start, unsigned *end) |
| { |
| jsbytecode *pc; |
| JSOp op; |
| ptrdiff_t jmplen; |
| int32_t low, high, n; |
| |
| pc = script->code + offset; |
| op = JSOp(*pc); |
| switch (op) { |
| case JSOP_TABLESWITCH: |
| jmplen = JUMP_OFFSET_LEN; |
| pc += jmplen; |
| low = GET_JUMP_OFFSET(pc); |
| pc += JUMP_OFFSET_LEN; |
| high = GET_JUMP_OFFSET(pc); |
| pc += JUMP_OFFSET_LEN; |
| n = high - low + 1; |
| break; |
| |
| default: |
| /* [condswitch] switch does not have any jump or lookup tables. */ |
| JS_ASSERT(op == JSOP_CONDSWITCH); |
| return; |
| } |
| |
| *start = (unsigned)(pc - script->code); |
| *end = *start + (unsigned)(n * jmplen); |
| } |
| |
| static void |
| SrcNotes(JSContext *cx, HandleScript script, Sprinter *sp) |
| { |
| Sprint(sp, "\nSource notes:\n"); |
| Sprint(sp, "%4s %4s %5s %6s %-8s %s\n", |
| "ofs", "line", "pc", "delta", "desc", "args"); |
| Sprint(sp, "---- ---- ----- ------ -------- ------\n"); |
| unsigned offset = 0; |
| unsigned colspan = 0; |
| unsigned lineno = script->lineno; |
| jssrcnote *notes = script->notes(); |
| unsigned switchTableEnd = 0, switchTableStart = 0; |
| for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { |
| unsigned delta = SN_DELTA(sn); |
| offset += delta; |
| SrcNoteType type = (SrcNoteType) SN_TYPE(sn); |
| const char *name = js_SrcNoteSpec[type].name; |
| Sprint(sp, "%3u: %4u %5u [%4u] %-8s", unsigned(sn - notes), lineno, offset, delta, name); |
| switch (type) { |
| case SRC_NULL: |
| case SRC_IF: |
| case SRC_CONTINUE: |
| case SRC_BREAK: |
| case SRC_BREAK2LABEL: |
| case SRC_SWITCHBREAK: |
| case SRC_ASSIGNOP: |
| case SRC_HIDDEN: |
| case SRC_CATCH: |
| case SRC_XDELTA: |
| break; |
| |
| case SRC_COLSPAN: |
| colspan = js_GetSrcNoteOffset(sn, 0); |
| if (colspan >= SN_COLSPAN_DOMAIN / 2) |
| colspan -= SN_COLSPAN_DOMAIN; |
| Sprint(sp, "%d", colspan); |
| break; |
| |
| case SRC_SETLINE: |
| lineno = js_GetSrcNoteOffset(sn, 0); |
| Sprint(sp, " lineno %u", lineno); |
| break; |
| |
| case SRC_NEWLINE: |
| ++lineno; |
| break; |
| |
| case SRC_FOR: |
| Sprint(sp, " cond %u update %u tail %u", |
| unsigned(js_GetSrcNoteOffset(sn, 0)), |
| unsigned(js_GetSrcNoteOffset(sn, 1)), |
| unsigned(js_GetSrcNoteOffset(sn, 2))); |
| break; |
| |
| case SRC_IF_ELSE: |
| Sprint(sp, " else %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| case SRC_FOR_IN: |
| Sprint(sp, " closingjump %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| case SRC_COND: |
| case SRC_WHILE: |
| case SRC_NEXTCASE: |
| Sprint(sp, " offset %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| case SRC_TABLESWITCH: { |
| JSOp op = JSOp(script->code[offset]); |
| JS_ASSERT(op == JSOP_TABLESWITCH); |
| Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
| UpdateSwitchTableBounds(cx, script, offset, |
| &switchTableStart, &switchTableEnd); |
| break; |
| } |
| case SRC_CONDSWITCH: { |
| JSOp op = JSOp(script->code[offset]); |
| JS_ASSERT(op == JSOP_CONDSWITCH); |
| Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
| unsigned caseOff = (unsigned) js_GetSrcNoteOffset(sn, 1); |
| if (caseOff) |
| Sprint(sp, " first case offset %u", caseOff); |
| UpdateSwitchTableBounds(cx, script, offset, |
| &switchTableStart, &switchTableEnd); |
| break; |
| } |
| |
| default: |
| JS_ASSERT(0); |
| break; |
| } |
| Sprint(sp, "\n"); |
| } |
| } |
| |
| static JSBool |
| Notes(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) |
| return false; |
| |
| for (unsigned i = 0; i < args.length(); i++) { |
| RootedScript script (cx, ValueToScript(cx, args[i])); |
| if (!script) |
| return false; |
| |
| SrcNotes(cx, script, &sprinter); |
| } |
| |
| JSString *str = JS_NewStringCopyZ(cx, sprinter.string()); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| JS_STATIC_ASSERT(JSTRY_CATCH == 0); |
| JS_STATIC_ASSERT(JSTRY_FINALLY == 1); |
| JS_STATIC_ASSERT(JSTRY_ITER == 2); |
| |
| static const char* const TryNoteNames[] = { "catch", "finally", "iter", "loop" }; |
| |
| static JSBool |
| TryNotes(JSContext *cx, HandleScript script, Sprinter *sp) |
| { |
| JSTryNote *tn, *tnlimit; |
| |
| if (!script->hasTrynotes()) |
| return true; |
| |
| tn = script->trynotes()->vector; |
| tnlimit = tn + script->trynotes()->length; |
| Sprint(sp, "\nException table:\nkind stack start end\n"); |
| do { |
| JS_ASSERT(tn->kind < ArrayLength(TryNoteNames)); |
| Sprint(sp, " %-7s %6u %8u %8u\n", |
| TryNoteNames[tn->kind], tn->stackDepth, |
| tn->start, tn->start + tn->length); |
| } while (++tn != tnlimit); |
| return true; |
| } |
| |
| static bool |
| DisassembleScript(JSContext *cx, HandleScript script, HandleFunction fun, bool lines, |
| bool recursive, Sprinter *sp) |
| { |
| if (fun) { |
| Sprint(sp, "flags:"); |
| if (fun->isLambda()) |
| Sprint(sp, " LAMBDA"); |
| if (fun->isHeavyweight()) |
| Sprint(sp, " HEAVYWEIGHT"); |
| if (fun->isExprClosure()) |
| Sprint(sp, " EXPRESSION_CLOSURE"); |
| if (fun->isFunctionPrototype()) |
| Sprint(sp, " Function.prototype"); |
| if (fun->isSelfHostedBuiltin()) |
| Sprint(sp, " SELF_HOSTED"); |
| if (fun->isSelfHostedConstructor()) |
| Sprint(sp, " SELF_HOSTED_CTOR"); |
| if (fun->isArrow()) |
| Sprint(sp, " ARROW"); |
| Sprint(sp, "\n"); |
| } |
| |
| if (!js_Disassemble(cx, script, lines, sp)) |
| return false; |
| SrcNotes(cx, script, sp); |
| TryNotes(cx, script, sp); |
| |
| if (recursive && script->hasObjects()) { |
| ObjectArray *objects = script->objects(); |
| for (unsigned i = 0; i != objects->length; ++i) { |
| JSObject *obj = objects->vector[i]; |
| if (obj->is<JSFunction>()) { |
| Sprint(sp, "\n"); |
| RootedFunction fun(cx, &obj->as<JSFunction>()); |
| if (fun->isInterpreted()) { |
| RootedScript script(cx, fun->getOrCreateScript(cx)); |
| if (!script || !DisassembleScript(cx, script, fun, lines, recursive, sp)) |
| return false; |
| } else { |
| Sprint(sp, "[native code]\n"); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| namespace { |
| |
| struct DisassembleOptionParser { |
| unsigned argc; |
| jsval *argv; |
| bool lines; |
| bool recursive; |
| |
| DisassembleOptionParser(unsigned argc, jsval *argv) |
| : argc(argc), argv(argv), lines(false), recursive(false) {} |
| |
| bool parse(JSContext *cx) { |
| /* Read options off early arguments */ |
| while (argc > 0 && argv[0].isString()) { |
| JSString *str = argv[0].toString(); |
| JSFlatString *flatStr = JS_FlattenString(cx, str); |
| if (!flatStr) |
| return false; |
| if (JS_FlatStringEqualsAscii(flatStr, "-l")) |
| lines = true; |
| else if (JS_FlatStringEqualsAscii(flatStr, "-r")) |
| recursive = true; |
| else |
| break; |
| argv++, argc--; |
| } |
| return true; |
| } |
| }; |
| |
| } /* anonymous namespace */ |
| |
| static bool |
| DisassembleToSprinter(JSContext *cx, unsigned argc, jsval *vp, Sprinter *sprinter) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| DisassembleOptionParser p(args.length(), args.array()); |
| if (!p.parse(cx)) |
| return false; |
| |
| if (p.argc == 0) { |
| /* Without arguments, disassemble the current script. */ |
| RootedScript script(cx, GetTopScript(cx)); |
| if (script) { |
| if (!js_Disassemble(cx, script, p.lines, sprinter)) |
| return false; |
| SrcNotes(cx, script, sprinter); |
| TryNotes(cx, script, sprinter); |
| } |
| } else { |
| for (unsigned i = 0; i < p.argc; i++) { |
| RootedFunction fun(cx); |
| RootedScript script (cx, ValueToScript(cx, p.argv[i], fun.address())); |
| if (!script) |
| return false; |
| if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, sprinter)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static JSBool |
| DisassembleToString(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) |
| return false; |
| if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) |
| return false; |
| |
| JSString *str = JS_NewStringCopyZ(cx, sprinter.string()); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static JSBool |
| Disassemble(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) |
| return false; |
| if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) |
| return false; |
| |
| fprintf(stdout, "%s\n", sprinter.string()); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| DisassFile(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| /* Support extra options at the start, just like Disassemble. */ |
| DisassembleOptionParser p(args.length(), args.array()); |
| if (!p.parse(cx)) |
| return false; |
| |
| if (!p.argc) { |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); |
| if (!thisobj) |
| return false; |
| |
| JSString *str = JS_ValueToString(cx, p.argv[0]); |
| if (!str) |
| return false; |
| JSAutoByteString filename(cx, str); |
| if (!filename) |
| return false; |
| |
| uint32_t oldopts = JS_GetOptions(cx); |
| JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); |
| CompileOptions options(cx); |
| options.setUTF8(true) |
| .setFileAndLine(filename.ptr(), 1); |
| RootedScript script (cx, JS::Compile(cx, thisobj, options, filename.ptr())); |
| JS_SetOptions(cx, oldopts); |
| if (!script) |
| return false; |
| |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) |
| return false; |
| bool ok = DisassembleScript(cx, script, NullPtr(), p.lines, p.recursive, &sprinter); |
| if (ok) |
| fprintf(stdout, "%s\n", sprinter.string()); |
| if (!ok) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| DisassWithSrc(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| #define LINE_BUF_LEN 512 |
| unsigned len, line1, line2, bupline; |
| FILE *file; |
| char linebuf[LINE_BUF_LEN]; |
| jsbytecode *pc, *end; |
| static const char sep[] = ";-------------------------"; |
| |
| bool ok = true; |
| RootedScript script(cx); |
| for (unsigned i = 0; ok && i < args.length(); i++) { |
| script = ValueToScript(cx, args[i]); |
| if (!script) |
| return false; |
| |
| if (!script->filename()) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_FILE_SCRIPTS_ONLY); |
| return false; |
| } |
| |
| file = fopen(script->filename(), "r"); |
| if (!file) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_CANT_OPEN, script->filename(), |
| strerror(errno)); |
| return false; |
| } |
| |
| pc = script->code; |
| end = pc + script->length; |
| |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) { |
| ok = false; |
| goto bail; |
| } |
| |
| /* burn the leading lines */ |
| line2 = JS_PCToLineNumber(cx, script, pc); |
| for (line1 = 0; line1 < line2 - 1; line1++) { |
| char *tmp = fgets(linebuf, LINE_BUF_LEN, file); |
| if (!tmp) { |
| JS_ReportError(cx, "failed to read %s fully", script->filename()); |
| ok = false; |
| goto bail; |
| } |
| } |
| |
| bupline = 0; |
| while (pc < end) { |
| line2 = JS_PCToLineNumber(cx, script, pc); |
| |
| if (line2 < line1) { |
| if (bupline != line2) { |
| bupline = line2; |
| Sprint(&sprinter, "%s %3u: BACKUP\n", sep, line2); |
| } |
| } else { |
| if (bupline && line1 == line2) |
| Sprint(&sprinter, "%s %3u: RESTORE\n", sep, line2); |
| bupline = 0; |
| while (line1 < line2) { |
| if (!fgets(linebuf, LINE_BUF_LEN, file)) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, |
| JSSMSG_UNEXPECTED_EOF, |
| script->filename()); |
| ok = false; |
| goto bail; |
| } |
| line1++; |
| Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf); |
| } |
| } |
| |
| len = js_Disassemble1(cx, script, pc, pc - script->code, true, &sprinter); |
| if (!len) { |
| ok = false; |
| goto bail; |
| } |
| pc += len; |
| } |
| |
| bail: |
| fclose(file); |
| } |
| args.rval().setUndefined(); |
| return ok; |
| #undef LINE_BUF_LEN |
| } |
| |
| static JSBool |
| DumpHeap(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| jsval v; |
| void* startThing; |
| JSGCTraceKind startTraceKind; |
| const char *badTraceArg; |
| void *thingToFind; |
| size_t maxDepth; |
| void *thingToIgnore; |
| FILE *dumpFile; |
| bool ok; |
| |
| const char *fileName = NULL; |
| JSAutoByteString fileNameBytes; |
| if (args.length() > 0) { |
| v = args[0]; |
| if (!v.isNull()) { |
| JSString *str; |
| |
| str = JS_ValueToString(cx, v); |
| if (!str) |
| return false; |
| args[0] = STRING_TO_JSVAL(str); |
| if (!fileNameBytes.encodeLatin1(cx, str)) |
| return false; |
| fileName = fileNameBytes.ptr(); |
| } |
| } |
| |
| // Grab the depth param first, because JS_ValueToECMAUint32 can GC, and |
| // there's no easy way to root the traceable void* parameters below. |
| maxDepth = (size_t)-1; |
| if (args.length() > 3) { |
| v = args[3]; |
| if (!v.isNull()) { |
| uint32_t depth; |
| |
| if (!JS_ValueToECMAUint32(cx, v, &depth)) |
| return false; |
| maxDepth = depth; |
| } |
| } |
| |
| startThing = NULL; |
| startTraceKind = JSTRACE_OBJECT; |
| if (args.length() > 1) { |
| v = args[1]; |
| if (v.isMarkable()) { |
| startThing = JSVAL_TO_TRACEABLE(v); |
| startTraceKind = v.gcKind(); |
| } else if (!v.isNull()) { |
| badTraceArg = "start"; |
| goto not_traceable_arg; |
| } |
| } |
| |
| thingToFind = NULL; |
| if (args.length() > 2) { |
| v = args[2]; |
| if (v.isMarkable()) { |
| thingToFind = JSVAL_TO_TRACEABLE(v); |
| } else if (!v.isNull()) { |
| badTraceArg = "toFind"; |
| goto not_traceable_arg; |
| } |
| } |
| |
| thingToIgnore = NULL; |
| if (args.length() > 4) { |
| v = args[4]; |
| if (v.isMarkable()) { |
| thingToIgnore = JSVAL_TO_TRACEABLE(v); |
| } else if (!v.isNull()) { |
| badTraceArg = "toIgnore"; |
| goto not_traceable_arg; |
| } |
| } |
| |
| if (!fileName) { |
| dumpFile = stdout; |
| } else { |
| dumpFile = fopen(fileName, "w"); |
| if (!dumpFile) { |
| JS_ReportError(cx, "can't open %s: %s", fileName, strerror(errno)); |
| return false; |
| } |
| } |
| |
| ok = JS_DumpHeap(JS_GetRuntime(cx), dumpFile, startThing, startTraceKind, thingToFind, |
| maxDepth, thingToIgnore); |
| if (dumpFile != stdout) |
| fclose(dumpFile); |
| if (!ok) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| args.rval().setUndefined(); |
| return true; |
| |
| not_traceable_arg: |
| JS_ReportError(cx, "argument '%s' is not null or a heap-allocated thing", |
| badTraceArg); |
| return false; |
| } |
| |
| static JSBool |
| DumpObject(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject arg0(cx); |
| if (!JS_ConvertArguments(cx, args.length(), args.array(), "o", arg0.address())) |
| return false; |
| |
| js_DumpObject(arg0); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| #endif /* DEBUG */ |
| |
| static JSBool |
| BuildDate(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| Intern(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JSString *str = JS_ValueToString(cx, args.length() == 0 ? UndefinedValue() : args[0]); |
| if (!str) |
| return false; |
| |
| size_t length; |
| const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); |
| if (!chars) |
| return false; |
| |
| if (!JS_InternUCStringN(cx, chars, length)) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| Clone(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedObject parent(cx); |
| RootedObject funobj(cx); |
| |
| if (!args.length()) { |
| JS_ReportError(cx, "Invalid arguments to clone"); |
| return false; |
| } |
| |
| { |
| Maybe<JSAutoCompartment> ac; |
| RootedObject obj(cx, JSVAL_IS_PRIMITIVE(args[0]) ? NULL : &args[0].toObject()); |
| |
| if (obj && IsCrossCompartmentWrapper(obj)) { |
| obj = UncheckedUnwrap(obj); |
| ac.construct(cx, obj); |
| args[0] = ObjectValue(*obj); |
| } |
| if (obj && obj->is<JSFunction>()) { |
| funobj = obj; |
| } else { |
| JSFunction *fun = JS_ValueToFunction(cx, args[0]); |
| if (!fun) |
| return false; |
| funobj = JS_GetFunctionObject(fun); |
| } |
| } |
| if (funobj->compartment() != cx->compartment()) { |
| JSFunction *fun = &funobj->as<JSFunction>(); |
| if (fun->hasScript() && fun->nonLazyScript()->compileAndGo) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE, |
| "function", "compile-and-go"); |
| return false; |
| } |
| } |
| |
| if (argc > 1) { |
| if (!JS_ValueToObject(cx, args[1], parent.address())) |
| return false; |
| } else { |
| parent = JS_GetParent(JSVAL_TO_OBJECT(JS_CALLEE(cx, vp))); |
| } |
| |
| JSObject *clone = JS_CloneFunctionObject(cx, funobj, parent); |
| if (!clone) |
| return false; |
| JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(clone)); |
| return true; |
| } |
| |
| static JSBool |
| GetPDA(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| RootedObject vobj(cx); |
| bool ok; |
| JSPropertyDescArray pda; |
| JSPropertyDesc *pd; |
| jsval v; |
| |
| if (!JS_ValueToObject(cx, argc == 0 ? UndefinedValue() : vp[2], vobj.address())) |
| return false; |
| if (!vobj) { |
| JS_SET_RVAL(cx, vp, UndefinedValue()); |
| return true; |
| } |
| |
| RootedObject aobj(cx, JS_NewArrayObject(cx, 0, NULL)); |
| if (!aobj) |
| return false; |
| JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(aobj)); |
| |
| ok = !!JS_GetPropertyDescArray(cx, vobj, &pda); |
| if (!ok) |
| return false; |
| pd = pda.array; |
| RootedObject pdobj(cx); |
| for (uint32_t i = 0; i < pda.length; i++, pd++) { |
| pdobj = JS_NewObject(cx, NULL, NULL, NULL); |
| if (!pdobj) { |
| ok = false; |
| break; |
| } |
| |
| /* Protect pdobj from GC by setting it as an element of aobj now */ |
| v = OBJECT_TO_JSVAL(pdobj); |
| ok = !!JS_SetElement(cx, aobj, i, &v); |
| if (!ok) |
| break; |
| |
| ok = JS_SetProperty(cx, pdobj, "id", &pd->id) && |
| JS_SetProperty(cx, pdobj, "value", &pd->value) && |
| (v = INT_TO_JSVAL(pd->flags), |
| JS_SetProperty(cx, pdobj, "flags", &v)) && |
| JS_SetProperty(cx, pdobj, "alias", &pd->alias); |
| if (!ok) |
| break; |
| } |
| JS_PutPropertyDescArray(cx, &pda); |
| return ok; |
| } |
| |
| static JSBool |
| GetSLX(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| RootedScript script(cx); |
| |
| script = ValueToScript(cx, argc == 0 ? UndefinedValue() : vp[2]); |
| if (!script) |
| return false; |
| JS_SET_RVAL(cx, vp, INT_TO_JSVAL(js_GetScriptLineExtent(script))); |
| return true; |
| } |
| |
| static JSBool |
| ToInt32(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| int32_t i; |
| |
| if (!JS_ValueToInt32(cx, argc == 0 ? UndefinedValue() : vp[2], &i)) |
| return false; |
| JS_SET_RVAL(cx, vp, JS_NumberValue(i)); |
| return true; |
| } |
| |
| static JSBool |
| ThrowError(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| JS_ReportError(cx, "This is an error"); |
| return false; |
| } |
| |
| #define LAZY_STANDARD_CLASSES |
| |
| /* A class for easily testing the inner/outer object callbacks. */ |
| typedef struct ComplexObject { |
| bool isInner; |
| bool frozen; |
| JSObject *inner; |
| JSObject *outer; |
| } ComplexObject; |
| |
| static JSBool |
| sandbox_enumerate(JSContext *cx, HandleObject obj) |
| { |
| RootedValue v(cx); |
| JSBool b; |
| |
| if (!JS_GetProperty(cx, obj, "lazy", v.address())) |
| return false; |
| |
| JS_ValueToBoolean(cx, v, &b); |
| return !b || JS_EnumerateStandardClasses(cx, obj); |
| } |
| |
| static JSBool |
| sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags, |
| MutableHandleObject objp) |
| { |
| RootedValue v(cx); |
| JSBool b, resolved; |
| |
| if (!JS_GetProperty(cx, obj, "lazy", v.address())) |
| return false; |
| |
| JS_ValueToBoolean(cx, v, &b); |
| if (b) { |
| if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) |
| return false; |
| if (resolved) { |
| objp.set(obj); |
| return true; |
| } |
| } |
| objp.set(NULL); |
| return true; |
| } |
| |
| static JSClass sandbox_class = { |
| "sandbox", |
| JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS, |
| JS_PropertyStub, JS_DeletePropertyStub, |
| JS_PropertyStub, JS_StrictPropertyStub, |
| sandbox_enumerate, (JSResolveOp)sandbox_resolve, |
| JS_ConvertStub |
| }; |
| |
| static JSObject * |
| NewSandbox(JSContext *cx, bool lazy) |
| { |
| RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, NULL)); |
| if (!obj) |
| return NULL; |
| |
| { |
| JSAutoCompartment ac(cx, obj); |
| if (!lazy && !JS_InitStandardClasses(cx, obj)) |
| return NULL; |
| |
| RootedValue value(cx, BooleanValue(lazy)); |
| if (!JS_SetProperty(cx, obj, "lazy", value.address())) |
| return NULL; |
| } |
| |
| if (!cx->compartment()->wrap(cx, obj.address())) |
| return NULL; |
| return obj; |
| } |
| |
| static JSBool |
| EvalInContext(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| RootedString str(cx); |
| RootedObject sobj(cx); |
| if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "S / o", str.address(), sobj.address())) |
| return false; |
| |
| size_t srclen; |
| const jschar *src = JS_GetStringCharsAndLength(cx, str, &srclen); |
| if (!src) |
| return false; |
| |
| SkipRoot skip(cx, &src); |
| |
| bool lazy = false; |
| if (srclen == 4) { |
| if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') { |
| lazy = true; |
| srclen = 0; |
| } |
| } |
| |
| if (!sobj) { |
| sobj = NewSandbox(cx, lazy); |
| if (!sobj) |
| return false; |
| } |
| |
| if (srclen == 0) { |
| JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(sobj)); |
| return true; |
| } |
| |
| RootedScript script(cx); |
| unsigned lineno; |
| |
| JS_DescribeScriptedCaller(cx, script.address(), &lineno); |
| RootedValue rval(cx); |
| { |
| Maybe<JSAutoCompartment> ac; |
| unsigned flags; |
| JSObject *unwrapped = UncheckedUnwrap(sobj, true, &flags); |
| if (flags & Wrapper::CROSS_COMPARTMENT) { |
| sobj = unwrapped; |
| ac.construct(cx, sobj); |
| } |
| |
| sobj = GetInnerObject(cx, sobj); |
| if (!sobj) |
| return false; |
| if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) { |
| JS_ReportError(cx, "Invalid scope argument to evalcx"); |
| return false; |
| } |
| if (!JS_EvaluateUCScript(cx, sobj, src, srclen, |
| script->filename(), |
| lineno, |
| rval.address())) { |
| return false; |
| } |
| } |
| |
| if (!cx->compartment()->wrap(cx, &rval)) |
| return false; |
| |
| JS_SET_RVAL(cx, vp, rval); |
| return true; |
| } |
| |
| static JSBool |
| EvalInFrame(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| jsval *argv = JS_ARGV(cx, vp); |
| if (argc < 2 || |
| !JSVAL_IS_INT(argv[0]) || |
| !JSVAL_IS_STRING(argv[1])) { |
| JS_ReportError(cx, "Invalid arguments to evalInFrame"); |
| return false; |
| } |
| |
| uint32_t upCount = JSVAL_TO_INT(argv[0]); |
| RootedString str(cx, JSVAL_TO_STRING(argv[1])); |
| |
| bool saveCurrent = (argc >= 3 && JSVAL_IS_BOOLEAN(argv[2])) |
| ? !!(JSVAL_TO_BOOLEAN(argv[2])) |
| : false; |
| |
| /* This is a copy of CheckDebugMode. */ |
| if (!JS_GetDebugMode(cx)) { |
| JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, |
| NULL, JSMSG_NEED_DEBUG_MODE); |
| return false; |
| } |
| |
| /* Debug-mode currently disables Ion compilation. */ |
| ScriptFrameIter fi(cx); |
| for (uint32_t i = 0; i < upCount; ++i, ++fi) { |
| ScriptFrameIter next(fi); |
| ++next; |
| if (next.done()) |
| break; |
| } |
| |
| AutoSaveFrameChain sfc(cx); |
| mozilla::Maybe<AutoCompartment> ac; |
| if (saveCurrent) { |
| if (!sfc.save()) |
| return false; |
| ac.construct(cx, GetDefaultGlobalForContext(cx)); |
| } |
| |
| size_t length; |
| const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); |
| if (!chars) |
| return false; |
| |
| JSAbstractFramePtr frame(Jsvalify(fi.abstractFramePtr())); |
| RootedScript fpscript(cx, frame.script()); |
| bool ok = !!frame.evaluateUCInStackFrame(cx, chars, length, |
| fpscript->filename(), |
| JS_PCToLineNumber(cx, fpscript, |
| fi.pc()), |
| MutableHandleValue::fromMarkedLocation(vp)); |
| return ok; |
| } |
| |
| static JSBool |
| ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp) |
| { |
| JS::Value v; |
| if (argc < 1 || !((v = JS_ARGV(cx, vp)[0]).isObject())) { |
| JS_ReportError(cx, "shapeOf: object expected"); |
| return false; |
| } |
| JSObject *obj = &v.toObject(); |
| JS_SET_RVAL(cx, vp, JS_NumberValue((double) ((uintptr_t)obj->lastProperty() >> 3))); |
| return true; |
| } |
| |
| /* |
| * If referent has an own property named id, copy that property to obj[id]. |
| * Since obj is native, this isn't totally transparent; properties of a |
| * non-native referent may be simplified to data properties. |
| */ |
| static JSBool |
| CopyProperty(JSContext *cx, HandleObject obj, HandleObject referent, HandleId id, |
| unsigned lookupFlags, MutableHandleObject objp) |
| { |
| RootedShape shape(cx); |
| AutoPropertyDescriptorRooter desc(cx); |
| unsigned propFlags = 0; |
| RootedObject obj2(cx); |
| |
| objp.set(NULL); |
| if (referent->isNative()) { |
| if (!LookupPropertyWithFlags(cx, referent, id, lookupFlags, &obj2, &shape)) |
| return false; |
| if (obj2 != referent) |
| return true; |
| |
| if (shape->hasSlot()) { |
| desc.value = referent->nativeGetSlot(shape->slot()); |
| } else { |
| desc.value.setUndefined(); |
| } |
| |
| desc.attrs = shape->attributes(); |
| desc.getter = shape->getter(); |
| if (!desc.getter && !(desc.attrs & JSPROP_GETTER)) |
| desc.getter = JS_PropertyStub; |
| desc.setter = shape->setter(); |
| if (!desc.setter && !(desc.attrs & JSPROP_SETTER)) |
| desc.setter = JS_StrictPropertyStub; |
| desc.shortid = shape->shortid(); |
| propFlags = shape->getFlags(); |
| } else if (IsProxy(referent)) { |
| if (!Proxy::getOwnPropertyDescriptor(cx, referent, id, &desc, 0)) |
| return false; |
| if (!desc.obj) |
| return true; |
| } else { |
| if (!JSObject::lookupGeneric(cx, referent, id, objp, &shape)) |
| return false; |
| if (objp != referent) |
| return true; |
| RootedValue value(cx); |
| if (!JSObject::getGeneric(cx, referent, referent, id, &value) || |
| !JSObject::getGenericAttributes(cx, referent, id, &desc.attrs)) |
| { |
| return false; |
| } |
| desc.value = value; |
| desc.attrs &= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; |
| desc.getter = JS_PropertyStub; |
| desc.setter = JS_StrictPropertyStub; |
| desc.shortid = 0; |
| } |
| |
| RootedValue value(cx, desc.value); |
| objp.set(obj); |
| return DefineNativeProperty(cx, obj, id, value, desc.getter, desc.setter, |
| desc.attrs, propFlags, desc.shortid); |
| } |
| |
| static JSBool |
| resolver_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags, |
| MutableHandleObject objp) |
| { |
| jsval v = JS_GetReservedSlot(obj, 0); |
| Rooted<JSObject*> vobj(cx, &v.toObject()); |
| return CopyProperty(cx, obj, vobj, id, flags, objp); |
| } |
| |
| static JSBool |
| resolver_enumerate(JSContext *cx, HandleObject obj) |
| { |
| jsval v = JS_GetReservedSlot(obj, 0); |
| RootedObject referent(cx, JSVAL_TO_OBJECT(v)); |
| |
| AutoIdArray ida(cx, JS_Enumerate(cx, referent)); |
| bool ok = !!ida; |
| RootedObject ignore(cx); |
| for (size_t i = 0; ok && i < ida.length(); i++) { |
| Rooted<jsid> id(cx, ida[i]); |
| ok = CopyProperty(cx, obj, referent, id, 0, &ignore); |
| } |
| return ok; |
| } |
| |
| static JSClass resolver_class = { |
| "resolver", |
| JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1), |
| JS_PropertyStub, JS_DeletePropertyStub, |
| JS_PropertyStub, JS_StrictPropertyStub, |
| resolver_enumerate, (JSResolveOp)resolver_resolve, |
| JS_ConvertStub |
| }; |
| |
| |
| static JSBool |
| Resolver(JSContext *cx, unsigned argc, jsval *vp) |
| { |
| RootedObject referent(cx, NULL); |
| RootedObject proto(cx, NULL); |
| if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "o/o", &referent, &proto)) |
| return false; |
| |
| RootedObject parent(cx, JS_GetParent(referent)); |
| JSObject *result = (argc > 1 |
| ? JS_NewObjectWithGivenProto |
| : JS_NewObject)(cx, &resolver_class, proto, parent); |
| if (!result) |
| return false; |
| |
| JS_SetReservedSlot(result, 0, OBJECT_TO_JSVAL(referent)); |
| JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result)); |
| return true; |
| } |
| |
| #ifdef JS_THREADSAFE |
| |
| /* |
| * Check that t1 comes strictly before t2. The function correctly deals with |
| * wrap-around between t2 and t1 assuming that t2 and t1 stays within INT32_MAX |
| * from each other. We use MAX_TIMEOUT_INTERVAL to enforce this restriction. |
| */ |
| static bool |
| IsBefore(int64_t t1, int64_t t2) |
| { |
| return int32_t(t1 |