| /* -*- 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 "mozilla/ArrayUtils.h" |
| #include "mozilla/Atomics.h" |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/GuardObjects.h" |
| #if !defined(STARBOARD) |
| // mozalloc is only (minimally) used in the shell, and thus is not fully |
| // starboardized. Don't bother including it and just ifdef out references to |
| // stuff from it. |
| #include "mozilla/mozalloc.h" |
| #endif // !defined(STARBOARD) |
| #include "mozilla/PodOperations.h" |
| |
| #ifdef XP_WIN |
| # include <direct.h> |
| # include <process.h> |
| #endif |
| #include <errno.h> |
| #include <fcntl.h> |
| #if defined(XP_WIN) |
| # include <io.h> /* for isatty() */ |
| #endif |
| #include <locale.h> |
| #if defined(MALLOC_H) |
| # include MALLOC_H /* for malloc_usable_size, malloc_size, _msize */ |
| #endif |
| #include <math.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #ifdef XP_UNIX |
| # include <sys/mman.h> |
| # include <sys/stat.h> |
| # include <sys/wait.h> |
| # include <unistd.h> |
| #endif |
| |
| #include "jsapi.h" |
| #include "jsarray.h" |
| #include "jsatom.h" |
| #include "jscntxt.h" |
| #include "jsfun.h" |
| #include "jslock.h" |
| #include "jsobj.h" |
| #include "jsprf.h" |
| #include "jsscript.h" |
| #include "jstypes.h" |
| #include "jsutil.h" |
| #ifdef XP_WIN |
| # include "jswin.h" |
| #endif |
| #include "jswrapper.h" |
| #include "shellmoduleloader.out.h" |
| |
| #include "builtin/ModuleObject.h" |
| #include "builtin/TestingFunctions.h" |
| #include "frontend/Parser.h" |
| #include "gc/GCInternals.h" |
| #include "jit/arm/Simulator-arm.h" |
| #include "jit/InlinableNatives.h" |
| #include "jit/Ion.h" |
| #include "jit/JitcodeMap.h" |
| #include "jit/OptimizationTracking.h" |
| #include "js/Debug.h" |
| #include "js/GCAPI.h" |
| #include "js/Initialization.h" |
| #include "js/StructuredClone.h" |
| #include "js/TrackedOptimizationInfo.h" |
| #include "perf/jsperf.h" |
| #include "shell/jsoptparse.h" |
| #include "shell/OSObject.h" |
| #include "vm/ArgumentsObject.h" |
| #include "vm/Compression.h" |
| #include "vm/Debugger.h" |
| #include "vm/HelperThreads.h" |
| #include "vm/Monitor.h" |
| #include "vm/Shape.h" |
| #include "vm/SharedArrayObject.h" |
| #include "vm/StringBuffer.h" |
| #include "vm/Time.h" |
| #include "vm/TypedArrayObject.h" |
| #include "vm/WrapperObject.h" |
| |
| #include "jscompartmentinlines.h" |
| #include "jsobjinlines.h" |
| |
| #include "vm/ErrorObject-inl.h" |
| #include "vm/Interpreter-inl.h" |
| #include "vm/Stack-inl.h" |
| |
| // Unified leak fix: |
| #include "jsshell.h" |
| |
| #if defined(STARBOARD) |
| |
| #if !defined(PATH_MAX) |
| #include "starboard/configuration.h" |
| #define PATH_MAX (SB_FILE_MAX_PATH + 1) |
| #endif |
| |
| #include "starboard/client_porting/wrap_main/wrap_main.h" |
| #include "starboard/memory.h" |
| |
| #endif |
| |
| |
| using namespace js; |
| using namespace js::cli; |
| using namespace js::shell; |
| |
| using mozilla::ArrayLength; |
| using mozilla::Atomic; |
| using mozilla::MakeUnique; |
| using mozilla::Maybe; |
| using mozilla::NumberEqualsInt32; |
| using mozilla::PodCopy; |
| using mozilla::PodEqual; |
| using mozilla::UniquePtr; |
| |
| // Unified leak fix: |
| using namespace JS; |
| |
| enum JSShellExitCode { |
| EXITCODE_RUNTIME_ERROR = 3, |
| EXITCODE_FILE_NOT_FOUND = 4, |
| EXITCODE_OUT_OF_MEMORY = 5, |
| EXITCODE_TIMEOUT = 6 |
| }; |
| |
| static const size_t gStackChunkSize = 8192; |
| |
| /* |
| * 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)) |
| static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; |
| #else |
| static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; |
| #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 const double MAX_TIMEOUT_INTERVAL = 1800.0; |
| |
| // Per-runtime shell state. |
| struct ShellRuntime |
| { |
| ShellRuntime(); |
| |
| bool isWorker; |
| double timeoutInterval; |
| Atomic<bool> serviceInterrupt; |
| JS::PersistentRootedValue interruptFunc; |
| bool lastWarningEnabled; |
| JS::PersistentRootedValue lastWarning; |
| |
| /* |
| * Watchdog thread state. |
| */ |
| PRLock* watchdogLock; |
| PRCondVar* watchdogWakeup; |
| PRThread* watchdogThread; |
| bool watchdogHasTimeout; |
| int64_t watchdogTimeout; |
| |
| PRCondVar* sleepWakeup; |
| |
| int exitCode; |
| bool quitting; |
| bool gotError; |
| }; |
| |
| // Shell state set once at startup. |
| static bool enableCodeCoverage = false; |
| static bool enableDisassemblyDumps = false; |
| static bool offthreadCompilation = false; |
| static bool enableBaseline = false; |
| static bool enableIon = false; |
| static bool enableAsmJS = false; |
| static bool enableNativeRegExp = false; |
| static bool enableUnboxedArrays = false; |
| #ifdef JS_GC_ZEAL |
| static char gZealStr[128]; |
| #endif |
| static bool printTiming = false; |
| static const char* jsCacheDir = nullptr; |
| static const char* jsCacheAsmJSPath = nullptr; |
| static FILE* gErrFile = nullptr; |
| static FILE* gOutFile = nullptr; |
| static bool reportWarnings = true; |
| static bool compileOnly = false; |
| static bool fuzzingSafe = false; |
| static bool disableOOMFunctions = false; |
| static const char* moduleLoadPath = "."; |
| |
| #ifdef DEBUG |
| static bool dumpEntrainedVariables = false; |
| static bool OOM_printAllocationCount = false; |
| #endif |
| |
| // Shell state this is only accessed on the main thread. |
| bool jsCachingEnabled = false; |
| mozilla::Atomic<bool> jsCacheOpened(false); |
| |
| static bool |
| SetTimeoutValue(JSContext* cx, double t); |
| |
| static bool |
| InitWatchdog(JSRuntime* rt); |
| |
| static void |
| KillWatchdog(JSRuntime *rt); |
| |
| static bool |
| ScheduleWatchdog(JSRuntime* rt, double t); |
| |
| static void |
| CancelExecution(JSRuntime* rt); |
| |
| static JSContext* |
| NewContext(JSRuntime* rt); |
| |
| static void |
| DestroyContext(JSContext* cx, bool withGC); |
| |
| static JSObject* |
| NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options, |
| JSPrincipals* principals); |
| |
| /* |
| * A toy principals type for the shell. |
| * |
| * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the |
| * set bits in P are a superset of those in Q. Thus, the principal 0 is |
| * subsumed by everything, and the principal ~0 subsumes everything. |
| * |
| * As a special case, a null pointer as a principal is treated like 0xffff. |
| * |
| * The 'newGlobal' function takes an option indicating which principal the |
| * new global should have; 'evaluate' does for the new code. |
| */ |
| class ShellPrincipals final : public JSPrincipals { |
| uint32_t bits; |
| |
| static uint32_t getBits(JSPrincipals* p) { |
| if (!p) |
| return 0xffff; |
| return static_cast<ShellPrincipals*>(p)->bits; |
| } |
| |
| public: |
| explicit ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) { |
| this->refcount = refcount; |
| } |
| |
| bool write(JSContext* cx, JSStructuredCloneWriter* writer) override { |
| MOZ_ASSERT(false, "not implemented"); |
| return false; |
| } |
| |
| static void destroy(JSPrincipals* principals) { |
| MOZ_ASSERT(principals != &fullyTrusted); |
| MOZ_ASSERT(principals->refcount == 0); |
| js_delete(static_cast<const ShellPrincipals*>(principals)); |
| } |
| |
| static bool subsumes(JSPrincipals* first, JSPrincipals* second) { |
| uint32_t firstBits = getBits(first); |
| uint32_t secondBits = getBits(second); |
| return (firstBits | secondBits) == firstBits; |
| } |
| |
| static JSSecurityCallbacks securityCallbacks; |
| |
| // Fully-trusted principals singleton. |
| static ShellPrincipals fullyTrusted; |
| }; |
| |
| JSSecurityCallbacks ShellPrincipals::securityCallbacks = { |
| nullptr, // contentSecurityPolicyAllows |
| subsumes |
| }; |
| |
| // The fully-trusted principal subsumes all other principals. |
| ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1); |
| |
| #ifdef EDITLINE |
| extern "C" { |
| extern JS_EXPORT_API(char*) readline(const char* prompt); |
| extern JS_EXPORT_API(void) add_history(char* line); |
| } // extern "C" |
| #endif |
| |
| ShellRuntime::ShellRuntime() |
| : isWorker(false), |
| timeoutInterval(-1.0), |
| serviceInterrupt(false), |
| lastWarningEnabled(false), |
| watchdogLock(nullptr), |
| watchdogWakeup(nullptr), |
| watchdogThread(nullptr), |
| watchdogHasTimeout(false), |
| watchdogTimeout(0), |
| sleepWakeup(nullptr), |
| exitCode(0), |
| quitting(false), |
| gotError(false) |
| {} |
| |
| static ShellRuntime* |
| GetShellRuntime(JSRuntime *rt) |
| { |
| ShellRuntime* sr = static_cast<ShellRuntime*>(JS_GetRuntimePrivate(rt)); |
| MOZ_ASSERT(sr); |
| return sr; |
| } |
| |
| static ShellRuntime* |
| GetShellRuntime(JSContext* cx) |
| { |
| return GetShellRuntime(cx->runtime()); |
| } |
| |
| static char* |
| GetLine(FILE* file, const char * prompt) |
| { |
| #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 nullptr; |
| if (linep[0] != '\0') |
| add_history(linep); |
| return linep; |
| } |
| #endif |
| |
| size_t len = 0; |
| if (*prompt != '\0') { |
| fprintf(gOutFile, "%s", prompt); |
| fflush(gOutFile); |
| } |
| |
| size_t size = 80; |
| char* buffer = static_cast<char*>(malloc(size)); |
| if (!buffer) |
| return nullptr; |
| |
| char* current = buffer; |
| while (true) { |
| while (true) { |
| if (fgets(current, size - len, file)) |
| break; |
| if (errno != EINTR) { |
| free(buffer); |
| return nullptr; |
| } |
| } |
| |
| 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 = static_cast<char*>(realloc(buffer, size)); |
| if (!tmp) { |
| free(buffer); |
| return nullptr; |
| } |
| buffer = tmp; |
| } |
| current = buffer + len; |
| } |
| |
| if (len && !ferror(file)) |
| return buffer; |
| free(buffer); |
| return nullptr; |
| } |
| |
| /* State to store as JSContext private. */ |
| struct JSShellContextData { |
| /* Creation timestamp, used by the elapsed() shell builtin. */ |
| int64_t startTime; |
| }; |
| |
| static JSShellContextData* |
| NewContextData() |
| { |
| JSShellContextData* data = (JSShellContextData*) |
| js_calloc(sizeof(JSShellContextData), 1); |
| if (!data) |
| return nullptr; |
| data->startTime = PRMJ_Now(); |
| return data; |
| } |
| |
| static inline JSShellContextData* |
| GetContextData(JSContext* cx) |
| { |
| JSShellContextData* data = (JSShellContextData*) JS_GetContextPrivate(cx); |
| |
| MOZ_ASSERT(data); |
| return data; |
| } |
| |
| static bool |
| ShellInterruptCallback(JSContext* cx) |
| { |
| ShellRuntime* sr = GetShellRuntime(cx); |
| if (!sr->serviceInterrupt) |
| return true; |
| |
| // Reset serviceInterrupt. CancelExecution or InterruptIf will set it to |
| // true to distinguish watchdog or user triggered interrupts. |
| // Do this first to prevent other interrupts that may occur while the |
| // user-supplied callback is executing from re-entering the handler. |
| sr->serviceInterrupt = false; |
| |
| bool result; |
| RootedValue interruptFunc(cx, sr->interruptFunc); |
| if (!interruptFunc.isNull()) { |
| JS::AutoSaveExceptionState savedExc(cx); |
| JSAutoCompartment ac(cx, &interruptFunc.toObject()); |
| RootedValue rval(cx); |
| if (!JS_CallFunctionValue(cx, nullptr, interruptFunc, |
| JS::HandleValueArray::empty(), &rval)) |
| { |
| return false; |
| } |
| if (rval.isBoolean()) |
| result = rval.toBoolean(); |
| else |
| result = false; |
| } else { |
| result = false; |
| } |
| |
| if (!result && sr->exitCode == 0) |
| sr->exitCode = EXITCODE_TIMEOUT; |
| |
| return result; |
| } |
| |
| /* |
| * 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 |
| RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) |
| { |
| ShellRuntime* sr = GetShellRuntime(cx); |
| |
| 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); |
| |
| int64_t t1 = PRMJ_Now(); |
| RootedScript script(cx); |
| |
| { |
| CompileOptions options(cx); |
| options.setIntroductionType("js shell file") |
| .setUTF8(true) |
| .setFileAndLine(filename, 1) |
| .setIsRunOnce(true) |
| .setNoScriptRval(true); |
| |
| sr->gotError = false; |
| (void) JS::Compile(cx, options, file, &script); |
| MOZ_ASSERT_IF(!script, sr->gotError); |
| } |
| |
| #ifdef DEBUG |
| if (dumpEntrainedVariables) |
| AnalyzeEntrainedVariables(cx, script); |
| #endif |
| if (script && !compileOnly) { |
| if (!JS_ExecuteScript(cx, script)) { |
| if (!sr->quitting && sr->exitCode != EXITCODE_TIMEOUT) |
| sr->exitCode = EXITCODE_RUNTIME_ERROR; |
| } |
| int64_t t2 = PRMJ_Now() - t1; |
| if (printTiming) |
| printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC); |
| } |
| } |
| |
| static bool |
| InitModuleLoader(JSContext* cx) |
| { |
| // Decompress and evaluate the embedded module loader source to initialize |
| // the module loader for the current compartment. |
| |
| uint32_t srcLen = moduleloader::GetRawScriptsSize(); |
| ScopedJSFreePtr<char> src(cx->pod_malloc<char>(srcLen)); |
| if (!src || !DecompressString(moduleloader::compressedSources, moduleloader::GetCompressedSize(), |
| reinterpret_cast<unsigned char*>(src.get()), srcLen)) |
| { |
| return false; |
| } |
| |
| CompileOptions options(cx); |
| options.setIntroductionType("shell module loader"); |
| options.setFileAndLine("shell/ModuleLoader.js", 1); |
| options.setSelfHostingMode(false); |
| options.setCanLazilyParse(false); |
| options.setVersion(JSVERSION_LATEST); |
| options.werrorOption = true; |
| options.strictOption = true; |
| |
| RootedValue rv(cx); |
| return Evaluate(cx, options, src, srcLen, &rv); |
| } |
| |
| static bool |
| GetLoaderObject(JSContext* cx, MutableHandleObject resultOut) |
| { |
| // Look up the |Reflect.Loader| object that has been defined by the module |
| // loader. |
| |
| RootedObject object(cx, cx->global()); |
| RootedValue value(cx); |
| if (!JS_GetProperty(cx, object, "Reflect", &value) || !value.isObject()) |
| return false; |
| |
| object = &value.toObject(); |
| if (!JS_GetProperty(cx, object, "Loader", &value) || !value.isObject()) |
| return false; |
| |
| resultOut.set(&value.toObject()); |
| return true; |
| } |
| |
| static bool |
| GetImportMethod(JSContext* cx, HandleObject loader, MutableHandleFunction resultOut) |
| { |
| // Look up the module loader's |import| method. |
| |
| RootedValue value(cx); |
| if (!JS_GetProperty(cx, loader, "import", &value) || !value.isObject()) |
| return false; |
| |
| RootedObject object(cx, &value.toObject()); |
| if (!object->is<JSFunction>()) |
| return false; |
| |
| resultOut.set(&object->as<JSFunction>()); |
| return true; |
| } |
| |
| static void |
| RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly) |
| { |
| // Exectute a module by calling |Reflect.Loader.import(filename)|. |
| |
| ShellRuntime* sr = GetShellRuntime(cx); |
| |
| RootedObject loaderObj(cx); |
| MOZ_ALWAYS_TRUE(GetLoaderObject(cx, &loaderObj)); |
| |
| RootedFunction importFun(cx); |
| MOZ_ALWAYS_TRUE(GetImportMethod(cx, loaderObj, &importFun)); |
| |
| AutoValueArray<2> args(cx); |
| args[0].setString(JS_NewStringCopyZ(cx, filename)); |
| args[1].setUndefined(); |
| |
| RootedValue value(cx); |
| if (!JS_CallFunction(cx, loaderObj, importFun, args, &value)) { |
| sr->exitCode = EXITCODE_RUNTIME_ERROR; |
| return; |
| } |
| } |
| |
| static bool |
| EvalAndPrint(JSContext* cx, const char* bytes, size_t length, |
| int lineno, bool compileOnly, FILE* out) |
| { |
| // Eval. |
| JS::CompileOptions options(cx); |
| options.setIntroductionType("js shell interactive") |
| .setUTF8(true) |
| .setIsRunOnce(true) |
| .setFileAndLine("typein", lineno); |
| RootedScript script(cx); |
| if (!JS::Compile(cx, options, bytes, length, &script)) |
| return false; |
| if (compileOnly) |
| return true; |
| RootedValue result(cx); |
| if (!JS_ExecuteScript(cx, script, &result)) |
| return false; |
| |
| if (!result.isUndefined()) { |
| // Print. |
| RootedString str(cx); |
| str = JS_ValueToSource(cx, result); |
| if (!str) |
| return false; |
| |
| char* utf8chars = JS_EncodeStringToUTF8(cx, str); |
| if (!utf8chars) |
| return false; |
| fprintf(out, "%s\n", utf8chars); |
| JS_free(cx, utf8chars); |
| } |
| return true; |
| } |
| |
| static void |
| ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) |
| { |
| ShellRuntime* sr = GetShellRuntime(cx); |
| 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> CharBuffer; |
| CharBuffer buffer(cx); |
| do { |
| ScheduleWatchdog(cx->runtime(), -1); |
| sr->serviceInterrupt = 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(), sr->timeoutInterval)) { |
| hitEOF = true; |
| break; |
| } |
| } while (!JS_BufferIsCompilableUnit(cx, cx->global(), buffer.begin(), buffer.length())); |
| |
| if (hitEOF && buffer.empty()) |
| break; |
| |
| if (!EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly, |
| out)) |
| { |
| // Catch the error, report it, and keep going. |
| JS_ReportPendingException(cx); |
| } |
| } while (!hitEOF && !sr->quitting); |
| |
| fprintf(out, "\n"); |
| } |
| |
| enum FileKind |
| { |
| FileScript, |
| FileModule |
| }; |
| |
| static void |
| Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = FileScript) |
| { |
| FILE* file; |
| if (forceTTY || !filename || strcmp(filename, "-") == 0) { |
| file = stdin; |
| } else { |
| file = fopen(filename, "r"); |
| if (!file) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_CANT_OPEN, filename, strerror(errno)); |
| return; |
| } |
| } |
| AutoCloseFile autoClose(file); |
| |
| if (!forceTTY && !isatty(fileno(file))) { |
| // It's not interactive - just execute it. |
| if (kind == FileScript) |
| RunFile(cx, filename, file, compileOnly); |
| else |
| RunModule(cx, filename, file, compileOnly); |
| } else { |
| // It's an interactive filehandle; drop into read-eval-print loop. |
| MOZ_ASSERT(kind == FileScript); |
| ReadEvalPrintLoop(cx, file, gOutFile, compileOnly); |
| } |
| } |
| |
| static bool |
| Version(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JSVersion origVersion = JS_GetVersion(cx); |
| if (args.length() == 0 || args[0].isUndefined()) { |
| /* 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(); |
| int32_t fvi; |
| if (NumberEqualsInt32(fv, &fvi)) |
| v = fvi; |
| } |
| if (v < 0 || v > JSVERSION_LATEST) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "version"); |
| return false; |
| } |
| JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v)); |
| args.rval().setInt32(origVersion); |
| } |
| return true; |
| } |
| |
| static bool |
| CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() < 1 || args.length() > 3) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
| "createMappedArrayBuffer"); |
| return false; |
| } |
| |
| RootedString rawFilenameStr(cx, JS::ToString(cx, args[0])); |
| if (!rawFilenameStr) |
| return false; |
| // It's a little bizarre to resolve relative to the script, but for testing |
| // I need a file at a known location, and the only good way I know of to do |
| // that right now is to include it in the repo alongside the test script. |
| // Bug 944164 would introduce an alternative. |
| JSString* filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative); |
| if (!filenameStr) |
| return false; |
| JSAutoByteString filename(cx, filenameStr); |
| if (!filename) |
| return false; |
| |
| uint32_t offset = 0; |
| if (args.length() >= 2) { |
| if (!JS::ToUint32(cx, args[1], &offset)) |
| return false; |
| } |
| |
| bool sizeGiven = false; |
| uint32_t size; |
| if (args.length() >= 3) { |
| if (!JS::ToUint32(cx, args[2], &size)) |
| return false; |
| sizeGiven = true; |
| if (offset > size) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); |
| return false; |
| } |
| } |
| |
| FILE* file = fopen(filename.ptr(), "r"); |
| if (!file) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_CANT_OPEN, filename.ptr(), strerror(errno)); |
| return false; |
| } |
| AutoCloseFile autoClose(file); |
| |
| if (!sizeGiven) { |
| struct stat st; |
| if (fstat(fileno(file), &st) < 0) { |
| JS_ReportError(cx, "Unable to stat file"); |
| return false; |
| } |
| if (st.st_size < off_t(offset)) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); |
| return false; |
| } |
| size = st.st_size - offset; |
| } |
| |
| void* contents = JS_CreateMappedArrayBufferContents(fileno(file), offset, size); |
| if (!contents) { |
| JS_ReportError(cx, "failed to allocate mapped array buffer contents (possibly due to bad alignment)"); |
| return false; |
| } |
| |
| RootedObject obj(cx, JS_NewMappedArrayBufferWithContents(cx, size, contents)); |
| if (!obj) |
| return false; |
| |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| static bool |
| Options(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| JS::RuntimeOptions oldRuntimeOptions = JS::RuntimeOptionsRef(cx); |
| for (unsigned i = 0; i < args.length(); i++) { |
| JSString* str = JS::ToString(cx, args[i]); |
| if (!str) |
| return false; |
| args[i].setString(str); |
| |
| JSAutoByteString opt(cx, str); |
| if (!opt) |
| return false; |
| |
| if (strcmp(opt.ptr(), "strict") == 0) |
| JS::RuntimeOptionsRef(cx).toggleExtraWarnings(); |
| else if (strcmp(opt.ptr(), "werror") == 0) |
| JS::RuntimeOptionsRef(cx).toggleWerror(); |
| else if (strcmp(opt.ptr(), "throw_on_asmjs_validation_failure") == 0) |
| JS::RuntimeOptionsRef(cx).toggleThrowOnAsmJSValidationFailure(); |
| else if (strcmp(opt.ptr(), "strict_mode") == 0) |
| JS::RuntimeOptionsRef(cx).toggleStrictMode(); |
| else { |
| JS_ReportError(cx, |
| "unknown option name '%s'." |
| " The valid names are strict," |
| " werror, and strict_mode.", |
| opt.ptr()); |
| return false; |
| } |
| } |
| |
| char* names = strdup(""); |
| bool found = false; |
| if (names && oldRuntimeOptions.extraWarnings()) { |
| names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict"); |
| found = true; |
| } |
| if (names && oldRuntimeOptions.werror()) { |
| names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror"); |
| found = true; |
| } |
| if (names && oldRuntimeOptions.throwOnAsmJSValidationFailure()) { |
| names = JS_sprintf_append(names, "%s%s", found ? "," : "", "throw_on_asmjs_validation_failure"); |
| found = true; |
| } |
| if (names && oldRuntimeOptions.strictMode()) { |
| names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict_mode"); |
| found = true; |
| } |
| if (!names) { |
| JS_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| JSString* str = JS_NewStringCopyZ(cx, names); |
| free(names); |
| if (!str) |
| return false; |
| args.rval().setString(str); |
| return true; |
| } |
| |
| static bool |
| LoadScript(JSContext* cx, unsigned argc, Value* vp, bool scriptRelative) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| RootedString str(cx); |
| for (unsigned i = 0; i < args.length(); i++) { |
| str = JS::ToString(cx, args[i]); |
| if (!str) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "load"); |
| return false; |
| } |
| str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative); |
| 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.setIntroductionType("js shell load") |
| .setUTF8(true) |
| .setIsRunOnce(true) |
| .setNoScriptRval(true); |
| RootedScript script(cx); |
| RootedValue unused(cx); |
| if ((compileOnly && !Compile(cx, opts, filename.ptr(), &script)) || |
| !Evaluate(cx, opts, filename.ptr(), &unused)) |
| { |
| return false; |
| } |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| Load(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return LoadScript(cx, argc, vp, false); |
| } |
| |
| static bool |
| LoadScriptRelativeToScript(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return LoadScript(cx, argc, vp, true); |
| } |
| |
| // Populate |options| with the options given by |opts|'s properties. If we |
| // need to convert a filename to a C string, let fileNameBytes own the |
| // bytes. |
| static bool |
| ParseCompileOptions(JSContext* cx, CompileOptions& options, HandleObject opts, |
| JSAutoByteString& fileNameBytes) |
| { |
| RootedValue v(cx); |
| RootedString s(cx); |
| |
| if (!JS_GetProperty(cx, opts, "isRunOnce", &v)) |
| return false; |
| if (!v.isUndefined()) |
| options.setIsRunOnce(ToBoolean(v)); |
| |
| if (!JS_GetProperty(cx, opts, "noScriptRval", &v)) |
| return false; |
| if (!v.isUndefined()) |
| options.setNoScriptRval(ToBoolean(v)); |
| |
| if (!JS_GetProperty(cx, opts, "fileName", &v)) |
| return false; |
| if (v.isNull()) { |
| options.setFile(nullptr); |
| } else if (!v.isUndefined()) { |
| s = ToString(cx, v); |
| if (!s) |
| return false; |
| char* fileName = fileNameBytes.encodeLatin1(cx, s); |
| if (!fileName) |
| return false; |
| options.setFile(fileName); |
| } |
| |
| if (!JS_GetProperty(cx, opts, "element", &v)) |
| return false; |
| if (v.isObject()) |
| options.setElement(&v.toObject()); |
| |
| if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) |
| return false; |
| if (!v.isUndefined()) { |
| s = ToString(cx, v); |
| if (!s) |
| return false; |
| options.setElementAttributeName(s); |
| } |
| |
| if (!JS_GetProperty(cx, opts, "lineNumber", &v)) |
| return false; |
| if (!v.isUndefined()) { |
| uint32_t u; |
| if (!ToUint32(cx, v, &u)) |
| return false; |
| options.setLine(u); |
| } |
| |
| if (!JS_GetProperty(cx, opts, "columnNumber", &v)) |
| return false; |
| if (!v.isUndefined()) { |
| int32_t c; |
| if (!ToInt32(cx, v, &c)) |
| return false; |
| options.setColumn(c); |
| } |
| |
| if (!JS_GetProperty(cx, opts, "sourceIsLazy", &v)) |
| return false; |
| if (v.isBoolean()) |
| options.setSourceIsLazy(v.toBoolean()); |
| |
| return true; |
| } |
| |
| class AutoNewContext |
| { |
| private: |
| JSContext* oldcx; |
| JSContext* newcx; |
| Maybe<JSAutoRequest> newRequest; |
| Maybe<AutoCompartment> newCompartment; |
| |
| AutoNewContext(const AutoNewContext&) = delete; |
| |
| public: |
| AutoNewContext() : oldcx(nullptr), newcx(nullptr) {} |
| |
| bool enter(JSContext* cx) { |
| MOZ_ASSERT(!JS_IsExceptionPending(cx)); |
| oldcx = cx; |
| newcx = NewContext(JS_GetRuntime(cx)); |
| if (!newcx) |
| return false; |
| JS::ContextOptionsRef(newcx).setDontReportUncaught(true); |
| |
| newRequest.emplace(newcx); |
| newCompartment.emplace(newcx, JS::CurrentGlobalOrNull(cx)); |
| return true; |
| } |
| |
| JSContext* get() { return newcx; } |
| |
| ~AutoNewContext() { |
| if (newcx) { |
| RootedValue exc(oldcx); |
| bool throwing = JS_IsExceptionPending(newcx); |
| if (throwing) |
| JS_GetPendingException(newcx, &exc); |
| newCompartment.reset(); |
| newRequest.reset(); |
| if (throwing && JS_WrapValue(oldcx, &exc)) |
| JS_SetPendingException(oldcx, exc); |
| DestroyContext(newcx, false); |
| } |
| } |
| }; |
| |
| static void |
| my_LargeAllocFailCallback(void* data) |
| { |
| JSContext* cx = (JSContext*)data; |
| JSRuntime* rt = cx->runtime(); |
| |
| if (!cx->isJSContext()) |
| return; |
| |
| MOZ_ASSERT(!rt->isHeapBusy()); |
| MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess()); |
| |
| JS::PrepareForFullGC(rt); |
| AutoKeepAtoms keepAtoms(cx->perThreadData); |
| rt->gc.gc(GC_NORMAL, JS::gcreason::SHARED_MEMORY_LIMIT); |
| } |
| |
| static const uint32_t CacheEntry_SOURCE = 0; |
| static const uint32_t CacheEntry_BYTECODE = 1; |
| |
| static const JSClass CacheEntry_class = { |
| "CacheEntryObject", JSCLASS_HAS_RESERVED_SLOTS(2) |
| }; |
| |
| static bool |
| CacheEntry(JSContext* cx, unsigned argc, JS::Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 1 || !args[0].isString()) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "CacheEntry"); |
| return false; |
| } |
| |
| RootedObject obj(cx, JS_NewObject(cx, &CacheEntry_class)); |
| if (!obj) |
| return false; |
| |
| SetReservedSlot(obj, CacheEntry_SOURCE, args[0]); |
| SetReservedSlot(obj, CacheEntry_BYTECODE, UndefinedValue()); |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| static bool |
| CacheEntry_isCacheEntry(JSObject* cache) |
| { |
| return JS_GetClass(cache) == &CacheEntry_class; |
| } |
| |
| static JSString* |
| CacheEntry_getSource(HandleObject cache) |
| { |
| MOZ_ASSERT(CacheEntry_isCacheEntry(cache)); |
| Value v = JS_GetReservedSlot(cache, CacheEntry_SOURCE); |
| if (!v.isString()) |
| return nullptr; |
| |
| return v.toString(); |
| } |
| |
| static uint8_t* |
| CacheEntry_getBytecode(HandleObject cache, uint32_t* length) |
| { |
| MOZ_ASSERT(CacheEntry_isCacheEntry(cache)); |
| Value v = JS_GetReservedSlot(cache, CacheEntry_BYTECODE); |
| if (!v.isObject() || !v.toObject().is<ArrayBufferObject>()) |
| return nullptr; |
| |
| ArrayBufferObject* arrayBuffer = &v.toObject().as<ArrayBufferObject>(); |
| *length = arrayBuffer->byteLength(); |
| return arrayBuffer->dataPointer(); |
| } |
| |
| static bool |
| CacheEntry_setBytecode(JSContext* cx, HandleObject cache, uint8_t* buffer, uint32_t length) |
| { |
| MOZ_ASSERT(CacheEntry_isCacheEntry(cache)); |
| |
| ArrayBufferObject::BufferContents contents = |
| ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(buffer); |
| Rooted<ArrayBufferObject*> arrayBuffer(cx, ArrayBufferObject::create(cx, length, contents)); |
| if (!arrayBuffer) |
| return false; |
| |
| SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer)); |
| return true; |
| } |
| |
| class AutoSaveFrameChain |
| { |
| JSContext* cx_; |
| bool saved_; |
| |
| public: |
| explicit 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 bool |
| Evaluate(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() < 1 || args.length() > 2) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
| "evaluate"); |
| return false; |
| } |
| |
| RootedString code(cx, nullptr); |
| RootedObject cacheEntry(cx, nullptr); |
| if (args[0].isString()) { |
| code = args[0].toString(); |
| } else if (args[0].isObject() && CacheEntry_isCacheEntry(&args[0].toObject())) { |
| cacheEntry = &args[0].toObject(); |
| code = CacheEntry_getSource(cacheEntry); |
| } |
| |
| if (!code || (args.length() == 2 && args[1].isPrimitive())) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate"); |
| return false; |
| } |
| |
| CompileOptions options(cx); |
| JSAutoByteString fileNameBytes; |
| bool newContext = false; |
| RootedString displayURL(cx); |
| RootedString sourceMapURL(cx); |
| RootedObject global(cx, nullptr); |
| bool catchTermination = false; |
| bool saveFrameChain = false; |
| bool loadBytecode = false; |
| bool saveBytecode = false; |
| bool assertEqBytecode = false; |
| RootedObject callerGlobal(cx, cx->global()); |
| |
| options.setIntroductionType("js shell evaluate") |
| .setFileAndLine("@evaluate", 1); |
| |
| global = JS_GetGlobalForObject(cx, &args.callee()); |
| if (!global) |
| return false; |
| |
| if (args.length() == 2) { |
| RootedObject opts(cx, &args[1].toObject()); |
| RootedValue v(cx); |
| |
| if (!ParseCompileOptions(cx, options, opts, fileNameBytes)) |
| return false; |
| |
| if (!JS_GetProperty(cx, opts, "newContext", &v)) |
| return false; |
| if (!v.isUndefined()) |
| newContext = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, opts, "displayURL", &v)) |
| return false; |
| if (!v.isUndefined()) { |
| displayURL = ToString(cx, v); |
| if (!displayURL) |
| return false; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "sourceMapURL", &v)) |
| return false; |
| if (!v.isUndefined()) { |
| sourceMapURL = ToString(cx, v); |
| if (!sourceMapURL) |
| return false; |
| } |
| |
| if (!JS_GetProperty(cx, opts, "global", &v)) |
| return false; |
| if (!v.isUndefined()) { |
| if (v.isObject()) { |
| global = js::UncheckedUnwrap(&v.toObject()); |
| if (!global) |
| return false; |
| } |
| if (!global || !(JS_GetClass(global)->flags & JSCLASS_IS_GLOBAL)) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
| "\"global\" passed to evaluate()", "not a global object"); |
| return false; |
| } |
| } |
| |
| if (!JS_GetProperty(cx, opts, "catchTermination", &v)) |
| return false; |
| if (!v.isUndefined()) |
| catchTermination = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, opts, "saveFrameChain", &v)) |
| return false; |
| if (!v.isUndefined()) |
| saveFrameChain = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, opts, "loadBytecode", &v)) |
| return false; |
| if (!v.isUndefined()) |
| loadBytecode = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, opts, "saveBytecode", &v)) |
| return false; |
| if (!v.isUndefined()) |
| saveBytecode = ToBoolean(v); |
| |
| if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) |
| return false; |
| if (!v.isUndefined()) |
| assertEqBytecode = ToBoolean(v); |
| |
| // We cannot load or save the bytecode if we have no object where the |
| // bytecode cache is stored. |
| if (loadBytecode || saveBytecode) { |
| if (!cacheEntry) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, |
| "evaluate"); |
| return false; |
| } |
| } |
| } |
| |
| AutoStableStringChars codeChars(cx); |
| if (!codeChars.initTwoByte(cx, code)) |
| return false; |
| |
| AutoNewContext ancx; |
| if (newContext) { |
| if (!ancx.enter(cx)) |
| return false; |
| cx = ancx.get(); |
| } |
| |
| uint32_t loadLength = 0; |
| uint8_t* loadBuffer = nullptr; |
| uint32_t saveLength = 0; |
| ScopedJSFreePtr<uint8_t> saveBuffer; |
| |
| if (loadBytecode) { |
| loadBuffer = CacheEntry_getBytecode(cacheEntry, &loadLength); |
| if (!loadBuffer) |
| return false; |
| } |
| |
| { |
| AutoSaveFrameChain asfc(cx); |
| if (saveFrameChain && !asfc.save()) |
| return false; |
| |
| JSAutoCompartment ac(cx, global); |
| RootedScript script(cx); |
| |
| { |
| if (saveBytecode) { |
| if (!JS::CompartmentOptionsRef(cx).cloneSingletons()) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_CACHE_SINGLETON_FAILED); |
| return false; |
| } |
| |
| // cloneSingletons implies that singletons are used as template objects. |
| MOZ_ASSERT(JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()); |
| } |
| |
| if (loadBytecode) { |
| script = JS_DecodeScript(cx, loadBuffer, loadLength); |
| } else { |
| mozilla::Range<const char16_t> chars = codeChars.twoByteRange(); |
| (void) JS::Compile(cx, options, chars.start().get(), chars.length(), &script); |
| } |
| |
| if (!script) |
| return false; |
| } |
| |
| if (displayURL && !script->scriptSource()->hasDisplayURL()) { |
| JSFlatString* flat = displayURL->ensureFlat(cx); |
| if (!flat) |
| return false; |
| |
| AutoStableStringChars chars(cx); |
| if (!chars.initTwoByte(cx, flat)) |
| return false; |
| |
| const char16_t* durl = chars.twoByteRange().start().get(); |
| if (!script->scriptSource()->setDisplayURL(cx, durl)) |
| return false; |
| } |
| if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) { |
| JSFlatString* flat = sourceMapURL->ensureFlat(cx); |
| if (!flat) |
| return false; |
| |
| AutoStableStringChars chars(cx); |
| if (!chars.initTwoByte(cx, flat)) |
| return false; |
| |
| const char16_t* smurl = chars.twoByteRange().start().get(); |
| if (!script->scriptSource()->setSourceMapURL(cx, smurl)) |
| return false; |
| } |
| if (!JS_ExecuteScript(cx, script, args.rval())) { |
| 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; |
| } |
| |
| if (saveBytecode) { |
| saveBuffer = reinterpret_cast<uint8_t*>(JS_EncodeScript(cx, script, &saveLength)); |
| if (!saveBuffer) |
| return false; |
| } |
| } |
| |
| if (saveBytecode) { |
| // If we are both loading and saving, we assert that we are going to |
| // replace the current bytecode by the same stream of bytes. |
| if (loadBytecode && assertEqBytecode) { |
| if (saveLength != loadLength) { |
| char loadLengthStr[16]; |
| JS_snprintf(loadLengthStr, sizeof(loadLengthStr), "%u", loadLength); |
| char saveLengthStr[16]; |
| JS_snprintf(saveLengthStr, sizeof(saveLengthStr), "%u", saveLength); |
| |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED, |
| loadLengthStr, saveLengthStr); |
| return false; |
| } |
| |
| if (!PodEqual(loadBuffer, saveBuffer.get(), loadLength)) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_CACHE_EQ_CONTENT_FAILED); |
| return false; |
| } |
| } |
| |
| if (!CacheEntry_setBytecode(cx, cacheEntry, saveBuffer, saveLength)) |
| return false; |
| |
| saveBuffer.forget(); |
| } |
| |
| return JS_WrapValue(cx, args.rval()); |
| } |
| |
| JSString* |
| js::shell::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 nullptr; |
| } |
| AutoCloseFile autoClose(file); |
| |
| 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 { |
| char16_t* ucbuf = |
| JS::UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf, len), &len).get(); |
| if (!ucbuf) { |
| JS_ReportError(cx, "Invalid UTF-8 in file '%s'", pathname); |
| return nullptr; |
| } |
| str = JS_NewUCStringCopyN(cx, ucbuf, len); |
| free(ucbuf); |
| } |
| JS_free(cx, buf); |
| } |
| } |
| } |
| |
| return str; |
| } |
| |
| /* |
| * 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 bool |
| Run(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() != 1) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "run"); |
| return false; |
| } |
| |
| RootedString str(cx, JS::ToString(cx, args[0])); |
| if (!str) |
| return false; |
| args[0].setString(str); |
| JSAutoByteString filename(cx, str); |
| if (!filename) |
| return false; |
| |
| str = FileAsString(cx, filename.ptr()); |
| if (!str) |
| return false; |
| |
| AutoStableStringChars chars(cx); |
| if (!chars.initTwoByte(cx, str)) |
| return false; |
| |
| const char16_t* ucbuf = chars.twoByteRange().start().get(); |
| size_t buflen = str->length(); |
| |
| RootedScript script(cx); |
| int64_t startClock = PRMJ_Now(); |
| { |
| JS::CompileOptions options(cx); |
| options.setIntroductionType("js shell run") |
| .setFileAndLine(filename.ptr(), 1) |
| .setIsRunOnce(true) |
| .setNoScriptRval(true); |
| if (!JS_CompileUCScript(cx, ucbuf, buflen, options, &script)) |
| return false; |
| } |
| |
| if (!JS_ExecuteScript(cx, script)) |
| 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 bool |
| ReadLine(JSContext* cx, unsigned argc, Value* 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 = static_cast<char*>(JS_realloc(cx, buf, bufsize / 2, bufsize)); |
| } else { |
| JS_ReportOutOfMemory(cx); |
| tmp = nullptr; |
| } |
| |
| 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, bufsize, 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 bool |
| PutStr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() != 0) { |
| RootedString str(cx, JS::ToString(cx, args[0])); |
| if (!str) |
| return false; |
| char* bytes = JS_EncodeStringToUTF8(cx, str); |
| if (!bytes) |
| return false; |
| fputs(bytes, gOutFile); |
| JS_free(cx, bytes); |
| fflush(gOutFile); |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| Now(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC); |
| args.rval().setDouble(now); |
| return true; |
| } |
| |
| static bool |
| PrintInternal(JSContext* cx, const CallArgs& args, FILE* file) |
| { |
| for (unsigned i = 0; i < args.length(); i++) { |
| RootedString str(cx, JS::ToString(cx, args[i])); |
| if (!str) |
| return false; |
| char* bytes = JS_EncodeStringToUTF8(cx, str); |
| if (!bytes) |
| return false; |
| fprintf(file, "%s%s", i ? " " : "", bytes); |
| JS_free(cx, bytes); |
| } |
| |
| fputc('\n', file); |
| fflush(file); |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| Print(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return PrintInternal(cx, args, gOutFile); |
| } |
| |
| static bool |
| PrintErr(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return PrintInternal(cx, args, gErrFile); |
| } |
| |
| static bool |
| Help(JSContext* cx, unsigned argc, Value* vp); |
| |
| static bool |
| Quit(JSContext* cx, unsigned argc, Value* vp) |
| { |
| ShellRuntime* sr = GetShellRuntime(cx); |
| |
| #ifdef JS_MORE_DETERMINISTIC |
| // Print a message to stderr in more-deterministic builds to help jsfunfuzz |
| // find uncatchable-exception bugs. |
| fprintf(stderr, "quit called\n"); |
| #endif |
| |
| CallArgs args = CallArgsFromVp(argc, vp); |
| int32_t code; |
| if (!ToInt32(cx, args.get(0), &code)) |
| return false; |
| |
| // The fuzzers check the shell's exit code and assume a value >= 128 means |
| // the process crashed (for instance, SIGSEGV will result in code 139). On |
| // POSIX platforms, the exit code is 8-bit and negative values can also |
| // result in an exit code >= 128. We restrict the value to range [0, 127] to |
| // avoid false positives. |
| if (code < 0 || code >= 128) { |
| JS_ReportError(cx, "quit exit code should be in range 0-127"); |
| return false; |
| } |
| |
| sr->exitCode = code; |
| sr->quitting = true; |
| return false; |
| } |
| |
| static bool |
| StartTimingMutator(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() > 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_TOO_MANY_ARGS, "startTimingMutator"); |
| return false; |
| } |
| |
| if (!cx->runtime()->gc.stats.startTimingMutator()) { |
| JS_ReportError(cx, "StartTimingMutator should only be called from outside of GC"); |
| return false; |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| StopTimingMutator(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (args.length() > 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_TOO_MANY_ARGS, "stopTimingMutator"); |
| return false; |
| } |
| |
| double mutator_ms, gc_ms; |
| if (!cx->runtime()->gc.stats.stopTimingMutator(mutator_ms, gc_ms)) { |
| JS_ReportError(cx, "stopTimingMutator called when not timing the mutator"); |
| return false; |
| } |
| double total_ms = mutator_ms + gc_ms; |
| if (total_ms > 0) { |
| fprintf(gOutFile, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n", |
| mutator_ms, mutator_ms / total_ms * 100.0, gc_ms, gc_ms / total_ms * 100.0); |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static const char* |
| ToSource(JSContext* cx, MutableHandleValue vp, JSAutoByteString* bytes) |
| { |
| JSString* str = JS_ValueToSource(cx, vp); |
| if (str) { |
| vp.setString(str); |
| if (bytes->encodeLatin1(cx, str)) |
| return bytes->ptr(); |
| } |
| JS_ClearPendingException(cx); |
| return "<<error converting value to string>>"; |
| } |
| |
| static bool |
| AssertEq(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| (args.length() < 2) |
| ? JSSMSG_NOT_ENOUGH_ARGS |
| : (args.length() == 3) |
| ? JSSMSG_INVALID_ARGS |
| : JSSMSG_TOO_MANY_ARGS, |
| "assertEq"); |
| return false; |
| } |
| |
| bool 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, nullptr, JSSMSG_ASSERT_EQ_FAILED, |
| actual, expected); |
| } else { |
| JSAutoByteString bytes2(cx, args[2].toString()); |
| if (!bytes2) |
| return false; |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED_MSG, |
| actual, expected, bytes2.ptr()); |
| } |
| return false; |
| } |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSScript* |
| ValueToScript(JSContext* cx, Value vArg, JSFunction** funp = nullptr) |
| { |
| RootedValue v(cx, vArg); |
| |
| if (v.isString()) { |
| // To convert a string to a script, compile it. Parse it as an ES6 Program. |
| RootedLinearString linearStr(cx, StringToLinearString(cx, v.toString())); |
| if (!linearStr) |
| return nullptr; |
| size_t len = GetLinearStringLength(linearStr); |
| AutoStableStringChars linearChars(cx); |
| if (!linearChars.initTwoByte(cx, linearStr)) |
| return nullptr; |
| const char16_t* chars = linearChars.twoByteRange().start().get(); |
| |
| RootedScript script(cx); |
| CompileOptions options(cx); |
| if (!JS::Compile(cx, options, chars, len, &script)) |
| return nullptr; |
| return script; |
| } |
| |
| RootedFunction fun(cx, JS_ValueToFunction(cx, v)); |
| if (!fun) |
| return nullptr; |
| |
| // 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, nullptr, JSSMSG_SCRIPTS_ONLY); |
| return nullptr; |
| } |
| |
| JSScript* script = fun->getOrCreateScript(cx); |
| if (!script) |
| return nullptr; |
| |
| if (fun && funp) |
| *funp = fun; |
| |
| return script; |
| } |
| |
| static JSScript* |
| GetTopScript(JSContext* cx) |
| { |
| NonBuiltinScriptFrameIter iter(cx); |
| return iter.done() ? nullptr : iter.script(); |
| } |
| |
| static bool |
| GetScriptAndPCArgs(JSContext* cx, unsigned argc, Value* argv, MutableHandleScript scriptp, |
| int32_t* ip) |
| { |
| RootedScript script(cx, GetTopScript(cx)); |
| *ip = 0; |
| if (argc != 0) { |
| Value v = argv[0]; |
| unsigned intarg = 0; |
| if (v.isObject() && |
| JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) { |
| script = ValueToScript(cx, v); |
| if (!script) |
| return false; |
| intarg++; |
| } |
| if (argc > intarg) { |
| if (!JS::ToInt32(cx, HandleValue::fromMarkedLocation(&argv[intarg]), ip)) |
| return false; |
| if ((uint32_t)*ip >= script->length()) { |
| JS_ReportError(cx, "Invalid PC"); |
| return false; |
| } |
| } |
| } |
| |
| scriptp.set(script); |
| |
| return true; |
| } |
| |
| static bool |
| LineToPC(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| if (args.length() == 0) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_LINE2PC_USAGE); |
| return false; |
| } |
| |
| RootedScript script(cx, GetTopScript(cx)); |
| int32_t lineArg = 0; |
| if (args[0].isObject() && args[0].toObject().is<JSFunction>()) { |
| script = ValueToScript(cx, args[0]); |
| if (!script) |
| return false; |
| lineArg++; |
| } |
| |
| uint32_t lineno; |
| if (!ToUint32(cx, args.get(lineArg), &lineno)) |
| return false; |
| |
| jsbytecode* pc = LineNumberToPC(script, lineno); |
| if (!pc) |
| return false; |
| args.rval().setInt32(script->pcToOffset(pc)); |
| return true; |
| } |
| |
| static bool |
| PCToLine(JSContext* cx, unsigned argc, Value* 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 = PCToLineNumber(script, script->offsetToPC(i)); |
| if (!lineno) |
| return false; |
| args.rval().setInt32(lineno); |
| return true; |
| } |
| |
| #ifdef 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->offsetToPC(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. */ |
| MOZ_ASSERT(op == JSOP_CONDSWITCH); |
| return; |
| } |
| |
| *start = script->pcToOffset(pc); |
| *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_XDELTA: |
| break; |
| |
| case SRC_COLSPAN: |
| colspan = SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, 0)); |
| Sprint(sp, "%d", colspan); |
| break; |
| |
| case SRC_SETLINE: |
| lineno = 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(GetSrcNoteOffset(sn, 0)), |
| unsigned(GetSrcNoteOffset(sn, 1)), |
| unsigned(GetSrcNoteOffset(sn, 2))); |
| break; |
| |
| case SRC_IF_ELSE: |
| Sprint(sp, " else %u", unsigned(GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| case SRC_FOR_IN: |
| case SRC_FOR_OF: |
| Sprint(sp, " closingjump %u", unsigned(GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| case SRC_COND: |
| case SRC_WHILE: |
| case SRC_NEXTCASE: |
| Sprint(sp, " offset %u", unsigned(GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| case SRC_TABLESWITCH: { |
| JSOp op = JSOp(script->code()[offset]); |
| MOZ_ASSERT(op == JSOP_TABLESWITCH); |
| Sprint(sp, " length %u", unsigned(GetSrcNoteOffset(sn, 0))); |
| UpdateSwitchTableBounds(cx, script, offset, |
| &switchTableStart, &switchTableEnd); |
| break; |
| } |
| case SRC_CONDSWITCH: { |
| JSOp op = JSOp(script->code()[offset]); |
| MOZ_ASSERT(op == JSOP_CONDSWITCH); |
| Sprint(sp, " length %u", unsigned(GetSrcNoteOffset(sn, 0))); |
| unsigned caseOff = (unsigned) GetSrcNoteOffset(sn, 1); |
| if (caseOff) |
| Sprint(sp, " first case offset %u", caseOff); |
| UpdateSwitchTableBounds(cx, script, offset, |
| &switchTableStart, &switchTableEnd); |
| break; |
| } |
| |
| case SRC_TRY: |
| MOZ_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY); |
| Sprint(sp, " offset to jump %u", unsigned(GetSrcNoteOffset(sn, 0))); |
| break; |
| |
| default: |
| MOZ_ASSERT(0); |
| break; |
| } |
| Sprint(sp, "\n"); |
| } |
| } |
| |
| static bool |
| Notes(JSContext* cx, unsigned argc, Value* 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_FOR_IN == 2); |
| |
| static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop" }; |
| |
| static bool |
| TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) |
| { |
| if (!script->hasTrynotes()) |
| return true; |
| |
| JSTryNote* tn = script->trynotes()->vector; |
| JSTryNote* tnlimit = tn + script->trynotes()->length; |
| Sprint(sp, "\nException table:\nkind stack start end\n"); |
| do { |
| MOZ_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 |
| BlockNotes(JSContext* cx, HandleScript script, Sprinter* sp) |
| { |
| if (!script->hasBlockScopes()) |
| return true; |
| |
| Sprint(sp, "\nBlock table:\n index parent start end\n"); |
| |
| BlockScopeArray* scopes = script->blockScopes(); |
| for (uint32_t i = 0; i < scopes->length; i++) { |
| const BlockScopeNote* note = &scopes->vector[i]; |
| if (note->index == BlockScopeNote::NoBlockScopeIndex) |
| Sprint(sp, "%8s ", "(none)"); |
| else |
| Sprint(sp, "%8u ", note->index); |
| if (note->parent == BlockScopeNote::NoBlockScopeIndex) |
| Sprint(sp, "%8s ", "(none)"); |
| else |
| Sprint(sp, "%8u ", note->parent); |
| Sprint(sp, "%8u %8u\n", note->start, note->start + note->length); |
| } |
| return true; |
| } |
| |
| static bool |
| DisassembleScript(JSContext* cx, HandleScript script, HandleFunction fun, |
| bool lines, bool recursive, bool sourceNotes, Sprinter* sp) |
| { |
| if (fun) { |
| Sprint(sp, "flags:"); |
| if (fun->isLambda()) |
| Sprint(sp, " LAMBDA"); |
| if (fun->needsCallObject()) |
| Sprint(sp, " NEEDS_CALLOBJECT"); |
| if (fun->isConstructor()) |
| Sprint(sp, " CONSTRUCTOR"); |
| if (fun->isExprBody()) |
| Sprint(sp, " EXPRESSION_CLOSURE"); |
| if (fun->isFunctionPrototype()) |
| Sprint(sp, " Function.prototype"); |
| if (fun->isSelfHostedBuiltin()) |
| Sprint(sp, " SELF_HOSTED"); |
| if (fun->isArrow()) |
| Sprint(sp, " ARROW"); |
| Sprint(sp, "\n"); |
| } |
| |
| if (!Disassemble(cx, script, lines, sp)) |
| return false; |
| if (sourceNotes) |
| SrcNotes(cx, script, sp); |
| TryNotes(cx, script, sp); |
| BlockNotes(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) { |
| if (!DisassembleScript(cx, script, fun, lines, recursive, sourceNotes, sp)) |
| return false; |
| } |
| } else { |
| Sprint(sp, "[native code]\n"); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| namespace { |
| |
| struct DisassembleOptionParser { |
| unsigned argc; |
| Value* argv; |
| bool lines; |
| bool recursive; |
| bool sourceNotes; |
| |
| DisassembleOptionParser(unsigned argc, Value* argv) |
| : argc(argc), argv(argv), lines(false), recursive(false), sourceNotes(true) {} |
| |
| 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 if (JS_FlatStringEqualsAscii(flatStr, "-S")) |
| sourceNotes = false; |
| else |
| break; |
| argv++, argc--; |
| } |
| return true; |
| } |
| }; |
| |
| } /* anonymous namespace */ |
| |
| static bool |
| DisassembleToSprinter(JSContext* cx, unsigned argc, Value* 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) { |
| JSAutoCompartment ac(cx, script); |
| if (!Disassemble(cx, script, p.lines, sprinter)) |
| return false; |
| SrcNotes(cx, script, sprinter); |
| TryNotes(cx, script, sprinter); |
| BlockNotes(cx, script, sprinter); |
| } |
| } else { |
| for (unsigned i = 0; i < p.argc; i++) { |
| RootedFunction fun(cx); |
| RootedScript script(cx); |
| RootedValue value(cx, p.argv[i]); |
| if (value.isObject() && value.toObject().is<ModuleObject>()) |
| script = value.toObject().as<ModuleObject>().script(); |
| else |
| script = ValueToScript(cx, value, fun.address()); |
| if (!script) |
| return false; |
| if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, p.sourceNotes, sprinter)) |
| return false; |
| } |
| } |
| |
| return !sprinter->hadOutOfMemory(); |
| } |
| |
| static bool |
| DisassembleToString(JSContext* cx, unsigned argc, Value* 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 bool |
| Disassemble(JSContext* cx, unsigned argc, Value* 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 bool |
| DisassFile(JSContext* cx, unsigned argc, Value* 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; |
| } |
| |
| // We should change DisassembleOptionParser to store CallArgs. |
| JSString* str = JS::ToString(cx, HandleValue::fromMarkedLocation(&p.argv[0])); |
| if (!str) |
| return false; |
| JSAutoByteString filename(cx, str); |
| if (!filename) |
| return false; |
| RootedScript script(cx); |
| |
| { |
| CompileOptions options(cx); |
| options.setIntroductionType("js shell disFile") |
| .setUTF8(true) |
| .setFileAndLine(filename.ptr(), 1) |
| .setIsRunOnce(true) |
| .setNoScriptRval(true); |
| |
| if (!JS::Compile(cx, options, filename.ptr(), &script)) |
| return false; |
| } |
| |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) |
| return false; |
| bool ok = DisassembleScript(cx, script, nullptr, p.lines, p.recursive, p.sourceNotes, &sprinter); |
| if (ok) |
| fprintf(stdout, "%s\n", sprinter.string()); |
| if (!ok) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| DisassWithSrc(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| #define LINE_BUF_LEN 512 |
| unsigned len, line1, line2, bupline; |
| FILE* file; |
| char linebuf[LINE_BUF_LEN]; |
| 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, nullptr, |
| JSSMSG_FILE_SCRIPTS_ONLY); |
| return false; |
| } |
| |
| file = fopen(script->filename(), "r"); |
| if (!file) { |
| JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
| JSSMSG_CANT_OPEN, script->filename(), |
| strerror(errno)); |
| return false; |
| } |
| |
| jsbytecode* pc = script->code(); |
| jsbytecode* end = script->codeEnd(); |
| |
| Sprinter sprinter(cx); |
| if (!sprinter.init()) { |
| ok = false; |
| goto bail; |
| } |
| |
| /* burn the leading lines */ |
| line2 = PCToLineNumber(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 = PCToLineNumber(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, nullptr, |
| JSSMSG_UNEXPECTED_EOF, |
| script->filename()); |
| ok = false; |
| goto bail; |
| } |
| line1++; |
| Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf); |
| } |
| } |
| |
| len = Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter); |
| if (!len) { |
| ok = false; |
| goto bail; |
| } |
| pc += len; |
| } |
| |
| fprintf(stdout, "%s\n", sprinter.string()); |
| |
| bail: |
| fclose(file); |
| } |
| args.rval().setUndefined(); |
| return ok; |
| #undef LINE_BUF_LEN |
| } |
| |
| #endif /* DEBUG */ |
| |
| static bool |
| Intern(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| JSString* str = JS::ToString(cx, args.get(0)); |
| if (!str) |
| return false; |
| |
| AutoStableStringChars strChars(cx); |
| if (!strChars.initTwoByte(cx, str)) |
| return false; |
| |
| mozilla::Range<const char16_t> chars = strChars.twoByteRange(); |
| |
| if (!JS_AtomizeAndPinUCStringN(cx, chars.start().get(), chars.length())) |
| return false; |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static bool |
| Clone(JSContext* cx, unsigned argc, Value* 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, args[0].isPrimitive() ? nullptr : &args[0].toObject()); |
| |
| if (obj && obj->is<CrossCompartmentWrapperObject>()) { |
| obj = UncheckedUnwrap(obj); |
| ac.emplace(cx, obj); |
| args[0].setObject(*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 (args.length() > 1) { |
| if (!JS_ValueToObject(cx, args[1], &parent)) |
| return false; |
| } else { |
| parent = js::GetGlobalForObjectCrossCompartment(&args.callee()); |
| } |
| |
| // Should it worry us that we might be getting with wrappers |
| // around with wrappers here? |
| JS::AutoObjectVector scopeChain(cx); |
| if (!parent->is<GlobalObject>() && !scopeChain.append(parent)) |
| return false; |
| JSObject* clone = JS::CloneFunctionObject(cx, funobj, scopeChain); |
| if (!clone) |
| return false; |
| args.rval().setObject(*clone); |
| return true; |
| } |
| |
| static bool |
| GetSLX(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| RootedScript script(cx); |
| |
| script = ValueToScript(cx, args.get(0)); |
| if (!script) |
| return false; |
| args.rval().setInt32(GetScriptLineExtent(script)); |
| return true; |
| } |
| |
| static bool |
| ThrowError(JSContext* cx, unsigned argc, Value* 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 bool |
| sandbox_enumerate(JSContext* cx, HandleObject obj) |
| { |
| RootedValue v(cx); |
| |
| if (!JS_GetProperty(cx, obj, "lazy", &v)) |
| return false; |
| |
| if (!ToBoolean(v)) |
| return true; |
| |
| return JS_EnumerateStandardClasses(cx, obj); |
| } |
| |
| static bool |
| sandbox_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) |
| { |
| RootedValue v(cx); |
| if (!JS_GetProperty(cx, obj, "lazy", &v)) |
| return false; |
| |
| if (ToBoolean(v)) |
| return JS_ResolveStandardClass(cx, obj, id, resolvedp); |
| return true; |
| } |
| |
| static const JSClass sandbox_class = { |
| "sandbox", |
| JSCLASS_GLOBAL_FLAGS, |
| nullptr, nullptr, nullptr, nullptr, |
| sandbox_enumerate, sandbox_resolve, |
| nullptr, nullptr, |
| nullptr, nullptr, nullptr, |
| JS_GlobalObjectTraceHook |
| }; |
| |
| static JSObject* |
| NewSandbox(JSContext* cx, bool lazy) |
| { |
| RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, nullptr, |
| JS::DontFireOnNewGlobalHook)); |
| if (!obj) |
| return nullptr; |
| |
| { |
| JSAutoCompartment ac(cx, obj); |
| if (!lazy && !JS_InitStandardClasses(cx, obj)) |
| return nullptr; |
| |
| RootedValue value(cx, BooleanValue(lazy)); |
| if (!JS_SetProperty(cx, obj, "lazy", value)) |
| return nullptr; |
| } |
| |
| JS_FireOnNewGlobalObject(cx, obj); |
| |
| if (!cx->compartment()->wrap(cx, &obj)) |
| return nullptr; |
| return obj; |
| } |
| |
| static bool |
| EvalInContext(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.requireAtLeast(cx, "evalcx", 1)) |
| return false; |
| |
| RootedString str(cx, ToString(cx, args[0])); |
| if (!str) |
| return false; |
| |
| RootedObject sobj(cx); |
| if (args.hasDefined(1)) { |
| sobj = ToObject(cx, args[1]); |
| if (!sobj) |
| return false; |
| } |
| |
| AutoStableStringChars strChars(cx); |
| if (!strChars.initTwoByte(cx, str)) |
| return false; |
| |
| mozilla::Range<const char16_t> chars = strChars.twoByteRange(); |
| size_t srclen = chars.length(); |
| const char16_t* src = chars.start().get(); |
| |
| 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) { |
| args.rval().setObject(*sobj); |
| return true; |
| } |
| |
| JS::AutoFilename filename; |
| unsigned lineno; |
| |
| DescribeScriptedCaller(cx, &filename, &lineno); |
| { |
| Maybe<JSAutoCompartment> ac; |
| unsigned flags; |
| JSObject* unwrapped = UncheckedUnwrap(sobj, true, &flags); |
| if (flags & Wrapper::CROSS_COMPARTMENT) { |
| sobj = unwrapped; |
| ac.emplace(cx, sobj); |
| } |
| |
| sobj = ToWindowIfWindowProxy(sobj); |
| |
| if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) { |
| JS_ReportError(cx, "Invalid scope argument to evalcx"); |
| return false; |
| } |
| JS::CompileOptions opts(cx); |
| opts.setFileAndLine(filename.get(), lineno); |
| if (!JS::Evaluate(cx, opts, src, srclen, args.rval())) { |
| return false; |
| } |
| } |
| |
| if (!cx->compartment()->wrap(cx, args.rval())) |
| return false; |
| |
| return true; |
| } |
| |
| struct WorkerInput |
| { |
| JSRuntime* runtime; |
| char16_t* chars; |
| size_t length; |
| |
| WorkerInput(JSRuntime* runtime, char16_t* chars, size_t length) |
| : runtime(runtime), chars(chars), length(length) |
| {} |
| |
| ~WorkerInput() { |
| js_free(chars); |
| } |
| }; |
| |
| static void SetWorkerRuntimeOptions(JSRuntime* rt); |
| |
| static void |
| WorkerMain(void* arg) |
| { |
| WorkerInput* input = (WorkerInput*) arg; |
| |
| JSRuntime* rt = JS_NewRuntime(8L * 1024L * 1024L, 2L * 1024L * 1024L, input->runtime); |
| if (!rt) { |
| js_delete(input); |
| return; |
| } |
| |
| mozilla::UniquePtr<ShellRuntime> sr = MakeUnique<ShellRuntime>(); |
| if (!sr) { |
| JS_DestroyRuntime(rt); |
| js_delete(input); |
| return; |
| } |
| |
| sr->isWorker = true; |
| JS_SetRuntimePrivate(rt, sr.get()); |
| JS_SetErrorReporter(rt, my_ErrorReporter); |
| SetWorkerRuntimeOptions(rt); |
| |
| if (!InitWatchdog(rt)) { |
| JS_DestroyRuntime(rt); |
| js_delete(input); |
| return; |
| } |
| |
| JSContext* cx = NewContext(rt); |
| if (!cx) { |
| JS_DestroyRuntime(rt); |
| js_delete(input); |
| return; |
| } |
| |
| JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx); |
| |
| do { |
| JSAutoRequest ar(cx); |
| |
| JS::CompartmentOptions compartmentOptions; |
| compartmentOptions.setVersion(JSVERSION_DEFAULT); |
| RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr)); |
| if (!global) |
| break; |
| |
| JSAutoCompartment ac(cx, global); |
| |
| JS::CompileOptions options(cx); |
| options.setFileAndLine("<string>", 1) |
| .setIsRunOnce(true); |
| |
| RootedScript script(cx); |
| if (!JS::Compile(cx, options, input->chars, input->length, &script)) |
| break; |
| RootedValue result(cx); |
| JS_ExecuteScript(cx, script, &result); |
| } while (0); |
| |
| JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr); |
| |
| DestroyContext(cx, false); |
| JS_DestroyRuntime(rt); |
| |
| js_delete(input); |
| } |
| |
| Vector<PRThread*, 0, SystemAllocPolicy> workerThreads; |
| |
| static bool |
| EvalInWorker(JSContext* cx, unsigned argc, Value* vp) |
| { |
| if (!CanUseExtraThreads()) { |
| JS_ReportError(cx, "Can't create worker threads with --no-threads"); |
| return false; |
| } |
| |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.get(0).isString()) { |
| JS_ReportError(cx, "Invalid arguments to evalInWorker"); |
| return false; |
| } |
| |
| if (!args[0].toString()->ensureLinear(cx)) |
| return false; |
| |
| JSLinearString* str = &args[0].toString()->asLinear(); |
| |
| char16_t* chars = (char16_t*) js_malloc(str->length() * sizeof(char16_t)); |
| if (!chars) |
| return false; |
| CopyChars(chars, *str); |
| |
| WorkerInput* input = js_new<WorkerInput>(cx->runtime(), chars, str->length()); |
| if (!input) |
| return false; |
| |
| PRThread* thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input, |
| PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, |
| gMaxStackSize + 128 * 1024); |
| if (!thread || !workerThreads.append(thread)) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!args.get(0).isObject()) { |
| JS_ReportError(cx, "shapeOf: object expected"); |
| return false; |
| } |
| JSObject* obj = &args[0].toObject(); |
| args.rval().set(JS_NumberValue(double(uintptr_t(obj->maybeShape()) >> 3))); |
| return true; |
| } |
| |
| /* |
| * 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 - t2) < 0; |
| } |
| |
| static bool |
| Sleep_fn(JSContext* cx, unsigned argc, Value* vp) |
| { |
| ShellRuntime* sr = GetShellRuntime(cx); |
| CallArgs args = CallArgsFromVp(argc, vp); |
| int64_t t_ticks; |
| |
| if (args.length() == 0) { |
| t_ticks = 0; |
| } else { |
|