summaryrefslogtreecommitdiffstats
path: root/js/src/shell
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/shell')
-rw-r--r--js/src/shell/Makefile.in16
-rw-r--r--js/src/shell/ModuleLoader.js43
-rw-r--r--js/src/shell/OSObject.cpp1029
-rw-r--r--js/src/shell/OSObject.h39
-rw-r--r--js/src/shell/js-gdb.py.in11
-rw-r--r--js/src/shell/js.cpp7997
-rw-r--r--js/src/shell/jsoptparse.cpp644
-rw-r--r--js/src/shell/jsoptparse.h298
-rw-r--r--js/src/shell/jsshell.cpp74
-rw-r--r--js/src/shell/jsshell.h84
-rw-r--r--js/src/shell/moz.build67
11 files changed, 10302 insertions, 0 deletions
diff --git a/js/src/shell/Makefile.in b/js/src/shell/Makefile.in
new file mode 100644
index 000000000..ebc58cfc7
--- /dev/null
+++ b/js/src/shell/Makefile.in
@@ -0,0 +1,16 @@
+# -*- Mode: makefile -*-
+#
+# 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/.
+
+ifdef QEMU_EXE
+MOZ_POST_PROGRAM_COMMAND = $(topsrcdir)/build/qemu-wrap --qemu $(QEMU_EXE) --libdir $(CROSS_LIB)
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+# Install versioned binary for parallel installability in Linux distributions
+install:: $(PROGRAM)
+ cp $^ $^$(MOZJS_MAJOR_VERSION)
+ $(SYSINSTALL) $^$(MOZJS_MAJOR_VERSION) $(DESTDIR)$(bindir)
diff --git a/js/src/shell/ModuleLoader.js b/js/src/shell/ModuleLoader.js
new file mode 100644
index 000000000..d8767f05f
--- /dev/null
+++ b/js/src/shell/ModuleLoader.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+// A basic synchronous module loader for testing the shell.
+
+Reflect.Loader = new class {
+ constructor() {
+ this.registry = new Map();
+ this.loadPath = getModuleLoadPath();
+ }
+
+ resolve(name) {
+ if (os.path.isAbsolute(name))
+ return name;
+
+ return os.path.join(this.loadPath, name);
+ }
+
+ fetch(path) {
+ return os.file.readFile(path);
+ }
+
+ loadAndParse(name) {
+ let path = this.resolve(name);
+
+ if (this.registry.has(path))
+ return this.registry.get(path);
+
+ let source = this.fetch(path);
+ let module = parseModule(source, path);
+ this.registry.set(path, module);
+ return module;
+ }
+
+ ["import"](name, referrer) {
+ let module = this.loadAndParse(name);
+ module.declarationInstantiation();
+ return module.evaluation();
+ }
+};
+
+setModuleResolveHook((module, requestName) => Reflect.Loader.loadAndParse(requestName));
diff --git a/js/src/shell/OSObject.cpp b/js/src/shell/OSObject.cpp
new file mode 100644
index 000000000..846ec7b15
--- /dev/null
+++ b/js/src/shell/OSObject.cpp
@@ -0,0 +1,1029 @@
+/* -*- 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/. */
+
+// OSObject.h - os object for exposing posix system calls in the JS shell
+
+#include "shell/OSObject.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#ifdef XP_WIN
+#include <direct.h>
+#include <process.h>
+#include <string.h>
+#else
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#include "jsapi.h"
+// For JSFunctionSpecWithHelp
+#include "jsfriendapi.h"
+#include "jsobj.h"
+#ifdef XP_WIN
+# include "jswin.h"
+#endif
+#include "jswrapper.h"
+
+#include "js/Conversions.h"
+#include "shell/jsshell.h"
+#include "vm/StringBuffer.h"
+#include "vm/TypedArrayObject.h"
+
+#include "jsobjinlines.h"
+
+#ifdef XP_WIN
+# define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
+# define getcwd _getcwd
+#else
+# include <libgen.h>
+#endif
+
+using js::shell::RCFile;
+
+static RCFile** gErrFilePtr = nullptr;
+static RCFile** gOutFilePtr = nullptr;
+
+namespace js {
+namespace shell {
+
+#ifdef XP_WIN
+const char PathSeparator = '\\';
+#else
+const char PathSeparator = '/';
+#endif
+
+static bool
+IsAbsolutePath(const JSAutoByteString& filename)
+{
+ const char* pathname = filename.ptr();
+
+ if (pathname[0] == PathSeparator)
+ return true;
+
+#ifdef XP_WIN
+ // On Windows there are various forms of absolute paths (see
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
+ // for details):
+ //
+ // "\..."
+ // "\\..."
+ // "C:\..."
+ //
+ // The first two cases are handled by the test above so we only need a test
+ // for the last one here.
+
+ if ((strlen(pathname) > 3 &&
+ isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\'))
+ {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+/*
+ * 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.
+ */
+JSString*
+ResolvePath(JSContext* cx, HandleString filenameStr, PathResolutionMode resolveMode)
+{
+ if (!filenameStr) {
+#ifdef XP_WIN
+ return JS_NewStringCopyZ(cx, "nul");
+#else
+ return JS_NewStringCopyZ(cx, "/dev/null");
+#endif
+ }
+
+ JSAutoByteString filename(cx, filenameStr);
+ if (!filename)
+ return nullptr;
+
+ if (IsAbsolutePath(filename))
+ return filenameStr;
+
+ /* Get the currently executing script's name. */
+ JS::AutoFilename scriptFilename;
+ if (!DescribeScriptedCaller(cx, &scriptFilename))
+ return nullptr;
+
+ if (!scriptFilename.get())
+ return nullptr;
+
+ if (strcmp(scriptFilename.get(), "-e") == 0 || strcmp(scriptFilename.get(), "typein") == 0)
+ resolveMode = RootRelative;
+
+ char buffer[PATH_MAX+1];
+ if (resolveMode == ScriptRelative) {
+#ifdef XP_WIN
+ // The docs say it can return EINVAL, but the compiler says it's void
+ _splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr);
+#else
+ strncpy(buffer, scriptFilename.get(), PATH_MAX+1);
+ if (buffer[PATH_MAX] != '\0')
+ return nullptr;
+
+ // dirname(buffer) might return buffer, or it might return a
+ // statically-allocated string
+ memmove(buffer, dirname(buffer), strlen(buffer) + 1);
+#endif
+ } else {
+ const char* cwd = getcwd(buffer, PATH_MAX);
+ if (!cwd)
+ return nullptr;
+ }
+
+ size_t len = strlen(buffer);
+ buffer[len] = '/';
+ strncpy(buffer + len + 1, filename.ptr(), sizeof(buffer) - (len+1));
+ if (buffer[PATH_MAX] != '\0')
+ return nullptr;
+
+ return JS_NewStringCopyZ(cx, buffer);
+}
+
+JSObject*
+FileAsTypedArray(JSContext* cx, JS::HandleString pathnameStr)
+{
+ JSAutoByteString pathname(cx, pathnameStr);
+ if (!pathname)
+ return nullptr;
+
+ FILE* file = fopen(pathname.ptr(), "rb");
+ if (!file) {
+ /*
+ * Use Latin1 variant here because the encoding of the return value of
+ * strerror function can be non-UTF-8.
+ */
+ JS_ReportErrorLatin1(cx, "can't open %s: %s", pathname.ptr(), strerror(errno));
+ return nullptr;
+ }
+ AutoCloseFile autoClose(file);
+
+ RootedObject obj(cx);
+ if (fseek(file, 0, SEEK_END) != 0) {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't seek end of %s", pathname.ptr());
+ } else {
+ size_t len = ftell(file);
+ if (fseek(file, 0, SEEK_SET) != 0) {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.ptr());
+ } else {
+ obj = JS_NewUint8Array(cx, len);
+ if (!obj)
+ return nullptr;
+ js::TypedArrayObject& ta = obj->as<js::TypedArrayObject>();
+ if (ta.isSharedMemory()) {
+ // Must opt in to use shared memory. For now, don't.
+ //
+ // (It is incorrect to read into the buffer without
+ // synchronization since that can create a race. A
+ // lock here won't fix it - both sides must
+ // participate. So what one must do is to create a
+ // temporary buffer, read into that, and use a
+ // race-safe primitive to copy memory into the
+ // buffer.)
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't read %s: shared memory buffer", pathname.ptr());
+ return nullptr;
+ }
+ char* buf = static_cast<char*>(ta.viewDataUnshared());
+ size_t cc = fread(buf, 1, len, file);
+ if (cc != len) {
+ if (ptrdiff_t(cc) < 0) {
+ /*
+ * Use Latin1 variant here because the encoding of the return
+ * value of strerror function can be non-UTF-8.
+ */
+ JS_ReportErrorLatin1(cx, "can't read %s: %s", pathname.ptr(), strerror(errno));
+ } else {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't read %s: short read", pathname.ptr());
+ }
+ obj = nullptr;
+ }
+ }
+ }
+ return obj;
+}
+
+static bool
+ReadFile(JSContext* cx, unsigned argc, Value* vp, bool scriptRelative)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1 || args.length() > 2) {
+ JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr,
+ args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
+ "snarf");
+ return false;
+ }
+
+ if (!args[0].isString() || (args.length() == 2 && !args[1].isString())) {
+ JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "snarf");
+ return false;
+ }
+
+ RootedString givenPath(cx, args[0].toString());
+ RootedString str(cx, js::shell::ResolvePath(cx, givenPath, scriptRelative ? ScriptRelative : RootRelative));
+ if (!str)
+ return false;
+
+ if (args.length() > 1) {
+ JSString* opt = JS::ToString(cx, args[1]);
+ if (!opt)
+ return false;
+ bool match;
+ if (!JS_StringEqualsAscii(cx, opt, "binary", &match))
+ return false;
+ if (match) {
+ JSObject* obj;
+ if (!(obj = FileAsTypedArray(cx, str)))
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+ }
+ }
+
+ if (!(str = FileAsString(cx, str)))
+ return false;
+ args.rval().setString(str);
+ return true;
+}
+
+static bool
+osfile_readFile(JSContext* cx, unsigned argc, Value* vp)
+{
+ return ReadFile(cx, argc, vp, false);
+}
+
+static bool
+osfile_readRelativeToScript(JSContext* cx, unsigned argc, Value* vp)
+{
+ return ReadFile(cx, argc, vp, true);
+}
+
+static bool
+osfile_writeTypedArrayToFile(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 2 ||
+ !args[0].isString() ||
+ !args[1].isObject() ||
+ !args[1].toObject().is<TypedArrayObject>())
+ {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "writeTypedArrayToFile");
+ return false;
+ }
+
+ RootedString givenPath(cx, args[0].toString());
+ RootedString str(cx, ResolvePath(cx, givenPath, RootRelative));
+ if (!str)
+ return false;
+
+ JSAutoByteString filename(cx, str);
+ if (!filename)
+ return false;
+
+ FILE* file = fopen(filename.ptr(), "wb");
+ if (!file) {
+ /*
+ * Use Latin1 variant here because the encoding of the return value of
+ * strerror function can be non-UTF-8.
+ */
+ JS_ReportErrorLatin1(cx, "can't open %s: %s", filename.ptr(), strerror(errno));
+ return false;
+ }
+ AutoCloseFile autoClose(file);
+
+ TypedArrayObject* obj = &args[1].toObject().as<TypedArrayObject>();
+
+ if (obj->isSharedMemory()) {
+ // Must opt in to use shared memory. For now, don't.
+ //
+ // See further comments in FileAsTypedArray, above.
+ filename.clear();
+ if (!filename.encodeUtf8(cx, str))
+ return false;
+ JS_ReportErrorUTF8(cx, "can't write %s: shared memory buffer", filename.ptr());
+ return false;
+ }
+ void* buf = obj->viewDataUnshared();
+ if (fwrite(buf, obj->bytesPerElement(), obj->length(), file) != obj->length() ||
+ !autoClose.release())
+ {
+ filename.clear();
+ if (!filename.encodeUtf8(cx, str))
+ return false;
+ JS_ReportErrorUTF8(cx, "can't write %s", filename.ptr());
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/* static */ RCFile*
+RCFile::create(JSContext* cx, const char* filename, const char* mode)
+{
+ FILE* fp = fopen(filename, mode);
+ if (!fp)
+ return nullptr;
+
+ RCFile* file = cx->new_<RCFile>(fp);
+ if (!file) {
+ fclose(fp);
+ return nullptr;
+ }
+
+ return file;
+}
+
+void
+RCFile::close()
+{
+ if (fp)
+ fclose(fp);
+ fp = nullptr;
+}
+
+bool
+RCFile::release()
+{
+ if (--numRefs)
+ return false;
+ this->close();
+ return true;
+}
+
+class FileObject : public JSObject {
+ enum : uint32_t {
+ FILE_SLOT = 0,
+ NUM_SLOTS
+ };
+
+ public:
+ static const js::Class class_;
+
+ static FileObject* create(JSContext* cx, RCFile* file) {
+ JSObject* obj = js::NewObjectWithClassProto(cx, &class_, nullptr);
+ if (!obj)
+ return nullptr;
+
+ FileObject* fileObj = &obj->as<FileObject>();
+ fileObj->setRCFile(file);
+ file->acquire();
+ return fileObj;
+ }
+
+ static void finalize(FreeOp* fop, JSObject* obj) {
+ FileObject* fileObj = &obj->as<FileObject>();
+ RCFile* file = fileObj->rcFile();
+ if (file->release()) {
+ fileObj->setRCFile(nullptr);
+ fop->delete_(file);
+ }
+ }
+
+ bool isOpen() {
+ RCFile* file = rcFile();
+ return file && file->isOpen();
+ }
+
+ void close() {
+ if (!isOpen())
+ return;
+ rcFile()->close();
+ }
+
+ RCFile* rcFile() {
+ return reinterpret_cast<RCFile*>(js::GetReservedSlot(this, FILE_SLOT).toPrivate());
+ }
+
+ private:
+
+ void setRCFile(RCFile* file) {
+ js::SetReservedSlot(this, FILE_SLOT, PrivateValue(file));
+ }
+};
+
+static const js::ClassOps FileObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ FileObject::finalize, /* finalize */
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ nullptr /* trace */
+};
+
+const js::Class FileObject::class_ = {
+ "File",
+ JSCLASS_HAS_RESERVED_SLOTS(FileObject::NUM_SLOTS) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &FileObjectClassOps
+};
+
+static FileObject*
+redirect(JSContext* cx, HandleString relFilename, RCFile** globalFile)
+{
+ RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative));
+ if (!filename)
+ return nullptr;
+ JSAutoByteString filenameABS(cx, filename);
+ if (!filenameABS)
+ return nullptr;
+ RCFile* file = RCFile::create(cx, filenameABS.ptr(), "wb");
+ if (!file) {
+ /*
+ * Use Latin1 variant here because the encoding of the return value of
+ * strerror function can be non-UTF-8.
+ */
+ JS_ReportErrorLatin1(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno));
+ return nullptr;
+ }
+
+ // Grant the global gOutFile ownership of the new file, release ownership
+ // of its old file, and return a FileObject owning the old file.
+ file->acquire(); // Global owner of new file
+
+ FileObject* fileObj = FileObject::create(cx, *globalFile); // Newly created owner of old file
+ if (!fileObj) {
+ file->release();
+ return nullptr;
+ }
+
+ (*globalFile)->release(); // Release (global) ownership of old file.
+ *globalFile = file;
+
+ return fileObj;
+}
+
+static bool
+Redirect(JSContext* cx, const CallArgs& args, RCFile** outFile)
+{
+ if (args.length() > 1) {
+ JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "redirect");
+ return false;
+ }
+
+ RCFile* oldFile = *outFile;
+ RootedObject oldFileObj(cx, FileObject::create(cx, oldFile));
+ if (!oldFileObj)
+ return false;
+
+ if (args.get(0).isUndefined()) {
+ args.rval().setObject(*oldFileObj);
+ return true;
+ }
+
+ if (args[0].isObject()) {
+ RootedObject fileObj(cx, js::CheckedUnwrap(&args[0].toObject()));
+ if (!fileObj)
+ return false;
+
+ if (fileObj->is<FileObject>()) {
+ // Passed in a FileObject. Create a FileObject for the previous
+ // global file, and set the global file to the passed-in one.
+ *outFile = fileObj->as<FileObject>().rcFile();
+ (*outFile)->acquire();
+ oldFile->release();
+
+ args.rval().setObject(*oldFileObj);
+ return true;
+ }
+ }
+
+ RootedString filename(cx);
+ if (!args[0].isNull()) {
+ filename = JS::ToString(cx, args[0]);
+ if (!filename)
+ return false;
+ }
+
+ if (!redirect(cx, filename, outFile))
+ return false;
+
+ args.rval().setObject(*oldFileObj);
+ return true;
+}
+
+static bool
+osfile_redirectOutput(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return Redirect(cx, args, gOutFilePtr);
+}
+
+static bool
+osfile_redirectError(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return Redirect(cx, args, gErrFilePtr);
+}
+
+static bool
+osfile_close(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<FileObject*> fileObj(cx);
+ if (args.get(0).isObject()) {
+ JSObject *obj = js::CheckedUnwrap(&args[0].toObject());
+ if (obj->is<FileObject>())
+ fileObj = &obj->as<FileObject>();
+ }
+
+ if (!fileObj) {
+ JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "close");
+ return false;
+ }
+
+ fileObj->close();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static const JSFunctionSpecWithHelp osfile_functions[] = {
+ JS_FN_HELP("readFile", osfile_readFile, 1, 0,
+"readFile(filename, [\"binary\"])",
+" Read filename into returned string. Filename is relative to the current\n"
+ " working directory."),
+
+ JS_FN_HELP("readRelativeToScript", osfile_readRelativeToScript, 1, 0,
+"readRelativeToScript(filename, [\"binary\"])",
+" Read filename into returned string. Filename is relative to the directory\n"
+" containing the current script."),
+
+ JS_FS_HELP_END
+};
+
+static const JSFunctionSpecWithHelp osfile_unsafe_functions[] = {
+ JS_FN_HELP("writeTypedArrayToFile", osfile_writeTypedArrayToFile, 2, 0,
+"writeTypedArrayToFile(filename, data)",
+" Write the contents of a typed array to the named file."),
+
+ JS_FN_HELP("redirect", osfile_redirectOutput, 1, 0,
+"redirect([path-or-object])",
+" Redirect print() output to the named file.\n"
+" Return an opaque object representing the previous destination, which\n"
+" may be passed into redirect() later to restore the output."),
+
+ JS_FN_HELP("redirectErr", osfile_redirectError, 1, 0,
+"redirectErr([path-or-object])",
+" Same as redirect(), but for printErr"),
+
+ JS_FN_HELP("close", osfile_close, 1, 0,
+"close(object)",
+" Close the file returned by an earlier redirect call."),
+
+ JS_FS_HELP_END
+};
+
+static bool
+ospath_isAbsolute(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "isAbsolute");
+ return false;
+ }
+
+ JSAutoByteString path(cx, args[0].toString());
+ if (!path)
+ return false;
+
+ args.rval().setBoolean(IsAbsolutePath(path));
+ return true;
+}
+
+static bool
+ospath_join(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "join");
+ return false;
+ }
+
+ // This function doesn't take into account some aspects of Windows paths,
+ // e.g. the drive letter is always reset when an absolute path is appended.
+
+ StringBuffer buffer(cx);
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ if (!args[i].isString()) {
+ JS_ReportErrorASCII(cx, "join expects string arguments only");
+ return false;
+ }
+
+ JSAutoByteString path(cx, args[i].toString());
+ if (!path)
+ return false;
+
+ if (IsAbsolutePath(path)) {
+ MOZ_ALWAYS_TRUE(buffer.resize(0));
+ } else if (i != 0) {
+ if (!buffer.append(PathSeparator))
+ return false;
+ }
+
+ if (!buffer.append(args[i].toString()))
+ return false;
+ }
+
+ JSString* result = buffer.finishString();
+ if (!result)
+ return false;
+
+ args.rval().setString(result);
+ return true;
+}
+
+static const JSFunctionSpecWithHelp ospath_functions[] = {
+ JS_FN_HELP("isAbsolute", ospath_isAbsolute, 1, 0,
+"isAbsolute(path)",
+" Return whether the given path is absolute."),
+
+ JS_FN_HELP("join", ospath_join, 1, 0,
+"join(paths...)",
+" Join one or more path components in a platform independent way."),
+
+ JS_FS_HELP_END
+};
+
+static bool
+os_getenv(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "os.getenv requires 1 argument");
+ return false;
+ }
+ RootedString key(cx, ToString(cx, args[0]));
+ if (!key)
+ return false;
+ JSAutoByteString keyBytes;
+ if (!keyBytes.encodeUtf8(cx, key))
+ return false;
+
+ if (const char* valueBytes = getenv(keyBytes.ptr())) {
+ RootedString value(cx, JS_NewStringCopyZ(cx, valueBytes));
+ if (!value)
+ return false;
+ args.rval().setString(value);
+ } else {
+ args.rval().setUndefined();
+ }
+ return true;
+}
+
+static bool
+os_getpid(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 0) {
+ JS_ReportErrorASCII(cx, "os.getpid takes no arguments");
+ return false;
+ }
+ args.rval().setInt32(getpid());
+ return true;
+}
+
+#if !defined(XP_WIN)
+
+// There are two possible definitions of strerror_r floating around. The GNU
+// one returns a char* which may or may not be the buffer you passed in. The
+// other one returns an integer status code, and always writes the result into
+// the provided buffer.
+
+inline char*
+strerror_message(int result, char* buffer)
+{
+ return result == 0 ? buffer : nullptr;
+}
+
+inline char*
+strerror_message(char* result, char* buffer)
+{
+ return result;
+}
+
+#endif
+
+static void
+ReportSysError(JSContext* cx, const char* prefix)
+{
+ char buffer[200];
+
+#if defined(XP_WIN)
+ strerror_s(buffer, sizeof(buffer), errno);
+ const char* errstr = buffer;
+#else
+ const char* errstr = strerror_message(strerror_r(errno, buffer, sizeof(buffer)), buffer);
+#endif
+
+ if (!errstr)
+ errstr = "unknown error";
+
+ size_t nbytes = strlen(prefix) + strlen(errstr) + 3;
+ char* final = (char*) js_malloc(nbytes);
+ if (!final) {
+ JS_ReportOutOfMemory(cx);
+ return;
+ }
+
+ snprintf(final, nbytes, "%s: %s", prefix, errstr);
+ /*
+ * Use Latin1 variant here because the encoding of the return value of
+ * strerror_s and strerror_r function can be non-UTF-8.
+ */
+ JS_ReportErrorLatin1(cx, "%s", final);
+ js_free(final);
+}
+
+static bool
+os_system(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() == 0) {
+ JS_ReportErrorASCII(cx, "os.system requires 1 argument");
+ return false;
+ }
+
+ JSString* str = JS::ToString(cx, args[0]);
+ if (!str)
+ return false;
+
+ JSAutoByteString command(cx, str);
+ if (!command)
+ return false;
+
+ int result = system(command.ptr());
+ if (result == -1) {
+ ReportSysError(cx, "system call failed");
+ return false;
+ }
+
+ args.rval().setInt32(result);
+ return true;
+}
+
+#ifndef XP_WIN
+static bool
+os_spawn(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() == 0) {
+ JS_ReportErrorASCII(cx, "os.spawn requires 1 argument");
+ return false;
+ }
+
+ JSString* str = JS::ToString(cx, args[0]);
+ if (!str)
+ return false;
+
+ JSAutoByteString command(cx, str);
+ if (!command)
+ return false;
+
+ int32_t childPid = fork();
+ if (childPid == -1) {
+ ReportSysError(cx, "fork failed");
+ return false;
+ }
+
+ if (childPid) {
+ args.rval().setInt32(childPid);
+ return true;
+ }
+
+ // We are in the child
+
+ const char* cmd[] = {"sh", "-c", nullptr, nullptr};
+ cmd[2] = command.ptr();
+
+ execvp("sh", (char * const*)cmd);
+ exit(1);
+}
+
+static bool
+os_kill(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ int32_t pid;
+ if (args.length() < 1) {
+ JS_ReportErrorASCII(cx, "os.kill requires 1 argument");
+ return false;
+ }
+ if (!JS::ToInt32(cx, args[0], &pid))
+ return false;
+
+ // It is too easy to kill yourself accidentally with os.kill("goose").
+ if (pid == 0 && !args[0].isInt32()) {
+ JS_ReportErrorASCII(cx, "os.kill requires numeric pid");
+ return false;
+ }
+
+ int signal = SIGINT;
+ if (args.length() > 1) {
+ if (!JS::ToInt32(cx, args[1], &signal))
+ return false;
+ }
+
+ int status = kill(pid, signal);
+ if (status == -1) {
+ ReportSysError(cx, "kill failed");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+os_waitpid(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ int32_t pid;
+ if (args.length() == 0) {
+ pid = -1;
+ } else {
+ if (!JS::ToInt32(cx, args[0], &pid))
+ return false;
+ }
+
+ bool nohang = false;
+ if (args.length() >= 2)
+ nohang = JS::ToBoolean(args[1]);
+
+ int status = 0;
+ pid_t result = waitpid(pid, &status, nohang ? WNOHANG : 0);
+ if (result == -1) {
+ ReportSysError(cx, "os.waitpid failed");
+ return false;
+ }
+
+ RootedObject info(cx, JS_NewPlainObject(cx));
+ if (!info)
+ return false;
+
+ RootedValue v(cx);
+ if (result != 0) {
+ v.setInt32(result);
+ if (!JS_DefineProperty(cx, info, "pid", v, JSPROP_ENUMERATE))
+ return false;
+ if (WIFEXITED(status)) {
+ v.setInt32(WEXITSTATUS(status));
+ if (!JS_DefineProperty(cx, info, "exitStatus", v, JSPROP_ENUMERATE))
+ return false;
+ }
+ }
+
+ args.rval().setObject(*info);
+ return true;
+}
+#endif
+
+static const JSFunctionSpecWithHelp os_functions[] = {
+ JS_FN_HELP("getenv", os_getenv, 1, 0,
+"getenv(variable)",
+" Get the value of an environment variable."),
+
+ JS_FN_HELP("getpid", os_getpid, 0, 0,
+"getpid()",
+" Return the current process id."),
+
+ JS_FN_HELP("system", os_system, 1, 0,
+"system(command)",
+" Execute command on the current host, returning result code or throwing an\n"
+" exception on failure."),
+
+#ifndef XP_WIN
+ JS_FN_HELP("spawn", os_spawn, 1, 0,
+"spawn(command)",
+" Start up a separate process running the given command. Returns the pid."),
+
+ JS_FN_HELP("kill", os_kill, 1, 0,
+"kill(pid[, signal])",
+" Send a signal to the given pid. The default signal is SIGINT. The signal\n"
+" passed in must be numeric, if given."),
+
+ JS_FN_HELP("waitpid", os_waitpid, 1, 0,
+"waitpid(pid[, nohang])",
+" Calls waitpid(). 'nohang' is a boolean indicating whether to pass WNOHANG.\n"
+" The return value is an object containing a 'pid' field, if a process was waitable\n"
+" and an 'exitStatus' field if a pid exited."),
+#endif
+
+ JS_FS_HELP_END
+};
+
+bool
+DefineOS(JSContext* cx, HandleObject global,
+ bool fuzzingSafe,
+ RCFile** shellOut, RCFile** shellErr)
+{
+ RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj || !JS_DefineProperty(cx, global, "os", obj, 0))
+ return false;
+
+ if (!fuzzingSafe) {
+ if (!JS_DefineFunctionsWithHelp(cx, obj, os_functions))
+ return false;
+ }
+
+ RootedObject osfile(cx, JS_NewPlainObject(cx));
+ if (!osfile ||
+ !JS_DefineFunctionsWithHelp(cx, osfile, osfile_functions) ||
+ !JS_DefineProperty(cx, obj, "file", osfile, 0))
+ {
+ return false;
+ }
+
+ if (!fuzzingSafe) {
+ if (!JS_DefineFunctionsWithHelp(cx, osfile, osfile_unsafe_functions))
+ return false;
+ }
+
+ if (!GenerateInterfaceHelp(cx, osfile, "os.file"))
+ return false;
+
+ RootedObject ospath(cx, JS_NewPlainObject(cx));
+ if (!ospath ||
+ !JS_DefineFunctionsWithHelp(cx, ospath, ospath_functions) ||
+ !JS_DefineProperty(cx, obj, "path", ospath, 0) ||
+ !GenerateInterfaceHelp(cx, ospath, "os.path"))
+ {
+ return false;
+ }
+
+ if (!GenerateInterfaceHelp(cx, obj, "os"))
+ return false;
+
+ gOutFilePtr = shellOut;
+ gErrFilePtr = shellErr;
+
+ // For backwards compatibility, expose various os.file.* functions as
+ // direct methods on the global.
+ RootedValue val(cx);
+
+ struct {
+ const char* src;
+ const char* dst;
+ } osfile_exports[] = {
+ { "readFile", "read" },
+ { "readFile", "snarf" },
+ { "readRelativeToScript", "readRelativeToScript" },
+ { "redirect", "redirect" },
+ { "redirectErr", "redirectErr" }
+ };
+
+ for (auto pair : osfile_exports) {
+ if (!JS_GetProperty(cx, osfile, pair.src, &val))
+ return false;
+ if (val.isObject()) {
+ RootedObject function(cx, &val.toObject());
+ if (!JS_DefineProperty(cx, global, pair.dst, function, 0))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace shell
+} // namespace js
diff --git a/js/src/shell/OSObject.h b/js/src/shell/OSObject.h
new file mode 100644
index 000000000..6df7005da
--- /dev/null
+++ b/js/src/shell/OSObject.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+// OSObject.h - os object for exposing posix system calls in the JS shell
+
+#ifndef shell_OSObject_h
+#define shell_OSObject_h
+
+#include "jsapi.h"
+
+namespace js {
+namespace shell {
+
+struct RCFile;
+
+/* Define an os object on the given global object. */
+bool
+DefineOS(JSContext* cx, JS::HandleObject global,
+ bool fuzzingSafe,
+ RCFile** shellOut, RCFile** shellErr);
+
+enum PathResolutionMode {
+ RootRelative,
+ ScriptRelative
+};
+
+JSString*
+ResolvePath(JSContext* cx, JS::HandleString filenameStr, PathResolutionMode resolveMode);
+
+JSObject*
+FileAsTypedArray(JSContext* cx, JS::HandleString pathnameStr);
+
+} // namespace shell
+} // namespace js
+
+#endif /* shell_OSObject_h */
diff --git a/js/src/shell/js-gdb.py.in b/js/src/shell/js-gdb.py.in
new file mode 100644
index 000000000..16199bb4c
--- /dev/null
+++ b/js/src/shell/js-gdb.py.in
@@ -0,0 +1,11 @@
+""" GDB Python customization auto-loader for js shell """
+#filter substitution
+
+import os.path
+sys.path[0:0] = [os.path.join('@topsrcdir@', 'gdb')]
+
+import mozilla.autoload
+mozilla.autoload.register(gdb.current_objfile())
+
+import mozilla.asmjs
+mozilla.asmjs.install()
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
new file mode 100644
index 000000000..00a4a503c
--- /dev/null
+++ b/js/src/shell/js.cpp
@@ -0,0 +1,7997 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/SizePrintfMacros.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/TimeStamp.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 "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/jsshell.h"
+#include "shell/OSObject.h"
+#include "threading/ConditionVariable.h"
+#include "threading/LockGuard.h"
+#include "threading/Thread.h"
+#include "vm/ArgumentsObject.h"
+#include "vm/AsyncFunction.h"
+#include "vm/Compression.h"
+#include "vm/Debugger.h"
+#include "vm/HelperThreads.h"
+#include "vm/Monitor.h"
+#include "vm/MutexIDs.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 "wasm/WasmJS.h"
+
+#include "jscompartmentinlines.h"
+#include "jsobjinlines.h"
+
+#include "vm/ErrorObject-inl.h"
+#include "vm/Interpreter-inl.h"
+#include "vm/Stack-inl.h"
+
+using namespace js;
+using namespace js::cli;
+using namespace js::shell;
+
+using mozilla::ArrayLength;
+using mozilla::Atomic;
+using mozilla::MakeScopeExit;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::NumberEqualsInt32;
+using mozilla::PodCopy;
+using mozilla::PodEqual;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+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/XPCJSContext.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_SECONDS = 1800.0;
+
+// SharedArrayBuffer and Atomics settings track Firefox. Choose a custom setting
+// with --shared-memory={on,off}.
+#ifndef RELEASE_OR_BETA
+# define SHARED_MEMORY_DEFAULT 1
+#else
+# define SHARED_MEMORY_DEFAULT 0
+#endif
+
+#ifdef SPIDERMONKEY_PROMISE
+using JobQueue = GCVector<JSObject*, 0, SystemAllocPolicy>;
+
+struct ShellAsyncTasks
+{
+ explicit ShellAsyncTasks(JSContext* cx)
+ : outstanding(0),
+ finished(cx)
+ {}
+
+ size_t outstanding;
+ Vector<JS::AsyncTask*> finished;
+};
+#endif // SPIDERMONKEY_PROMISE
+
+enum class ScriptKind
+{
+ Script,
+ Module
+};
+
+class OffThreadState {
+ enum State {
+ IDLE, /* ready to work; no token, no source */
+ COMPILING, /* working; no token, have source */
+ DONE /* compilation done: have token and source */
+ };
+
+ public:
+ OffThreadState()
+ : monitor(mutexid::ShellOffThreadState),
+ state(IDLE),
+ token(),
+ source(nullptr)
+ { }
+
+ bool startIfIdle(JSContext* cx, ScriptKind kind,
+ ScopedJSFreePtr<char16_t>& newSource)
+ {
+ AutoLockMonitor alm(monitor);
+ if (state != IDLE)
+ return false;
+
+ MOZ_ASSERT(!token);
+
+ source = newSource.forget();
+
+ scriptKind = kind;
+ state = COMPILING;
+ return true;
+ }
+
+ void abandon(JSContext* cx) {
+ AutoLockMonitor alm(monitor);
+ MOZ_ASSERT(state == COMPILING);
+ MOZ_ASSERT(!token);
+ MOZ_ASSERT(source);
+
+ js_free(source);
+ source = nullptr;
+
+ state = IDLE;
+ }
+
+ void markDone(void* newToken) {
+ AutoLockMonitor alm(monitor);
+ MOZ_ASSERT(state == COMPILING);
+ MOZ_ASSERT(!token);
+ MOZ_ASSERT(source);
+ MOZ_ASSERT(newToken);
+
+ token = newToken;
+ state = DONE;
+ alm.notify();
+ }
+
+ void* waitUntilDone(JSContext* cx, ScriptKind kind) {
+ AutoLockMonitor alm(monitor);
+ if (state == IDLE || scriptKind != kind)
+ return nullptr;
+
+ if (state == COMPILING) {
+ while (state != DONE)
+ alm.wait();
+ }
+
+ MOZ_ASSERT(source);
+ js_free(source);
+ source = nullptr;
+
+ MOZ_ASSERT(token);
+ void* holdToken = token;
+ token = nullptr;
+ state = IDLE;
+ return holdToken;
+ }
+
+ private:
+ Monitor monitor;
+ ScriptKind scriptKind;
+ State state;
+ void* token;
+ char16_t* source;
+};
+
+// Per-context shell state.
+struct ShellContext
+{
+ explicit ShellContext(JSContext* cx);
+
+ bool isWorker;
+ double timeoutInterval;
+ double startTime;
+ Atomic<bool> serviceInterrupt;
+ Atomic<bool> haveInterruptFunc;
+ JS::PersistentRootedValue interruptFunc;
+ bool lastWarningEnabled;
+ JS::PersistentRootedValue lastWarning;
+#ifdef SPIDERMONKEY_PROMISE
+ JS::PersistentRootedValue promiseRejectionTrackerCallback;
+ JS::PersistentRooted<JobQueue> jobQueue;
+ ExclusiveData<ShellAsyncTasks> asyncTasks;
+ bool drainingJobQueue;
+#endif // SPIDERMONKEY_PROMISE
+
+ /*
+ * Watchdog thread state.
+ */
+ Mutex watchdogLock;
+ ConditionVariable watchdogWakeup;
+ Maybe<Thread> watchdogThread;
+ Maybe<TimeStamp> watchdogTimeout;
+
+ ConditionVariable sleepWakeup;
+
+ int exitCode;
+ bool quitting;
+
+ UniqueChars readLineBuf;
+ size_t readLineBufPos;
+
+ static const uint32_t SpsProfilingMaxStackSize = 1000;
+ ProfileEntry spsProfilingStack[SpsProfilingMaxStackSize];
+ uint32_t spsProfilingStackSize;
+
+ OffThreadState offThreadState;
+};
+
+struct MOZ_STACK_CLASS EnvironmentPreparer : public js::ScriptEnvironmentPreparer {
+ JSContext* cx;
+ explicit EnvironmentPreparer(JSContext* cx)
+ : cx(cx)
+ {
+ js::SetScriptEnvironmentPreparer(cx, this);
+ }
+ void invoke(JS::HandleObject scope, Closure& closure) override;
+};
+
+// 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 enableWasm = false;
+static bool enableNativeRegExp = false;
+static bool enableUnboxedArrays = false;
+static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
+static bool enableWasmAlwaysBaseline = false;
+#ifdef JS_GC_ZEAL
+static uint32_t gZealBits = 0;
+static uint32_t gZealFrequency = 0;
+#endif
+static bool printTiming = false;
+static const char* jsCacheDir = nullptr;
+static const char* jsCacheAsmJSPath = nullptr;
+static RCFile* gErrFile = nullptr;
+static RCFile* 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 void
+KillWatchdog(JSContext* cx);
+
+static bool
+ScheduleWatchdog(JSContext* cx, double t);
+
+static void
+CancelExecution(JSContext* cx);
+
+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 {
+ // The shell doesn't have a read principals hook, so it doesn't really
+ // matter what we write here, but we have to write something so the
+ // fuzzer is happy.
+ return JS_WriteUint32Pair(writer, bits, 0);
+ }
+
+ 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
+
+ShellContext::ShellContext(JSContext* cx)
+ : isWorker(false),
+ timeoutInterval(-1.0),
+ startTime(PRMJ_Now()),
+ serviceInterrupt(false),
+ haveInterruptFunc(false),
+ interruptFunc(cx, NullValue()),
+ lastWarningEnabled(false),
+ lastWarning(cx, NullValue()),
+#ifdef SPIDERMONKEY_PROMISE
+ promiseRejectionTrackerCallback(cx, NullValue()),
+ asyncTasks(mutexid::ShellAsyncTasks, cx),
+ drainingJobQueue(false),
+#endif // SPIDERMONKEY_PROMISE
+ watchdogLock(mutexid::ShellContextWatchdog),
+ exitCode(0),
+ quitting(false),
+ readLineBufPos(0),
+ spsProfilingStackSize(0)
+{}
+
+static ShellContext*
+GetShellContext(JSContext* cx)
+{
+ ShellContext* sc = static_cast<ShellContext*>(JS_GetContextPrivate(cx));
+ MOZ_ASSERT(sc);
+ return sc;
+}
+
+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' && gOutFile->isOpen()) {
+ fprintf(gOutFile->fp, "%s", prompt);
+ fflush(gOutFile->fp);
+ }
+
+ size_t size = 80;
+ char* buffer = static_cast<char*>(malloc(size));
+ if (!buffer)
+ return nullptr;
+
+ char* current = buffer;
+ do {
+ 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;
+ } while (true);
+ return nullptr;
+}
+
+static bool
+ShellInterruptCallback(JSContext* cx)
+{
+ ShellContext* sc = GetShellContext(cx);
+ if (!sc->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.
+ sc->serviceInterrupt = false;
+
+ bool result;
+ if (sc->haveInterruptFunc) {
+ bool wasAlreadyThrowing = cx->isExceptionPending();
+ JS::AutoSaveExceptionState savedExc(cx);
+ JSAutoCompartment ac(cx, &sc->interruptFunc.toObject());
+ RootedValue rval(cx);
+
+ // Report any exceptions thrown by the JS interrupt callback, but do
+ // *not* keep it on the cx. The interrupt handler is invoked at points
+ // that are not expected to throw catchable exceptions, like at
+ // JSOP_RETRVAL.
+ //
+ // If the interrupted JS code was already throwing, any exceptions
+ // thrown by the interrupt handler are silently swallowed.
+ {
+ Maybe<AutoReportException> are;
+ if (!wasAlreadyThrowing)
+ are.emplace(cx);
+ result = JS_CallFunctionValue(cx, nullptr, sc->interruptFunc,
+ JS::HandleValueArray::empty(), &rval);
+ }
+ savedExc.restore();
+
+ if (rval.isBoolean())
+ result = rval.toBoolean();
+ else
+ result = false;
+ } else {
+ result = false;
+ }
+
+ if (!result && sc->exitCode == 0)
+ sc->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);
+}
+
+void
+EnvironmentPreparer::invoke(HandleObject scope, Closure& closure)
+{
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+
+ AutoCompartment ac(cx, scope);
+ AutoReportException are(cx);
+ if (!closure(cx))
+ return;
+}
+
+static MOZ_MUST_USE bool
+RunFile(JSContext* cx, 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);
+
+ 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);
+
+ if (!JS::Compile(cx, options, file, &script))
+ return false;
+ MOZ_ASSERT(script);
+ }
+
+ #ifdef DEBUG
+ if (dumpEntrainedVariables)
+ AnalyzeEntrainedVariables(cx, script);
+ #endif
+ if (!compileOnly) {
+ if (!JS_ExecuteScript(cx, script))
+ return false;
+ int64_t t2 = PRMJ_Now() - t1;
+ if (printTiming)
+ printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
+ }
+ return true;
+}
+
+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 MOZ_MUST_USE bool
+RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
+{
+ // Execute a module by calling |Reflect.Loader.import(filename)|.
+
+ RootedObject loaderObj(cx);
+ MOZ_ALWAYS_TRUE(GetLoaderObject(cx, &loaderObj));
+
+ RootedFunction importFun(cx);
+ MOZ_ALWAYS_TRUE(GetImportMethod(cx, loaderObj, &importFun));
+
+ JS::AutoValueArray<2> args(cx);
+ args[0].setString(JS_NewStringCopyZ(cx, filename));
+ args[1].setUndefined();
+
+ RootedValue value(cx);
+ return JS_CallFunction(cx, loaderObj, importFun, args, &value);
+}
+
+#ifdef SPIDERMONKEY_PROMISE
+static JSObject*
+ShellGetIncumbentGlobalCallback(JSContext* cx)
+{
+ return JS::CurrentGlobalOrNull(cx);
+}
+
+static bool
+ShellEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, JS::HandleObject allocationSite,
+ JS::HandleObject incumbentGlobal, void* data)
+{
+ ShellContext* sc = GetShellContext(cx);
+ MOZ_ASSERT(job);
+ return sc->jobQueue.append(job);
+}
+
+static bool
+ShellStartAsyncTaskCallback(JSContext* cx, JS::AsyncTask* task)
+{
+ ShellContext* sc = GetShellContext(cx);
+ task->user = sc;
+
+ ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
+ asyncTasks->outstanding++;
+ return true;
+}
+
+static bool
+ShellFinishAsyncTaskCallback(JS::AsyncTask* task)
+{
+ ShellContext* sc = (ShellContext*)task->user;
+
+ ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
+ MOZ_ASSERT(asyncTasks->outstanding > 0);
+ asyncTasks->outstanding--;
+ return asyncTasks->finished.append(task);
+}
+#endif // SPIDERMONKEY_PROMISE
+
+static bool
+DrainJobQueue(JSContext* cx)
+{
+#ifdef SPIDERMONKEY_PROMISE
+ ShellContext* sc = GetShellContext(cx);
+ if (sc->quitting || sc->drainingJobQueue)
+ return true;
+
+ // Wait for any outstanding async tasks to finish so that the
+ // finishedAsyncTasks list is fixed.
+ while (true) {
+ AutoLockHelperThreadState lock;
+ if (!sc->asyncTasks.lock()->outstanding)
+ break;
+ HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
+ }
+
+ // Lock the whole time while copying back the asyncTasks finished queue so
+ // that any new tasks created during finish() cannot racily join the job
+ // queue. Call finish() only thereafter, to avoid a circular mutex
+ // dependency (see also bug 1297901).
+ Vector<JS::AsyncTask*> finished(cx);
+ {
+ ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
+ finished = Move(asyncTasks->finished);
+ asyncTasks->finished.clear();
+ }
+
+ for (JS::AsyncTask* task : finished)
+ task->finish(cx);
+
+ // It doesn't make sense for job queue draining to be reentrant. At the
+ // same time we don't want to assert against it, because that'd make
+ // drainJobQueue unsafe for fuzzers. We do want fuzzers to test this, so
+ // we simply ignore nested calls of drainJobQueue.
+ sc->drainingJobQueue = true;
+
+ RootedObject job(cx);
+ JS::HandleValueArray args(JS::HandleValueArray::empty());
+ RootedValue rval(cx);
+ // Execute jobs in a loop until we've reached the end of the queue.
+ // Since executing a job can trigger enqueuing of additional jobs,
+ // it's crucial to re-check the queue length during each iteration.
+ for (size_t i = 0; i < sc->jobQueue.length(); i++) {
+ job = sc->jobQueue[i];
+ AutoCompartment ac(cx, job);
+ {
+ AutoReportException are(cx);
+ JS::Call(cx, UndefinedHandleValue, job, args, &rval);
+ }
+ sc->jobQueue[i].set(nullptr);
+ }
+ sc->jobQueue.clear();
+ sc->drainingJobQueue = false;
+#endif // SPIDERMONKEY_PROMISE
+ return true;
+}
+
+static bool
+DrainJobQueue(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!DrainJobQueue(cx))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef SPIDERMONKEY_PROMISE
+static void
+ForwardingPromiseRejectionTrackerCallback(JSContext* cx, JS::HandleObject promise,
+ PromiseRejectionHandlingState state, void* data)
+{
+ RootedValue callback(cx, GetShellContext(cx)->promiseRejectionTrackerCallback);
+ if (callback.isNull()) {
+ return;
+ }
+
+ AutoCompartment ac(cx, &callback.toObject());
+
+ FixedInvokeArgs<2> args(cx);
+ args[0].setObject(*promise);
+ args[1].setInt32(static_cast<int32_t>(state));
+
+ if (!JS_WrapValue(cx, args[0]))
+ return;
+
+ RootedValue rval(cx);
+ if (!Call(cx, callback, UndefinedHandleValue, args, &rval))
+ JS_ClearPendingException(cx);
+}
+#endif // SPIDERMONKEY_PROMISE
+
+static bool
+SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+#ifdef SPIDERMONKEY_PROMISE
+ if (!IsCallable(args.get(0))) {
+ JS_ReportErrorASCII(cx,
+ "setPromiseRejectionTrackerCallback expects a function as its sole "
+ "argument");
+ return false;
+ }
+
+ GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];
+ JS::SetPromiseRejectionTrackerCallback(cx, ForwardingPromiseRejectionTrackerCallback);
+
+#endif // SPIDERMONKEY_PROMISE
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef ENABLE_INTL_API
+static bool
+AddIntlExtras(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
+ return false;
+ }
+ JS::RootedObject intl(cx, &args[0].toObject());
+
+ static const JSFunctionSpec funcs[] = {
+ JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
+ JS_FS_END
+ };
+
+ if (!JS_DefineFunctions(cx, intl, funcs))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif // ENABLE_INTL_API
+
+static bool
+EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
+ int lineno, bool compileOnly)
+{
+ // 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() && gOutFile->isOpen()) {
+ // Print.
+ RootedString str(cx);
+ str = JS_ValueToSource(cx, result);
+ if (!str)
+ return false;
+
+ char* utf8chars = JS_EncodeStringToUTF8(cx, str);
+ if (!utf8chars)
+ return false;
+ fprintf(gOutFile->fp, "%s\n", utf8chars);
+ JS_free(cx, utf8chars);
+ }
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
+{
+ ShellContext* sc = GetShellContext(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;
+ RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
+ CharBuffer buffer(cx);
+ do {
+ ScheduleWatchdog(cx, -1);
+ sc->serviceInterrupt = false;
+ errno = 0;
+
+ char* line = GetLine(in, startline == lineno ? "js> " : "");
+ if (!line) {
+ if (errno) {
+ /*
+ * Use Latin1 variant here because strerror(errno)'s
+ * encoding depends on the user's C locale.
+ */
+ JS_ReportErrorLatin1(cx, "%s", strerror(errno));
+ return false;
+ }
+ hitEOF = true;
+ break;
+ }
+
+ if (!buffer.append(line, strlen(line)) || !buffer.append('\n'))
+ return false;
+
+ lineno++;
+ if (!ScheduleWatchdog(cx, sc->timeoutInterval)) {
+ hitEOF = true;
+ break;
+ }
+ } while (!JS_BufferIsCompilableUnit(cx, cx->global(), buffer.begin(), buffer.length()));
+
+ if (hitEOF && buffer.empty())
+ break;
+
+ {
+ // Report exceptions but keep going.
+ AutoReportException are(cx);
+ (void) EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly);
+ }
+
+ // If a let or const fail to initialize they will remain in an unusable
+ // without further intervention. This call cleans up the global scope,
+ // setting uninitialized lexicals to undefined so that they may still
+ // be used. This behavior is _only_ acceptable in the context of the repl.
+ if (JS::ForceLexicalInitialization(cx, globalLexical) && gErrFile->isOpen()) {
+ fputs("Warning: According to the standard, after the above exception,\n"
+ "Warning: the global bindings should be permanently uninitialized.\n"
+ "Warning: We have non-standard-ly initialized them to `undefined`"
+ "for you.\nWarning: This nicety only happens in the JS shell.\n",
+ stderr);
+ }
+
+ DrainJobQueue(cx);
+
+ } while (!hitEOF && !sc->quitting);
+
+ if (gOutFile->isOpen())
+ fprintf(gOutFile->fp, "\n");
+
+ return true;
+}
+
+enum FileKind
+{
+ FileScript,
+ FileModule
+};
+
+static void
+ReportCantOpenErrorUnknownEncoding(JSContext* cx, const char* filename)
+{
+ /*
+ * Filenames are in some random system encoding. *Probably* it's UTF-8,
+ * but no guarantees.
+ *
+ * strerror(errno)'s encoding, in contrast, depends on the user's C locale.
+ *
+ * Latin-1 is possibly wrong for both of these -- but at least if it's
+ * wrong it'll produce mojibake *safely*. Run with Latin-1 til someone
+ * complains.
+ */
+ JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN,
+ filename, strerror(errno));
+}
+
+static MOZ_MUST_USE bool
+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) {
+ ReportCantOpenErrorUnknownEncoding(cx, filename);
+ return false;
+ }
+ }
+ AutoCloseFile autoClose(file);
+
+ if (!forceTTY && !isatty(fileno(file))) {
+ // It's not interactive - just execute it.
+ if (kind == FileScript) {
+ if (!RunFile(cx, filename, file, compileOnly))
+ return false;
+ } else {
+ if (!RunModule(cx, filename, file, compileOnly))
+ return false;
+ }
+ } else {
+ // It's an interactive filehandle; drop into read-eval-print loop.
+ MOZ_ASSERT(kind == FileScript);
+ if (!ReadEvalPrintLoop(cx, file, compileOnly))
+ return false;
+ }
+ return true;
+}
+
+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_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "version");
+ return false;
+ }
+ JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
+ args.rval().setInt32(origVersion);
+ }
+ return true;
+}
+
+#ifdef XP_WIN
+# define GET_FD_FROM_FILE(a) int(_get_osfhandle(fileno(a)))
+#else
+# define GET_FD_FROM_FILE(a) fileno(a)
+#endif
+
+static bool
+CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1 || args.length() > 3) {
+ JS_ReportErrorNumberASCII(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 (size == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
+ return false;
+ }
+ }
+
+ FILE* file = fopen(filename.ptr(), "r");
+ if (!file) {
+ ReportCantOpenErrorUnknownEncoding(cx, filename.ptr());
+ return false;
+ }
+ AutoCloseFile autoClose(file);
+
+ if (!sizeGiven) {
+ struct stat st;
+ if (fstat(fileno(file), &st) < 0) {
+ JS_ReportErrorASCII(cx, "Unable to stat file");
+ return false;
+ }
+ if (off_t(offset) >= st.st_size) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
+ return false;
+ }
+ size = st.st_size - offset;
+ }
+
+ void* contents = JS_CreateMappedArrayBufferContents(GET_FD_FROM_FILE(file), offset, size);
+ if (!contents) {
+ JS_ReportErrorASCII(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;
+}
+
+#undef GET_FD_FROM_FILE
+
+static bool
+AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 3) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
+ args.length() < 3 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
+ "addPromiseReactions");
+ return false;
+ }
+
+ RootedObject promise(cx);
+ if (args[0].isObject())
+ promise = &args[0].toObject();
+
+ if (!promise || !JS::IsPromiseObject(promise)) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "addPromiseReactions");
+ return false;
+ }
+
+ RootedObject onResolve(cx);
+ if (args[1].isObject())
+ onResolve = &args[1].toObject();
+
+ RootedObject onReject(cx);
+ if (args[2].isObject())
+ onReject = &args[2].toObject();
+
+ if (!onResolve || !onResolve->is<JSFunction>() || !onReject || !onReject->is<JSFunction>()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "addPromiseReactions");
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
+}
+
+static bool
+Options(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx);
+ for (unsigned i = 0; i < args.length(); i++) {
+ RootedString str(cx, JS::ToString(cx, args[i]));
+ if (!str)
+ return false;
+ args[i].setString(str);
+
+ JSAutoByteString opt;
+ if (!opt.encodeUtf8(cx, str))
+ return false;
+
+ if (strcmp(opt.ptr(), "strict") == 0)
+ JS::ContextOptionsRef(cx).toggleExtraWarnings();
+ else if (strcmp(opt.ptr(), "werror") == 0)
+ JS::ContextOptionsRef(cx).toggleWerror();
+ else if (strcmp(opt.ptr(), "throw_on_asmjs_validation_failure") == 0)
+ JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
+ else if (strcmp(opt.ptr(), "strict_mode") == 0)
+ JS::ContextOptionsRef(cx).toggleStrictMode();
+ else {
+ JS_ReportErrorUTF8(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 && oldContextOptions.extraWarnings()) {
+ names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict");
+ found = true;
+ }
+ if (names && oldContextOptions.werror()) {
+ names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror");
+ found = true;
+ }
+ if (names && oldContextOptions.throwOnAsmJSValidationFailure()) {
+ names = JS_sprintf_append(names, "%s%s", found ? "," : "", "throw_on_asmjs_validation_failure");
+ found = true;
+ }
+ if (names && oldContextOptions.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_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "load");
+ return false;
+ }
+ str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative);
+ if (!str) {
+ JS_ReportErrorASCII(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;
+}
+
+static void
+my_LargeAllocFailCallback(void* data)
+{
+ JSContext* cx = (JSContext*)data;
+ JSRuntime* rt = cx->runtime();
+
+ if (!cx->isJSContext())
+ return;
+
+ MOZ_ASSERT(!rt->isHeapBusy());
+
+ JS::PrepareForFullGC(cx);
+ 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_ReportErrorNumberASCII(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;
+}
+
+static bool
+ConvertTranscodeResultToJSException(JSContext* cx, JS::TranscodeResult rv)
+{
+ switch (rv) {
+ case JS::TranscodeResult_Ok:
+ return true;
+
+ default:
+ MOZ_FALLTHROUGH;
+ case JS::TranscodeResult_Failure:
+ MOZ_ASSERT(!cx->isExceptionPending());
+ JS_ReportErrorASCII(cx, "generic warning");
+ return false;
+ case JS::TranscodeResult_Failure_BadBuildId:
+ MOZ_ASSERT(!cx->isExceptionPending());
+ JS_ReportErrorASCII(cx, "the build-id does not match");
+ return false;
+ case JS::TranscodeResult_Failure_RunOnceNotSupported:
+ MOZ_ASSERT(!cx->isExceptionPending());
+ JS_ReportErrorASCII(cx, "run-once script are not supported by XDR");
+ return false;
+ case JS::TranscodeResult_Failure_AsmJSNotSupported:
+ MOZ_ASSERT(!cx->isExceptionPending());
+ JS_ReportErrorASCII(cx, "Asm.js is not supported by XDR");
+ return false;
+ case JS::TranscodeResult_Failure_UnknownClassKind:
+ MOZ_ASSERT(!cx->isExceptionPending());
+ JS_ReportErrorASCII(cx, "Unknown class kind, go fix it.");
+ return false;
+
+ case JS::TranscodeResult_Throw:
+ MOZ_ASSERT(cx->isExceptionPending());
+ return false;
+ }
+}
+
+static bool
+Evaluate(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1 || args.length() > 2) {
+ JS_ReportErrorNumberASCII(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_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
+ return false;
+ }
+
+ CompileOptions options(cx);
+ JSAutoByteString fileNameBytes;
+ RootedString displayURL(cx);
+ RootedString sourceMapURL(cx);
+ RootedObject global(cx, nullptr);
+ bool catchTermination = 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, "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_ReportErrorNumberASCII(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, "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_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "evaluate");
+ return false;
+ }
+ }
+ }
+
+ AutoStableStringChars codeChars(cx);
+ if (!codeChars.initTwoByte(cx, code))
+ return false;
+
+ JS::TranscodeBuffer loadBuffer;
+ JS::TranscodeBuffer saveBuffer;
+
+ if (loadBytecode) {
+ uint32_t loadLength = 0;
+ uint8_t* loadData = nullptr;
+ loadData = CacheEntry_getBytecode(cacheEntry, &loadLength);
+ if (!loadData)
+ return false;
+ if (!loadBuffer.append(loadData, loadLength)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ {
+ JSAutoCompartment ac(cx, global);
+ RootedScript script(cx);
+
+ {
+ if (saveBytecode) {
+ if (!JS::CompartmentCreationOptionsRef(cx).cloneSingletons()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
+ JSSMSG_CACHE_SINGLETON_FAILED);
+ return false;
+ }
+
+ // cloneSingletons implies that singletons are used as template objects.
+ MOZ_ASSERT(JS::CompartmentBehaviorsRef(cx).getSingletonsAsTemplates());
+ }
+
+ if (loadBytecode) {
+ JS::TranscodeResult rv = JS::DecodeScript(cx, loadBuffer, &script);
+ if (!ConvertTranscodeResultToJSException(cx, rv))
+ return false;
+ } else {
+ mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
+ (void) JS::Compile(cx, options, chars.begin().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().begin().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().begin().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) {
+ JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script);
+ if (!ConvertTranscodeResultToJSException(cx, rv))
+ 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 (saveBuffer.length() != loadBuffer.length()) {
+ char loadLengthStr[16];
+ SprintfLiteral(loadLengthStr, "%" PRIuSIZE, loadBuffer.length());
+ char saveLengthStr[16];
+ SprintfLiteral(saveLengthStr,"%" PRIuSIZE, saveBuffer.length());
+
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED,
+ loadLengthStr, saveLengthStr);
+ return false;
+ }
+
+ if (!PodEqual(loadBuffer.begin(), saveBuffer.begin(), loadBuffer.length())) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
+ JSSMSG_CACHE_EQ_CONTENT_FAILED);
+ return false;
+ }
+ }
+
+ size_t saveLength = saveBuffer.length();
+ if (saveLength >= INT32_MAX) {
+ JS_ReportErrorASCII(cx, "Cannot save large cache entry content");
+ return false;
+ }
+ uint8_t* saveData = saveBuffer.extractOrCopyRawBuffer();
+ if (!CacheEntry_setBytecode(cx, cacheEntry, saveData, saveLength)) {
+ js_free(saveData);
+ return false;
+ }
+ }
+
+ return JS_WrapValue(cx, args.rval());
+}
+
+JSString*
+js::shell::FileAsString(JSContext* cx, JS::HandleString pathnameStr)
+{
+ JSAutoByteString pathname(cx, pathnameStr);
+ if (!pathname)
+ return nullptr;
+
+ FILE* file;
+
+ file = fopen(pathname.ptr(), "rb");
+ if (!file) {
+ ReportCantOpenErrorUnknownEncoding(cx, pathname.ptr());
+ return nullptr;
+ }
+
+ AutoCloseFile autoClose(file);
+
+ if (fseek(file, 0, SEEK_END) != 0) {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't seek end of %s", pathname.ptr());
+ return nullptr;
+ }
+
+ size_t len = ftell(file);
+ if (fseek(file, 0, SEEK_SET) != 0) {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.ptr());
+ return nullptr;
+ }
+
+ UniqueChars buf(static_cast<char*>(js_malloc(len + 1)));
+ if (!buf)
+ return nullptr;
+
+ size_t cc = fread(buf.get(), 1, len, file);
+ if (cc != len) {
+ if (ptrdiff_t(cc) < 0) {
+ ReportCantOpenErrorUnknownEncoding(cx, pathname.ptr());
+ } else {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "can't read %s: short read", pathname.ptr());
+ }
+ return nullptr;
+ }
+
+ UniqueTwoByteChars ucbuf(
+ JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf.get(), len), &len).get()
+ );
+ if (!ucbuf) {
+ pathname.clear();
+ if (!pathname.encodeUtf8(cx, pathnameStr))
+ return nullptr;
+ JS_ReportErrorUTF8(cx, "Invalid UTF-8 in file '%s'", pathname.ptr());
+ return nullptr;
+ }
+
+ return JS_NewUCStringCopyN(cx, ucbuf.get(), len);
+}
+
+/*
+ * 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_ReportErrorNumberASCII(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);
+
+ str = FileAsString(cx, str);
+ if (!str)
+ return false;
+
+ AutoStableStringChars chars(cx);
+ if (!chars.initTwoByte(cx, str))
+ return false;
+
+ const char16_t* ucbuf = chars.twoByteRange().begin().get();
+ size_t buflen = str->length();
+
+ RootedScript script(cx);
+ int64_t startClock = PRMJ_Now();
+ {
+ /* FIXME: This should use UTF-8 (bug 987069). */
+ JSAutoByteString filename(cx, str);
+ if (!filename)
+ return false;
+
+ 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;
+}
+
+/*
+ * function readlineBuf()
+ * Provides a hook for scripts to emulate readline() using a string object.
+ */
+static bool
+ReadLineBuf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ ShellContext* sc = GetShellContext(cx);
+
+ if (!args.length()) {
+ if (!sc->readLineBuf) {
+ JS_ReportErrorASCII(cx, "No source buffer set. You must initially "
+ "call readlineBuf with an argument.");
+ return false;
+ }
+
+ char* currentBuf = sc->readLineBuf.get() + sc->readLineBufPos;
+ size_t buflen = strlen(currentBuf);
+
+ if (!buflen) {
+ args.rval().setNull();
+ return true;
+ }
+
+ size_t len = 0;
+ while(len < buflen) {
+ if (currentBuf[len] == '\n')
+ break;
+ len++;
+ }
+
+ JSString* str = JS_NewStringCopyN(cx, currentBuf, len);
+ if (!str)
+ return false;
+
+ if (currentBuf[len] == '\0')
+ sc->readLineBufPos += len;
+ else
+ sc->readLineBufPos += len + 1;
+
+ args.rval().setString(str);
+ return true;
+ }
+
+ if (args.length() == 1) {
+ if (sc->readLineBuf)
+ sc->readLineBuf.reset();
+
+ RootedString str(cx, JS::ToString(cx, args[0]));
+ if (!str)
+ return false;
+ sc->readLineBuf = UniqueChars(JS_EncodeStringToUTF8(cx, str));
+ if (!sc->readLineBuf)
+ return false;
+
+ sc->readLineBufPos = 0;
+ return true;
+ }
+
+ JS_ReportErrorASCII(cx, "Must specify at most one argument");
+ return false;
+}
+
+static bool
+PutStr(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 0) {
+ if (!gOutFile->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ 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->fp);
+ JS_free(cx, bytes);
+ fflush(gOutFile->fp);
+ }
+
+ 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, RCFile* file)
+{
+ if (!file->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ 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->fp, "%s%s", i ? " " : "", bytes);
+ JS_free(cx, bytes);
+ }
+
+ fputc('\n', file->fp);
+ fflush(file->fp);
+
+ 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)
+{
+ ShellContext* sc = GetShellContext(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_ReportErrorASCII(cx, "quit exit code should be in range 0-127");
+ return false;
+ }
+
+ sc->exitCode = code;
+ sc->quitting = true;
+ return false;
+}
+
+static bool
+StartTimingMutator(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() > 0) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_TOO_MANY_ARGS,
+ "startTimingMutator");
+ return false;
+ }
+
+ if (!cx->runtime()->gc.stats.startTimingMutator()) {
+ JS_ReportErrorASCII(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_ReportErrorNumberASCII(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_ReportErrorASCII(cx, "stopTimingMutator called when not timing the mutator");
+ return false;
+ }
+ double total_ms = mutator_ms + gc_ms;
+ if (total_ms > 0 && gOutFile->isOpen()) {
+ fprintf(gOutFile->fp, "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_ReportErrorNumberASCII(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_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED,
+ actual, expected);
+ } else {
+ JSAutoByteString bytes2(cx, args[2].toString());
+ if (!bytes2)
+ return false;
+ JS_ReportErrorNumberLatin1(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, HandleValue v, JSFunction** funp = nullptr)
+{
+ 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().begin().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;
+ }
+
+ // Get unwrapped async function.
+ if (IsWrappedAsyncFunction(fun))
+ fun = GetUnwrappedAsyncFunction(fun);
+
+ if (!fun->isInterpreted()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY);
+ return nullptr;
+ }
+
+ JSScript* script = fun->getOrCreateScript(cx);
+ if (!script)
+ return nullptr;
+
+ if (funp)
+ *funp = fun;
+
+ return script;
+}
+
+static JSScript*
+GetTopScript(JSContext* cx)
+{
+ NonBuiltinScriptFrameIter iter(cx);
+ return iter.done() ? nullptr : iter.script();
+}
+
+static bool
+GetScriptAndPCArgs(JSContext* cx, CallArgs& args, MutableHandleScript scriptp,
+ int32_t* ip)
+{
+ RootedScript script(cx, GetTopScript(cx));
+ *ip = 0;
+ if (!args.get(0).isUndefined()) {
+ HandleValue v = args[0];
+ unsigned intarg = 0;
+ if (v.isObject() &&
+ JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) {
+ script = ValueToScript(cx, v);
+ if (!script)
+ return false;
+ intarg++;
+ }
+ if (!args.get(intarg).isUndefined()) {
+ if (!JS::ToInt32(cx, args[intarg], ip))
+ return false;
+ if ((uint32_t)*ip >= script->length()) {
+ JS_ReportErrorASCII(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_ReportErrorNumberASCII(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, &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 MOZ_MUST_USE bool
+SrcNotes(JSContext* cx, HandleScript script, Sprinter* sp)
+{
+ if (sp->put("\nSource notes:\n") < 0 ||
+ !sp->jsprintf("%4s %4s %5s %6s %-8s %s\n",
+ "ofs", "line", "pc", "delta", "desc", "args") ||
+ sp->put("---- ---- ----- ------ -------- ------\n") < 0)
+ {
+ return false;
+ }
+
+ 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;
+ if (!sp->jsprintf("%3u: %4u %5u [%4u] %-8s",
+ unsigned(sn - notes), lineno, offset, delta, name))
+ {
+ return false;
+ }
+
+ 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));
+ if (!sp->jsprintf("%d", colspan))
+ return false;
+ break;
+
+ case SRC_SETLINE:
+ lineno = GetSrcNoteOffset(sn, 0);
+ if (!sp->jsprintf(" lineno %u", lineno))
+ return false;
+ break;
+
+ case SRC_NEWLINE:
+ ++lineno;
+ break;
+
+ case SRC_FOR:
+ if (!sp->jsprintf(" cond %u update %u tail %u",
+ unsigned(GetSrcNoteOffset(sn, 0)),
+ unsigned(GetSrcNoteOffset(sn, 1)),
+ unsigned(GetSrcNoteOffset(sn, 2))))
+ {
+ return false;
+ }
+ break;
+
+ case SRC_IF_ELSE:
+ if (!sp->jsprintf(" else %u", unsigned(GetSrcNoteOffset(sn, 0))))
+ return false;
+ break;
+
+ case SRC_FOR_IN:
+ case SRC_FOR_OF:
+ if (!sp->jsprintf(" closingjump %u", unsigned(GetSrcNoteOffset(sn, 0))))
+ return false;
+ break;
+
+ case SRC_COND:
+ case SRC_WHILE:
+ case SRC_NEXTCASE:
+ if (!sp->jsprintf(" offset %u", unsigned(GetSrcNoteOffset(sn, 0))))
+ return false;
+ break;
+
+ case SRC_TABLESWITCH: {
+ JSOp op = JSOp(script->code()[offset]);
+ MOZ_ASSERT(op == JSOP_TABLESWITCH);
+ if (!sp->jsprintf(" length %u", unsigned(GetSrcNoteOffset(sn, 0))))
+ return false;
+ UpdateSwitchTableBounds(cx, script, offset,
+ &switchTableStart, &switchTableEnd);
+ break;
+ }
+ case SRC_CONDSWITCH: {
+ JSOp op = JSOp(script->code()[offset]);
+ MOZ_ASSERT(op == JSOP_CONDSWITCH);
+ if (!sp->jsprintf(" length %u", unsigned(GetSrcNoteOffset(sn, 0))))
+ return false;
+ if (unsigned caseOff = (unsigned) GetSrcNoteOffset(sn, 1)) {
+ if (!sp->jsprintf(" first case offset %u", caseOff))
+ return false;
+ }
+ UpdateSwitchTableBounds(cx, script, offset,
+ &switchTableStart, &switchTableEnd);
+ break;
+ }
+
+ case SRC_TRY:
+ MOZ_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY);
+ if (!sp->jsprintf(" offset to jump %u", unsigned(GetSrcNoteOffset(sn, 0))))
+ return false;
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
+ }
+ if (sp->put("\n") < 0)
+ return false;
+ }
+
+ return true;
+}
+
+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;
+
+ if (!SrcNotes(cx, script, &sprinter))
+ return false;
+ }
+
+ 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 MOZ_MUST_USE bool
+TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
+{
+ if (!script->hasTrynotes())
+ return true;
+
+ if (sp->put("\nException table:\nkind stack start end\n") < 0)
+ return false;
+
+ JSTryNote* tn = script->trynotes()->vector;
+ JSTryNote* tnlimit = tn + script->trynotes()->length;
+ do {
+ MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames));
+ uint8_t startOff = script->pcToOffset(script->main()) + tn->start;
+ if (!sp->jsprintf(" %-7s %6u %8u %8u\n",
+ TryNoteNames[tn->kind], tn->stackDepth,
+ startOff, startOff + tn->length))
+ {
+ return false;
+ }
+ } while (++tn != tnlimit);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ScopeNotes(JSContext* cx, HandleScript script, Sprinter* sp)
+{
+ if (!script->hasScopeNotes())
+ return true;
+
+ if (sp->put("\nScope notes:\n index parent start end\n") < 0)
+ return false;
+
+ ScopeNoteArray* notes = script->scopeNotes();
+ for (uint32_t i = 0; i < notes->length; i++) {
+ const ScopeNote* note = &notes->vector[i];
+ if (note->index == ScopeNote::NoScopeIndex) {
+ if (!sp->jsprintf("%8s ", "(none)"))
+ return false;
+ } else {
+ if (!sp->jsprintf("%8u ", note->index))
+ return false;
+ }
+ if (note->parent == ScopeNote::NoScopeIndex) {
+ if (!sp->jsprintf("%8s ", "(none)"))
+ return false;
+ } else {
+ if (!sp->jsprintf("%8u ", note->parent))
+ return false;
+ }
+ if (!sp->jsprintf("%8u %8u\n", note->start, note->start + note->length))
+ return false;
+ }
+ return true;
+}
+
+static MOZ_MUST_USE bool
+DisassembleScript(JSContext* cx, HandleScript script, HandleFunction fun,
+ bool lines, bool recursive, bool sourceNotes, Sprinter* sp)
+{
+ if (fun) {
+ if (sp->put("flags:") < 0)
+ return false;
+ if (fun->isLambda()) {
+ if (sp->put(" LAMBDA") < 0)
+ return false;
+ }
+ if (fun->needsCallObject()) {
+ if (sp->put(" NEEDS_CALLOBJECT") < 0)
+ return false;
+ }
+ if (fun->needsExtraBodyVarEnvironment()) {
+ if (sp->put(" NEEDS_EXTRABODYVARENV") < 0)
+ return false;
+ }
+ if (fun->needsNamedLambdaEnvironment()) {
+ if (sp->put(" NEEDS_NAMEDLAMBDAENV") < 0)
+ return false;
+ }
+ if (fun->isConstructor()) {
+ if (sp->put(" CONSTRUCTOR") < 0)
+ return false;
+ }
+ if (fun->isExprBody()) {
+ if (sp->put(" EXPRESSION_CLOSURE") < 0)
+ return false;
+ }
+ if (fun->isSelfHostedBuiltin()) {
+ if (sp->put(" SELF_HOSTED") < 0)
+ return false;
+ }
+ if (fun->isArrow()) {
+ if (sp->put(" ARROW") < 0)
+ return false;
+ }
+ if (sp->put("\n") < 0)
+ return false;
+ }
+
+ if (!Disassemble(cx, script, lines, sp))
+ return false;
+ if (sourceNotes) {
+ if (!SrcNotes(cx, script, sp))
+ return false;
+ }
+ if (!TryNotes(cx, script, sp))
+ return false;
+ if (!ScopeNotes(cx, script, sp))
+ return false;
+
+ 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>()) {
+ if (sp->put("\n") < 0)
+ return false;
+
+ 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 {
+ if (sp->put("[native code]\n") < 0)
+ return false;
+ }
+ }
+ }
+ }
+
+ 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;
+ if (!SrcNotes(cx, script, sprinter))
+ return false;
+ if (!TryNotes(cx, script, sprinter))
+ return false;
+ if (!ScopeNotes(cx, script, sprinter))
+ return false;
+ }
+ } 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);
+
+ if (!gOutFile->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ Sprinter sprinter(cx);
+ if (!sprinter.init())
+ return false;
+ if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter))
+ return false;
+
+ fprintf(gOutFile->fp, "%s\n", sprinter.string());
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DisassFile(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!gOutFile->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ /* 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(gOutFile->fp, "%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);
+
+ if (!gOutFile->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ const size_t lineBufLen = 512;
+ unsigned len, line1, line2, bupline;
+ char linebuf[lineBufLen];
+ static const char sep[] = ";-------------------------";
+
+ RootedScript script(cx);
+ for (unsigned i = 0; i < args.length(); i++) {
+ script = ValueToScript(cx, args[i]);
+ if (!script)
+ return false;
+
+ if (!script->filename()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_FILE_SCRIPTS_ONLY);
+ return false;
+ }
+
+ FILE* file = fopen(script->filename(), "r");
+ if (!file) {
+ /* FIXME: script->filename() should become UTF-8 (bug 987069). */
+ ReportCantOpenErrorUnknownEncoding(cx, script->filename());
+ return false;
+ }
+ auto closeFile = MakeScopeExit([file]() { fclose(file); });
+
+ jsbytecode* pc = script->code();
+ jsbytecode* end = script->codeEnd();
+
+ Sprinter sprinter(cx);
+ if (!sprinter.init())
+ return false;
+
+ /* burn the leading lines */
+ line2 = PCToLineNumber(script, pc);
+ for (line1 = 0; line1 < line2 - 1; line1++) {
+ char* tmp = fgets(linebuf, lineBufLen, file);
+ if (!tmp) {
+ /* FIXME: This should use UTF-8 (bug 987069). */
+ JS_ReportErrorLatin1(cx, "failed to read %s fully", script->filename());
+ return false;
+ }
+ }
+
+ bupline = 0;
+ while (pc < end) {
+ line2 = PCToLineNumber(script, pc);
+
+ if (line2 < line1) {
+ if (bupline != line2) {
+ bupline = line2;
+ if (!sprinter.jsprintf("%s %3u: BACKUP\n", sep, line2))
+ return false;
+ }
+ } else {
+ if (bupline && line1 == line2) {
+ if (!sprinter.jsprintf("%s %3u: RESTORE\n", sep, line2))
+ return false;
+ }
+ bupline = 0;
+ while (line1 < line2) {
+ if (!fgets(linebuf, lineBufLen, file)) {
+ /*
+ * FIXME: script->filename() should become UTF-8
+ * (bug 987069).
+ */
+ JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr,
+ JSSMSG_UNEXPECTED_EOF,
+ script->filename());
+ return false;
+ }
+ line1++;
+ if (!sprinter.jsprintf("%s %3u: %s", sep, line1, linebuf))
+ return false;
+ }
+ }
+
+ len = Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter);
+ if (!len)
+ return false;
+
+ pc += len;
+ }
+
+ fprintf(gOutFile->fp, "%s\n", sprinter.string());
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#endif /* DEBUG */
+
+/* Pretend we can always preserve wrappers for dummy DOM objects. */
+static bool
+DummyPreserveWrapperCallback(JSContext* cx, JSObject* obj)
+{
+ return true;
+}
+
+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.begin().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_ReportErrorASCII(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
+Crash(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0)
+ MOZ_CRASH("forced crash");
+ RootedString message(cx, JS::ToString(cx, args[0]));
+ if (!message)
+ return false;
+ char* utf8chars = JS_EncodeStringToUTF8(cx, message);
+ if (!utf8chars)
+ return false;
+#ifndef DEBUG
+ MOZ_ReportCrash(utf8chars, __FILE__, __LINE__);
+#endif
+ MOZ_CRASH_UNSAFE_OOL(utf8chars);
+}
+
+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_ReportErrorASCII(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 JSClassOps sandbox_classOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ sandbox_enumerate, sandbox_resolve,
+ nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+};
+
+static const JSClass sandbox_class = {
+ "sandbox",
+ JSCLASS_GLOBAL_FLAGS,
+ &sandbox_classOps
+};
+
+static void
+SetStandardCompartmentOptions(JS::CompartmentOptions& options)
+{
+ options.behaviors().setVersion(JSVERSION_DEFAULT);
+ options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
+}
+
+static JSObject*
+NewSandbox(JSContext* cx, bool lazy)
+{
+ JS::CompartmentOptions options;
+ SetStandardCompartmentOptions(options);
+ RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, nullptr,
+ JS::DontFireOnNewGlobalHook, options));
+ 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.begin().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_ReportErrorASCII(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
+{
+ JSContext* context;
+ char16_t* chars;
+ size_t length;
+
+ WorkerInput(JSContext* context, char16_t* chars, size_t length)
+ : context(context), chars(chars), length(length)
+ {}
+
+ ~WorkerInput() {
+ js_free(chars);
+ }
+};
+
+static void SetWorkerContextOptions(JSContext* cx);
+
+static void
+WorkerMain(void* arg)
+{
+ WorkerInput* input = (WorkerInput*) arg;
+
+ JSContext* cx = JS_NewContext(8L * 1024L * 1024L, 2L * 1024L * 1024L, input->context);
+ if (!cx) {
+ js_delete(input);
+ return;
+ }
+
+ UniquePtr<ShellContext> sc = MakeUnique<ShellContext>(cx);
+ if (!sc) {
+ JS_DestroyContext(cx);
+ js_delete(input);
+ return;
+ }
+
+ sc->isWorker = true;
+ JS_SetContextPrivate(cx, sc.get());
+ JS_SetFutexCanWait(cx);
+ JS::SetWarningReporter(cx, WarningReporter);
+ js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
+ JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
+ SetWorkerContextOptions(cx);
+
+ if (!JS::InitSelfHostedCode(cx)) {
+ JS_DestroyContext(cx);
+ js_delete(input);
+ return;
+ }
+
+#ifdef SPIDERMONKEY_PROMISE
+ sc->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
+ JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
+ JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
+ JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
+#endif // SPIDERMONKEY_PROMISE
+
+ EnvironmentPreparer environmentPreparer(cx);
+
+ JS::SetLargeAllocationFailureCallback(cx, my_LargeAllocFailCallback, (void*)cx);
+
+ do {
+ JSAutoRequest ar(cx);
+
+ JS::CompartmentOptions compartmentOptions;
+ SetStandardCompartmentOptions(compartmentOptions);
+ RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr));
+ if (!global)
+ break;
+
+ JSAutoCompartment ac(cx, global);
+
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("<string>", 1)
+ .setIsRunOnce(true);
+
+ AutoReportException are(cx);
+ 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(cx, nullptr, nullptr);
+
+#ifdef SPIDERMONKEY_PROMISE
+ JS::SetGetIncumbentGlobalCallback(cx, nullptr);
+ JS::SetEnqueuePromiseJobCallback(cx, nullptr);
+ sc->jobQueue.reset();
+#endif // SPIDERMONKEY_PROMISE
+
+ KillWatchdog(cx);
+
+ JS_DestroyContext(cx);
+
+ js_delete(input);
+}
+
+// Workers can spawn other workers, so we need a lock to access workerThreads.
+static Mutex* workerThreadsLock = nullptr;
+static Vector<js::Thread*, 0, SystemAllocPolicy> workerThreads;
+
+class MOZ_RAII AutoLockWorkerThreads : public LockGuard<Mutex>
+{
+ using Base = LockGuard<Mutex>;
+ public:
+ AutoLockWorkerThreads()
+ : Base(*workerThreadsLock)
+ {
+ MOZ_ASSERT(workerThreadsLock);
+ }
+};
+
+static bool
+EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
+{
+ if (!CanUseExtraThreads()) {
+ JS_ReportErrorASCII(cx, "Can't create worker threads with --no-threads");
+ return false;
+ }
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isString()) {
+ JS_ReportErrorASCII(cx, "Invalid arguments to evalInWorker");
+ return false;
+ }
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+ if (cx->runtime()->runningOOMTest) {
+ JS_ReportErrorASCII(cx, "Can't create workers while running simulated OOM test");
+ return false;
+ }
+#endif
+
+ if (!args[0].toString()->ensureLinear(cx))
+ return false;
+
+ if (!workerThreadsLock) {
+ workerThreadsLock = js_new<Mutex>(mutexid::ShellWorkerThreads);
+ if (!workerThreadsLock) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ JSLinearString* str = &args[0].toString()->asLinear();
+
+ char16_t* chars = (char16_t*) js_malloc(str->length() * sizeof(char16_t));
+ if (!chars) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ CopyChars(chars, *str);
+
+ WorkerInput* input = js_new<WorkerInput>(JS_GetParentContext(cx), chars, str->length());
+ if (!input) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ auto thread = js_new<Thread>(Thread::Options().setStackSize(gMaxStackSize + 128 * 1024));
+ if (!thread || !thread->init(WorkerMain, input)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ AutoLockWorkerThreads alwt;
+ if (!workerThreads.append(thread)) {
+ ReportOutOfMemory(cx);
+ thread->join();
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "shapeOf: object expected");
+ return false;
+ }
+ JSObject* obj = &args[0].toObject();
+ args.rval().set(JS_NumberValue(double(uintptr_t(obj->maybeShape()) >> 3)));
+ return true;
+}
+
+static bool
+GroupOf(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject()) {
+ JS_ReportErrorASCII(cx, "groupOf: object expected");
+ return false;
+ }
+ JSObject* obj = &args[0].toObject();
+ ObjectGroup* group = obj->getGroup(cx);
+ if (!group)
+ return false;
+ args.rval().set(JS_NumberValue(double(uintptr_t(group) >> 3)));
+ return true;
+}
+
+static bool
+UnwrappedObjectsHaveSameShape(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject() || !args.get(1).isObject()) {
+ JS_ReportErrorASCII(cx, "2 objects expected");
+ return false;
+ }
+
+ RootedObject obj1(cx, UncheckedUnwrap(&args[0].toObject()));
+ RootedObject obj2(cx, UncheckedUnwrap(&args[1].toObject()));
+
+ if (!obj1->is<ShapedObject>() || !obj2->is<ShapedObject>()) {
+ JS_ReportErrorASCII(cx, "object does not have a Shape");
+ return false;
+ }
+
+ args.rval().setBoolean(obj1->as<ShapedObject>().shape() == obj2->as<ShapedObject>().shape());
+ return true;
+}
+
+static bool
+Sleep_fn(JSContext* cx, unsigned argc, Value* vp)
+{
+ ShellContext* sc = GetShellContext(cx);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ TimeDuration duration = TimeDuration::FromSeconds(0.0);
+ if (args.length() > 0) {
+ double t_secs;
+ if (!ToNumber(cx, args[0], &t_secs))
+ return false;
+ if (mozilla::IsNaN(t_secs)) {
+ JS_ReportErrorASCII(cx, "sleep interval is not a number");
+ return false;
+ }
+
+ duration = TimeDuration::FromSeconds(Max(0.0, t_secs));
+ const TimeDuration MAX_TIMEOUT_INTERVAL = TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
+ if (duration > MAX_TIMEOUT_INTERVAL) {
+ JS_ReportErrorASCII(cx, "Excessive sleep interval");
+ return false;
+ }
+ }
+ {
+ LockGuard<Mutex> guard(sc->watchdogLock);
+ TimeStamp toWakeup = TimeStamp::Now() + duration;
+ for (;;) {
+ sc->sleepWakeup.wait_for(guard, duration);
+ if (sc->serviceInterrupt)
+ break;
+ auto now = TimeStamp::Now();
+ if (now >= toWakeup)
+ break;
+ duration = toWakeup - now;
+ }
+ }
+ args.rval().setUndefined();
+ return !sc->serviceInterrupt;
+}
+
+static void
+KillWatchdog(JSContext* cx)
+{
+ ShellContext* sc = GetShellContext(cx);
+ Maybe<Thread> thread;
+
+ {
+ LockGuard<Mutex> guard(sc->watchdogLock);
+ Swap(sc->watchdogThread, thread);
+ if (thread) {
+ // The watchdog thread becoming Nothing is its signal to exit.
+ sc->watchdogWakeup.notify_one();
+ }
+ }
+ if (thread)
+ thread->join();
+
+ MOZ_ASSERT(!sc->watchdogThread);
+}
+
+static void
+WatchdogMain(JSContext* cx)
+{
+ ThisThread::SetName("JS Watchdog");
+
+ ShellContext* sc = GetShellContext(cx);
+
+ LockGuard<Mutex> guard(sc->watchdogLock);
+ while (sc->watchdogThread) {
+ auto now = TimeStamp::Now();
+ if (sc->watchdogTimeout && now >= sc->watchdogTimeout.value()) {
+ /*
+ * The timeout has just expired. Request an interrupt callback
+ * outside the lock.
+ */
+ sc->watchdogTimeout = Nothing();
+ {
+ UnlockGuard<Mutex> unlock(guard);
+ CancelExecution(cx);
+ }
+
+ /* Wake up any threads doing sleep. */
+ sc->sleepWakeup.notify_all();
+ } else {
+ if (sc->watchdogTimeout) {
+ /*
+ * Time hasn't expired yet. Simulate an interrupt callback
+ * which doesn't abort execution.
+ */
+ JS_RequestInterruptCallback(cx);
+ }
+
+ TimeDuration sleepDuration = sc->watchdogTimeout
+ ? TimeDuration::FromSeconds(0.1)
+ : TimeDuration::Forever();
+ sc->watchdogWakeup.wait_for(guard, sleepDuration);
+ }
+ }
+}
+
+static bool
+ScheduleWatchdog(JSContext* cx, double t)
+{
+ ShellContext* sc = GetShellContext(cx);
+
+ if (t <= 0) {
+ LockGuard<Mutex> guard(sc->watchdogLock);
+ sc->watchdogTimeout = Nothing();
+ return true;
+ }
+
+ auto interval = TimeDuration::FromSeconds(t);
+ auto timeout = TimeStamp::Now() + interval;
+ LockGuard<Mutex> guard(sc->watchdogLock);
+ if (!sc->watchdogThread) {
+ MOZ_ASSERT(!sc->watchdogTimeout);
+ sc->watchdogThread.emplace();
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!sc->watchdogThread->init(WatchdogMain, cx))
+ oomUnsafe.crash("watchdogThread.init");
+ } else if (!sc->watchdogTimeout || timeout < sc->watchdogTimeout.value()) {
+ sc->watchdogWakeup.notify_one();
+ }
+ sc->watchdogTimeout = Some(timeout);
+ return true;
+}
+
+static void
+KillWorkerThreads()
+{
+ MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty());
+
+ if (!workerThreadsLock) {
+ MOZ_ASSERT(workerThreads.empty());
+ return;
+ }
+
+ while (true) {
+ // We need to leave the AutoLockWorkerThreads scope before we call
+ // js::Thread::join, to avoid deadlocks when AutoLockWorkerThreads is
+ // used by the worker thread.
+ Thread* thread;
+ {
+ AutoLockWorkerThreads alwt;
+ if (workerThreads.empty())
+ break;
+ thread = workerThreads.popCopy();
+ }
+ thread->join();
+ }
+
+ js_delete(workerThreadsLock);
+ workerThreadsLock = nullptr;
+}
+
+static void
+CancelExecution(JSContext* cx)
+{
+ ShellContext* sc = GetShellContext(cx);
+ sc->serviceInterrupt = true;
+ JS_RequestInterruptCallback(cx);
+
+ if (sc->haveInterruptFunc) {
+ static const char msg[] = "Script runs for too long, terminating.\n";
+ fputs(msg, stderr);
+ }
+}
+
+static bool
+SetTimeoutValue(JSContext* cx, double t)
+{
+ if (mozilla::IsNaN(t)) {
+ JS_ReportErrorASCII(cx, "timeout is not a number");
+ return false;
+ }
+ const TimeDuration MAX_TIMEOUT_INTERVAL = TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
+ if (TimeDuration::FromSeconds(t) > MAX_TIMEOUT_INTERVAL) {
+ JS_ReportErrorASCII(cx, "Excessive timeout value");
+ return false;
+ }
+ GetShellContext(cx)->timeoutInterval = t;
+ if (!ScheduleWatchdog(cx, t)) {
+ JS_ReportErrorASCII(cx, "Failed to create the watchdog");
+ return false;
+ }
+ return true;
+}
+
+static bool
+Timeout(JSContext* cx, unsigned argc, Value* vp)
+{
+ ShellContext* sc = GetShellContext(cx);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() == 0) {
+ args.rval().setNumber(sc->timeoutInterval);
+ return true;
+ }
+
+ if (args.length() > 2) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ double t;
+ if (!ToNumber(cx, args[0], &t))
+ return false;
+
+ if (args.length() > 1) {
+ RootedValue value(cx, args[1]);
+ if (!value.isObject() || !value.toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "Second argument must be a timeout function");
+ return false;
+ }
+ sc->interruptFunc = value;
+ sc->haveInterruptFunc = true;
+ }
+
+ args.rval().setUndefined();
+ return SetTimeoutValue(cx, t);
+}
+
+static bool
+InterruptIf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ if (ToBoolean(args[0])) {
+ GetShellContext(cx)->serviceInterrupt = true;
+ JS_RequestInterruptCallback(cx);
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ GetShellContext(cx)->serviceInterrupt = true;
+ JS_RequestInterruptCallback(cx);
+ bool interruptRv = CheckForInterrupt(cx);
+
+ // The interrupt handler could have set a pending exception. Since we call
+ // back into JS, don't have it see the pending exception. If we have an
+ // uncatchable exception that's not propagating a debug mode forced
+ // return, return.
+ if (!interruptRv && !cx->isExceptionPending() && !cx->isPropagatingForcedReturn())
+ return false;
+
+ JS::AutoSaveExceptionState savedExc(cx);
+
+ FixedInvokeArgs<1> iargs(cx);
+
+ iargs[0].setBoolean(interruptRv);
+
+ RootedValue rv(cx);
+ if (!js::Call(cx, args[0], UndefinedHandleValue, iargs, &rv))
+ return false;
+
+ args.rval().setUndefined();
+ return interruptRv;
+}
+
+static bool
+SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedValue value(cx, args[0]);
+ if (!value.isObject() || !value.toObject().is<JSFunction>()) {
+ JS_ReportErrorASCII(cx, "Argument must be a function");
+ return false;
+ }
+ GetShellContext(cx)->interruptFunc = value;
+ GetShellContext(cx)->haveInterruptFunc = true;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+EnableLastWarning(JSContext* cx, unsigned argc, Value* vp)
+{
+ ShellContext* sc = GetShellContext(cx);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ sc->lastWarningEnabled = true;
+ sc->lastWarning.setNull();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DisableLastWarning(JSContext* cx, unsigned argc, Value* vp)
+{
+ ShellContext* sc = GetShellContext(cx);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ sc->lastWarningEnabled = false;
+ sc->lastWarning.setNull();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetLastWarning(JSContext* cx, unsigned argc, Value* vp)
+{
+ ShellContext* sc = GetShellContext(cx);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!sc->lastWarningEnabled) {
+ JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
+ return false;
+ }
+
+ if (!JS_WrapValue(cx, &sc->lastWarning))
+ return false;
+
+ args.rval().set(sc->lastWarning);
+ return true;
+}
+
+static bool
+ClearLastWarning(JSContext* cx, unsigned argc, Value* vp)
+{
+ ShellContext* sc = GetShellContext(cx);
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!sc->lastWarningEnabled) {
+ JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
+ return false;
+ }
+
+ sc->lastWarning.setNull();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifdef DEBUG
+static bool
+StackDump(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!gOutFile->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ bool showArgs = ToBoolean(args.get(0));
+ bool showLocals = ToBoolean(args.get(1));
+ bool showThisProps = ToBoolean(args.get(2));
+
+ char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps);
+ if (!buf) {
+ fputs("Failed to format JavaScript stack for dump\n", gOutFile->fp);
+ JS_ClearPendingException(cx);
+ } else {
+ fputs(buf, gOutFile->fp);
+ JS_smprintf_free(buf);
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+#endif
+
+static bool
+StackPointerInfo(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Copy the truncated stack pointer to the result. This value is not used
+ // as a pointer but as a way to measure frame-size from JS.
+ args.rval().setInt32(int32_t(reinterpret_cast<size_t>(&args) & 0xfffffff));
+ return true;
+}
+
+
+static bool
+Elapsed(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ double d = PRMJ_Now() - GetShellContext(cx)->startTime;
+ args.rval().setDouble(d);
+ return true;
+ }
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+}
+
+static bool
+Compile(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "compile", "0", "s");
+ return false;
+ }
+ if (!args[0].isString()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
+ return false;
+ }
+
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
+ if (!scriptContents)
+ return false;
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, scriptContents))
+ return false;
+
+ JS::CompileOptions options(cx);
+ options.setIntroductionType("js shell compile")
+ .setFileAndLine("<string>", 1)
+ .setIsRunOnce(true)
+ .setNoScriptRval(true);
+ RootedScript script(cx);
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+ bool ok = JS_CompileUCScript(cx, chars, scriptContents->length(), options, &script);
+ args.rval().setUndefined();
+ return ok;
+}
+
+static bool
+ParseModule(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "parseModule", "0", "s");
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
+ return false;
+ }
+
+ JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
+ if (!scriptContents)
+ return false;
+
+ UniqueChars filename;
+ CompileOptions options(cx);
+ if (args.length() > 1) {
+ if (!args[1].isString()) {
+ const char* typeName = InformalValueTypeName(args[1]);
+ JS_ReportErrorASCII(cx, "expected filename string, got %s", typeName);
+ return false;
+ }
+
+ RootedString str(cx, args[1].toString());
+ filename.reset(JS_EncodeString(cx, str));
+ if (!filename)
+ return false;
+
+ options.setFileAndLine(filename.get(), 1);
+ } else {
+ options.setFileAndLine("<string>", 1);
+ }
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, scriptContents))
+ return false;
+
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+ SourceBufferHolder srcBuf(chars, scriptContents->length(),
+ SourceBufferHolder::NoOwnership);
+
+ RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
+ if (!module)
+ return false;
+
+ args.rval().setObject(*module);
+ return true;
+}
+
+static bool
+SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() != 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "setModuleResolveHook", "0", "s");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
+ return false;
+ }
+
+ RootedFunction hook(cx, &args[0].toObject().as<JSFunction>());
+ Rooted<GlobalObject*> global(cx, cx->global());
+ global->setModuleResolveHook(hook);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(JS_NewStringCopyZ(cx, moduleLoadPath));
+ return true;
+}
+
+static bool
+Parse(JSContext* cx, unsigned argc, Value* vp)
+{
+ using namespace js::frontend;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "parse", "0", "s");
+ return false;
+ }
+ if (!args[0].isString()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
+ return false;
+ }
+
+ JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
+ if (!scriptContents)
+ return false;
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, scriptContents))
+ return false;
+
+ size_t length = scriptContents->length();
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+
+ CompileOptions options(cx);
+ options.setIntroductionType("js shell parse")
+ .setFileAndLine("<string>", 1);
+ UsedNameTracker usedNames(cx);
+ if (!usedNames.init())
+ return false;
+ Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars, length,
+ /* foldConstants = */ true, usedNames, nullptr, nullptr);
+ if (!parser.checkOptions())
+ return false;
+
+ ParseNode* pn = parser.parse();
+ if (!pn)
+ return false;
+#ifdef DEBUG
+ DumpParseTree(pn);
+ fputc('\n', stderr);
+#endif
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+SyntaxParse(JSContext* cx, unsigned argc, Value* vp)
+{
+ using namespace js::frontend;
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "parse", "0", "s");
+ return false;
+ }
+ if (!args[0].isString()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
+ return false;
+ }
+
+ JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
+ if (!scriptContents)
+ return false;
+ CompileOptions options(cx);
+ options.setIntroductionType("js shell syntaxParse")
+ .setFileAndLine("<string>", 1);
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, scriptContents))
+ return false;
+
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+ size_t length = scriptContents->length();
+ UsedNameTracker usedNames(cx);
+ if (!usedNames.init())
+ return false;
+ Parser<frontend::SyntaxParseHandler> parser(cx, cx->tempLifoAlloc(),
+ options, chars, length, false,
+ usedNames, nullptr, nullptr);
+ if (!parser.checkOptions())
+ return false;
+
+ bool succeeded = parser.parse();
+ if (cx->isExceptionPending())
+ return false;
+
+ if (!succeeded && !parser.hadAbortedSyntaxParse()) {
+ // If no exception is posted, either there was an OOM or a language
+ // feature unhandled by the syntax parser was encountered.
+ MOZ_ASSERT(cx->runtime()->hadOutOfMemory);
+ return false;
+ }
+
+ args.rval().setBoolean(succeeded);
+ return true;
+}
+
+static void
+OffThreadCompileScriptCallback(void* token, void* callbackData)
+{
+ ShellContext* sc = static_cast<ShellContext*>(callbackData);
+ sc->offThreadState.markDone(token);
+}
+
+static bool
+OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp)
+{
+ if (!CanUseExtraThreads()) {
+ JS_ReportErrorASCII(cx, "Can't use offThreadCompileScript with --no-threads");
+ return false;
+ }
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+ "offThreadCompileScript", "0", "s");
+ return false;
+ }
+ if (!args[0].isString()) {
+ const char* typeName = InformalValueTypeName(args[0]);
+ JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
+ return false;
+ }
+
+ JSAutoByteString fileNameBytes;
+ CompileOptions options(cx);
+ options.setIntroductionType("js shell offThreadCompileScript")
+ .setFileAndLine("<string>", 1);
+
+ if (args.length() >= 2) {
+ if (args[1].isPrimitive()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "evaluate");
+ return false;
+ }
+
+ RootedObject opts(cx, &args[1].toObject());
+ if (!ParseCompileOptions(cx, options, opts, fileNameBytes))
+ return false;
+ }
+
+ // These option settings must override whatever the caller requested.
+ options.setIsRunOnce(true)
+ .setSourceIsLazy(false);
+
+ // We assume the caller wants caching if at all possible, ignoring
+ // heuristics that make sense for a real browser.
+ options.forceAsync = true;
+
+ JSString* scriptContents = args[0].toString();
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, scriptContents))
+ return false;
+
+ size_t length = scriptContents->length();
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+
+ // Make sure we own the string's chars, so that they are not freed before
+ // the compilation is finished.
+ ScopedJSFreePtr<char16_t> ownedChars;
+ if (stableChars.maybeGiveOwnershipToCaller()) {
+ ownedChars = const_cast<char16_t*>(chars);
+ } else {
+ char16_t* copy = cx->pod_malloc<char16_t>(length);
+ if (!copy)
+ return false;
+
+ mozilla::PodCopy(copy, chars, length);
+ ownedChars = copy;
+ chars = copy;
+ }
+
+ if (!JS::CanCompileOffThread(cx, options, length)) {
+ JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
+ return false;
+ }
+
+ ShellContext* sc = GetShellContext(cx);
+ if (!sc->offThreadState.startIfIdle(cx, ScriptKind::Script, ownedChars)) {
+ JS_ReportErrorASCII(cx, "called offThreadCompileScript without calling runOffThreadScript"
+ " to receive prior off-thread compilation");
+ return false;
+ }
+
+ if (!JS::CompileOffThread(cx, options, chars, length,
+ OffThreadCompileScriptCallback, sc))
+ {
+ sc->offThreadState.abandon(cx);
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+runOffThreadScript(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (OffThreadParsingMustWaitForGC(cx))
+ gc::FinishGC(cx);
+
+ ShellContext* sc = GetShellContext(cx);
+ void* token = sc->offThreadState.waitUntilDone(cx, ScriptKind::Script);
+ if (!token) {
+ JS_ReportErrorASCII(cx, "called runOffThreadScript when no compilation is pending");
+ return false;
+ }
+
+ RootedScript script(cx, JS::FinishOffThreadScript(cx, token));
+ if (!script)
+ return false;
+
+ return JS_ExecuteScript(cx, script, args.rval());
+}
+
+static bool
+OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "offThreadCompileModule");
+ return false;
+ }
+
+ JSAutoByteString fileNameBytes;
+ CompileOptions options(cx);
+ options.setIntroductionType("js shell offThreadCompileModule")
+ .setFileAndLine("<string>", 1);
+ options.setIsRunOnce(true)
+ .setSourceIsLazy(false);
+ options.forceAsync = true;
+
+ JSString* scriptContents = args[0].toString();
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, scriptContents))
+ return false;
+
+ size_t length = scriptContents->length();
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+
+ // Make sure we own the string's chars, so that they are not freed before
+ // the compilation is finished.
+ ScopedJSFreePtr<char16_t> ownedChars;
+ if (stableChars.maybeGiveOwnershipToCaller()) {
+ ownedChars = const_cast<char16_t*>(chars);
+ } else {
+ char16_t* copy = cx->pod_malloc<char16_t>(length);
+ if (!copy)
+ return false;
+
+ mozilla::PodCopy(copy, chars, length);
+ ownedChars = copy;
+ chars = copy;
+ }
+
+ if (!JS::CanCompileOffThread(cx, options, length)) {
+ JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
+ return false;
+ }
+
+ ShellContext* sc = GetShellContext(cx);
+ if (!sc->offThreadState.startIfIdle(cx, ScriptKind::Module, ownedChars)) {
+ JS_ReportErrorASCII(cx, "called offThreadCompileModule without receiving prior off-thread "
+ "compilation");
+ return false;
+ }
+
+ if (!JS::CompileOffThreadModule(cx, options, chars, length,
+ OffThreadCompileScriptCallback, sc))
+ {
+ sc->offThreadState.abandon(cx);
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (OffThreadParsingMustWaitForGC(cx))
+ gc::FinishGC(cx);
+
+ ShellContext* sc = GetShellContext(cx);
+ void* token = sc->offThreadState.waitUntilDone(cx, ScriptKind::Module);
+ if (!token) {
+ JS_ReportErrorASCII(cx, "called finishOffThreadModule when no compilation is pending");
+ return false;
+ }
+
+ RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
+ if (!module)
+ return false;
+
+ args.rval().setObject(*module);
+ return true;
+}
+
+struct MOZ_RAII FreeOnReturn
+{
+ JSContext* cx;
+ const char* ptr;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+ explicit FreeOnReturn(JSContext* cx, const char* ptr = nullptr
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : cx(cx), ptr(ptr)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ }
+
+ void init(const char* ptr) {
+ MOZ_ASSERT(!this->ptr);
+ this->ptr = ptr;
+ }
+
+ ~FreeOnReturn() {
+ JS_free(cx, (void*)ptr);
+ }
+};
+
+static int sArgc;
+static char** sArgv;
+
+class AutoCStringVector
+{
+ Vector<char*> argv_;
+ public:
+ explicit AutoCStringVector(JSContext* cx) : argv_(cx) {}
+ ~AutoCStringVector() {
+ for (size_t i = 0; i < argv_.length(); i++)
+ js_free(argv_[i]);
+ }
+ bool append(char* arg) {
+ if (!argv_.append(arg)) {
+ js_free(arg);
+ return false;
+ }
+ return true;
+ }
+ char* const* get() const {
+ return argv_.begin();
+ }
+ size_t length() const {
+ return argv_.length();
+ }
+ char* operator[](size_t i) const {
+ return argv_[i];
+ }
+ void replace(size_t i, char* arg) {
+ js_free(argv_[i]);
+ argv_[i] = arg;
+ }
+ char* back() const {
+ return argv_.back();
+ }
+ void replaceBack(char* arg) {
+ js_free(argv_.back());
+ argv_.back() = arg;
+ }
+};
+
+#if defined(XP_WIN)
+static bool
+EscapeForShell(AutoCStringVector& argv)
+{
+ // Windows will break arguments in argv by various spaces, so we wrap each
+ // argument in quotes and escape quotes within. Even with quotes, \ will be
+ // treated like an escape character, so inflate each \ to \\.
+
+ for (size_t i = 0; i < argv.length(); i++) {
+ if (!argv[i])
+ continue;
+
+ size_t newLen = 3; // quotes before and after and null-terminator
+ for (char* p = argv[i]; *p; p++) {
+ newLen++;
+ if (*p == '\"' || *p == '\\')
+ newLen++;
+ }
+
+ char* escaped = (char*)js_malloc(newLen);
+ if (!escaped)
+ return false;
+
+ char* src = argv[i];
+ char* dst = escaped;
+ *dst++ = '\"';
+ while (*src) {
+ if (*src == '\"' || *src == '\\')
+ *dst++ = '\\';
+ *dst++ = *src++;
+ }
+ *dst++ = '\"';
+ *dst++ = '\0';
+ MOZ_ASSERT(escaped + newLen == dst);
+
+ argv.replace(i, escaped);
+ }
+ return true;
+}
+#endif
+
+static Vector<const char*, 4, js::SystemAllocPolicy> sPropagatedFlags;
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+static bool
+PropagateFlagToNestedShells(const char* flag)
+{
+ return sPropagatedFlags.append(flag);
+}
+#endif
+
+static bool
+NestedShell(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ AutoCStringVector argv(cx);
+
+ // The first argument to the shell is its path, which we assume is our own
+ // argv[0].
+ if (sArgc < 1) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
+ return false;
+ }
+ if (!argv.append(strdup(sArgv[0])))
+ return false;
+
+ // Propagate selected flags from the current shell
+ for (unsigned i = 0; i < sPropagatedFlags.length(); i++) {
+ char* cstr = strdup(sPropagatedFlags[i]);
+ if (!cstr || !argv.append(cstr))
+ return false;
+ }
+
+ // The arguments to nestedShell are stringified and append to argv.
+ RootedString str(cx);
+ for (unsigned i = 0; i < args.length(); i++) {
+ str = ToString(cx, args[i]);
+ if (!str || !argv.append(JS_EncodeString(cx, str)))
+ return false;
+
+ // As a special case, if the caller passes "--js-cache", replace that
+ // with "--js-cache=$(jsCacheDir)"
+ if (!strcmp(argv.back(), "--js-cache") && jsCacheDir) {
+ char* newArg = JS_smprintf("--js-cache=%s", jsCacheDir);
+ if (!newArg)
+ return false;
+ argv.replaceBack(newArg);
+ }
+ }
+
+ // execv assumes argv is null-terminated
+ if (!argv.append(nullptr))
+ return false;
+
+ int status = 0;
+#if defined(XP_WIN)
+ if (!EscapeForShell(argv))
+ return false;
+ status = _spawnv(_P_WAIT, sArgv[0], argv.get());
+#else
+ pid_t pid = fork();
+ switch (pid) {
+ case -1:
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
+ return false;
+ case 0:
+ (void)execv(sArgv[0], argv.get());
+ exit(-1);
+ default: {
+ while (waitpid(pid, &status, 0) < 0 && errno == EINTR)
+ continue;
+ break;
+ }
+ }
+#endif
+
+ if (status != 0) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+DecompileFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() < 1 || !args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ args.rval().setUndefined();
+ return true;
+ }
+ RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
+ JSString* result = JS_DecompileFunction(cx, fun, 0);
+ if (!result)
+ return false;
+ args.rval().setString(result);
+ return true;
+}
+
+static bool
+DecompileThisScript(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ NonBuiltinScriptFrameIter iter(cx);
+ if (iter.done()) {
+ args.rval().setString(cx->runtime()->emptyString);
+ return true;
+ }
+
+ {
+ JSAutoCompartment ac(cx, iter.script());
+
+ RootedScript script(cx, iter.script());
+ JSString* result = JS_DecompileScript(cx, script, "test", 0);
+ if (!result)
+ return false;
+
+ args.rval().setString(result);
+ }
+
+ return JS_WrapValue(cx, args.rval());
+}
+
+static bool
+ThisFilename(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::AutoFilename filename;
+ if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) {
+ args.rval().setString(cx->runtime()->emptyString);
+ return true;
+ }
+
+ JSString* str = JS_NewStringCopyZ(cx, filename.get());
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+ return true;
+}
+
+static bool
+WrapWithProto(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Value obj = UndefinedValue(), proto = UndefinedValue();
+ if (args.length() == 2) {
+ obj = args[0];
+ proto = args[1];
+ }
+ if (!obj.isObject() || !proto.isObjectOrNull()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "wrapWithProto");
+ return false;
+ }
+
+ WrapperOptions options(cx);
+ options.setProto(proto.toObjectOrNull());
+ JSObject* wrapped = Wrapper::New(cx, &obj.toObject(),
+ &Wrapper::singletonWithPrototype, options);
+ if (!wrapped)
+ return false;
+
+ args.rval().setObject(*wrapped);
+ return true;
+}
+
+static bool
+NewGlobal(JSContext* cx, unsigned argc, Value* vp)
+{
+ JSPrincipals* principals = nullptr;
+
+ JS::CompartmentOptions options;
+ JS::CompartmentCreationOptions& creationOptions = options.creationOptions();
+ JS::CompartmentBehaviors& behaviors = options.behaviors();
+
+ SetStandardCompartmentOptions(options);
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (args.length() == 1 && args[0].isObject()) {
+ RootedObject opts(cx, &args[0].toObject());
+ RootedValue v(cx);
+
+ if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v))
+ return false;
+ if (v.isBoolean())
+ creationOptions.setInvisibleToDebugger(v.toBoolean());
+
+ if (!JS_GetProperty(cx, opts, "cloneSingletons", &v))
+ return false;
+ if (v.isBoolean())
+ creationOptions.setCloneSingletons(v.toBoolean());
+
+ if (!JS_GetProperty(cx, opts, "sameZoneAs", &v))
+ return false;
+ if (v.isObject())
+ creationOptions.setSameZoneAs(UncheckedUnwrap(&v.toObject()));
+
+ if (!JS_GetProperty(cx, opts, "disableLazyParsing", &v))
+ return false;
+ if (v.isBoolean())
+ behaviors.setDisableLazyParsing(v.toBoolean());
+
+ if (!JS_GetProperty(cx, opts, "principal", &v))
+ return false;
+ if (!v.isUndefined()) {
+ uint32_t bits;
+ if (!ToUint32(cx, v, &bits))
+ return false;
+ principals = cx->new_<ShellPrincipals>(bits);
+ if (!principals)
+ return false;
+ JS_HoldPrincipals(principals);
+ }
+ }
+
+ RootedObject global(cx, NewGlobalObject(cx, options, principals));
+ if (principals)
+ JS_DropPrincipals(cx, principals);
+ if (!global)
+ return false;
+
+ if (!JS_WrapObject(cx, &global))
+ return false;
+
+ args.rval().setObject(*global);
+ return true;
+}
+
+static bool
+NukeCCW(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isObject() ||
+ !IsCrossCompartmentWrapper(&args[0].toObject()))
+ {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "nukeCCW");
+ return false;
+ }
+
+ NukeCrossCompartmentWrapper(cx, &args[0].toObject());
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+GetMaxArgs(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setInt32(ARGS_LENGTH_MAX);
+ return true;
+}
+
+static bool
+ObjectEmulatingUndefined(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ static const JSClass cls = {
+ "ObjectEmulatingUndefined",
+ JSCLASS_EMULATES_UNDEFINED
+ };
+
+ RootedObject obj(cx, JS_NewObject(cx, &cls));
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+GetSelfHostedValue(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1 || !args[0].isString()) {
+ JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
+ "getSelfHostedValue");
+ return false;
+ }
+ RootedAtom srcAtom(cx, ToAtom<CanGC>(cx, args[0]));
+ if (!srcAtom)
+ return false;
+ RootedPropertyName srcName(cx, srcAtom->asPropertyName());
+ return cx->runtime()->cloneSelfHostedValue(cx, srcName, args.rval());
+}
+
+class ShellSourceHook: public SourceHook {
+ // The function we should call to lazily retrieve source code.
+ PersistentRootedFunction fun;
+
+ public:
+ ShellSourceHook(JSContext* cx, JSFunction& fun) : fun(cx, &fun) {}
+
+ bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) {
+ RootedString str(cx, JS_NewStringCopyZ(cx, filename));
+ if (!str)
+ return false;
+ RootedValue filenameValue(cx, StringValue(str));
+
+ RootedValue result(cx);
+ if (!Call(cx, UndefinedHandleValue, fun, HandleValueArray(filenameValue), &result))
+ return false;
+
+ str = JS::ToString(cx, result);
+ if (!str)
+ return false;
+
+ *length = JS_GetStringLength(str);
+ *src = cx->pod_malloc<char16_t>(*length);
+ if (!*src)
+ return false;
+
+ JSLinearString* linear = str->ensureLinear(cx);
+ if (!linear)
+ return false;
+
+ CopyChars(*src, *linear);
+ return true;
+ }
+};
+
+static bool
+WithSourceHook(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 2) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()
+ || !args[1].isObject() || !args[1].toObject().is<JSFunction>()) {
+ ReportUsageErrorASCII(cx, callee, "First and second arguments must be functions.");
+ return false;
+ }
+
+ mozilla::UniquePtr<ShellSourceHook> hook =
+ mozilla::MakeUnique<ShellSourceHook>(cx, args[0].toObject().as<JSFunction>());
+ if (!hook)
+ return false;
+
+ mozilla::UniquePtr<SourceHook> savedHook = js::ForgetSourceHook(cx);
+ js::SetSourceHook(cx, Move(hook));
+
+ RootedObject fun(cx, &args[1].toObject());
+ bool result = Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), args.rval());
+ js::SetSourceHook(cx, Move(savedHook));
+ return result;
+}
+
+static bool
+IsCachingEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(jsCachingEnabled && jsCacheAsmJSPath != nullptr);
+ return true;
+}
+
+static bool
+SetCachingEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (GetShellContext(cx)->isWorker) {
+ JS_ReportErrorASCII(cx, "Caching is not supported in workers");
+ return false;
+ }
+
+ jsCachingEnabled = ToBoolean(args.get(0));
+ args.rval().setUndefined();
+ return true;
+}
+
+static void
+PrintProfilerEvents_Callback(const char* msg)
+{
+ fprintf(stderr, "PROFILER EVENT: %s\n", msg);
+}
+
+static bool
+PrintProfilerEvents(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (cx->runtime()->spsProfiler.enabled())
+ js::RegisterContextProfilingEventMarker(cx, &PrintProfilerEvents_Callback);
+ args.rval().setUndefined();
+ return true;
+}
+
+#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
+typedef Vector<char16_t, 0, SystemAllocPolicy> StackChars;
+Vector<StackChars, 0, SystemAllocPolicy> stacks;
+
+static void
+SingleStepCallback(void* arg, jit::Simulator* sim, void* pc)
+{
+ JSContext* cx = reinterpret_cast<JSContext*>(arg);
+
+ // If profiling is not enabled, don't do anything.
+ if (!cx->spsProfiler.enabled())
+ return;
+
+ JS::ProfilingFrameIterator::RegisterState state;
+ state.pc = pc;
+#if defined(JS_SIMULATOR_ARM)
+ state.sp = (void*)sim->get_register(jit::Simulator::sp);
+ state.lr = (void*)sim->get_register(jit::Simulator::lr);
+#elif defined(JS_SIMULATOR_MIPS64)
+ state.sp = (void*)sim->getRegister(jit::Simulator::sp);
+ state.lr = (void*)sim->getRegister(jit::Simulator::ra);
+#endif
+
+ mozilla::DebugOnly<void*> lastStackAddress = nullptr;
+ StackChars stack;
+ uint32_t frameNo = 0;
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
+ MOZ_ASSERT(i.stackAddress() != nullptr);
+ MOZ_ASSERT(lastStackAddress <= i.stackAddress());
+ lastStackAddress = i.stackAddress();
+ JS::ProfilingFrameIterator::Frame frames[16];
+ uint32_t nframes = i.extractStack(frames, 0, 16);
+ for (uint32_t i = 0; i < nframes; i++) {
+ if (frameNo > 0) {
+ if (!stack.append(",", 1))
+ oomUnsafe.crash("stack.append");
+ }
+ auto chars = frames[i].label.get();
+ if (!stack.append(chars, strlen(chars)))
+ oomUnsafe.crash("stack.append");
+ frameNo++;
+ }
+ }
+
+ // Only append the stack if it differs from the last stack.
+ if (stacks.empty() ||
+ stacks.back().length() != stack.length() ||
+ !PodEqual(stacks.back().begin(), stack.begin(), stack.length()))
+ {
+ if (!stacks.append(Move(stack)))
+ oomUnsafe.crash("stacks.append");
+ }
+}
+#endif
+
+static bool
+EnableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp)
+{
+#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ jit::Simulator* sim = cx->runtime()->simulator();
+ sim->enable_single_stepping(SingleStepCallback, cx);
+
+ args.rval().setUndefined();
+ return true;
+#else
+ JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
+ return false;
+#endif
+}
+
+static bool
+DisableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp)
+{
+#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ jit::Simulator* sim = cx->runtime()->simulator();
+ sim->disable_single_stepping();
+
+ AutoValueVector elems(cx);
+ for (size_t i = 0; i < stacks.length(); i++) {
+ JSString* stack = JS_NewUCStringCopyN(cx, stacks[i].begin(), stacks[i].length());
+ if (!stack)
+ return false;
+ if (!elems.append(StringValue(stack)))
+ return false;
+ }
+
+ JSObject* array = JS_NewArrayObject(cx, elems);
+ if (!array)
+ return false;
+
+ stacks.clear();
+ args.rval().setObject(*array);
+ return true;
+#else
+ JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
+ return false;
+#endif
+}
+
+static bool
+IsLatin1(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ bool isLatin1 = args.get(0).isString() && args[0].toString()->hasLatin1Chars();
+ args.rval().setBoolean(isLatin1);
+ return true;
+}
+
+static bool
+EnableSPSProfiling(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ ShellContext* sc = GetShellContext(cx);
+
+ // Disable before re-enabling; see the assertion in |SPSProfiler::setProfilingStack|.
+ if (cx->spsProfiler.installed())
+ cx->spsProfiler.enable(false);
+
+ SetContextProfilingStack(cx, sc->spsProfilingStack, &sc->spsProfilingStackSize,
+ ShellContext::SpsProfilingMaxStackSize);
+ cx->spsProfiler.enableSlowAssertions(false);
+ cx->spsProfiler.enable(true);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+EnableSPSProfilingWithSlowAssertions(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+
+ ShellContext* sc = GetShellContext(cx);
+
+ if (cx->spsProfiler.enabled()) {
+ // If profiling already enabled with slow assertions disabled,
+ // this is a no-op.
+ if (cx->spsProfiler.slowAssertionsEnabled())
+ return true;
+
+ // Slow assertions are off. Disable profiling before re-enabling
+ // with slow assertions on.
+ cx->spsProfiler.enable(false);
+ }
+
+ // Disable before re-enabling; see the assertion in |SPSProfiler::setProfilingStack|.
+ if (cx->spsProfiler.installed())
+ cx->spsProfiler.enable(false);
+
+ SetContextProfilingStack(cx, sc->spsProfilingStack, &sc->spsProfilingStackSize,
+ ShellContext::SpsProfilingMaxStackSize);
+ cx->spsProfiler.enableSlowAssertions(true);
+ cx->spsProfiler.enable(true);
+
+ return true;
+}
+
+static bool
+DisableSPSProfiling(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (cx->runtime()->spsProfiler.installed())
+ cx->runtime()->spsProfiler.enable(false);
+ args.rval().setUndefined();
+ return true;
+}
+
+// Global mailbox that is used to communicate a SharedArrayBuffer
+// value from one worker to another.
+//
+// For simplicity we store only the SharedArrayRawBuffer; retaining
+// the SAB object would require per-runtime storage, and would have no
+// real benefits.
+//
+// Invariant: when a SARB is in the mailbox its reference count is at
+// least 1, accounting for the reference from the mailbox.
+//
+// The lock guards the mailbox variable and prevents a race where two
+// workers try to set the mailbox at the same time to replace a SARB
+// that is only referenced from the mailbox: the workers will both
+// decrement the reference count on the old SARB, and one of those
+// decrements will be on a garbage object. We could implement this
+// with atomics and a CAS loop but it's not worth the bother.
+
+static Mutex* sharedArrayBufferMailboxLock;
+static SharedArrayRawBuffer* sharedArrayBufferMailbox;
+
+static bool
+InitSharedArrayBufferMailbox()
+{
+ sharedArrayBufferMailboxLock = js_new<Mutex>(mutexid::ShellArrayBufferMailbox);
+ return sharedArrayBufferMailboxLock != nullptr;
+}
+
+static void
+DestructSharedArrayBufferMailbox()
+{
+ // All workers need to have terminated at this point.
+ if (sharedArrayBufferMailbox)
+ sharedArrayBufferMailbox->dropReference();
+ js_delete(sharedArrayBufferMailboxLock);
+}
+
+static bool
+GetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JSObject* newObj = nullptr;
+ bool rval = true;
+
+ sharedArrayBufferMailboxLock->lock();
+ SharedArrayRawBuffer* buf = sharedArrayBufferMailbox;
+ if (buf) {
+ buf->addReference();
+ // Shared memory is enabled globally in the shell: there can't be a worker
+ // that does not enable it if the main thread has it.
+ MOZ_ASSERT(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
+ newObj = SharedArrayBufferObject::New(cx, buf);
+ if (!newObj) {
+ buf->dropReference();
+ rval = false;
+ }
+ }
+ sharedArrayBufferMailboxLock->unlock();
+
+ args.rval().setObjectOrNull(newObj);
+ return rval;
+}
+
+static bool
+SetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ SharedArrayRawBuffer* newBuffer = nullptr;
+
+ if (argc == 0 || args.get(0).isNullOrUndefined()) {
+ // Clear out the mailbox
+ }
+ else if (args.get(0).isObject() && args[0].toObject().is<SharedArrayBufferObject>()) {
+ newBuffer = args[0].toObject().as<SharedArrayBufferObject>().rawBufferObject();
+ newBuffer->addReference();
+ } else {
+ JS_ReportErrorASCII(cx, "Only a SharedArrayBuffer can be installed in the global mailbox");
+ return false;
+ }
+
+ sharedArrayBufferMailboxLock->lock();
+ SharedArrayRawBuffer* oldBuffer = sharedArrayBufferMailbox;
+ if (oldBuffer)
+ oldBuffer->dropReference();
+ sharedArrayBufferMailbox = newBuffer;
+ sharedArrayBufferMailboxLock->unlock();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+class SprintOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
+{
+ Sprinter* sp;
+ bool startedTypes_;
+ bool hadError_;
+
+ public:
+ explicit SprintOptimizationTypeInfoOp(Sprinter* sp)
+ : sp(sp),
+ startedTypes_(false),
+ hadError_(false)
+ { }
+
+ void readType(const char* keyedBy, const char* name,
+ const char* location, Maybe<unsigned> lineno) override
+ {
+ if (hadError_)
+ return;
+
+ do {
+ if (!startedTypes_) {
+ startedTypes_ = true;
+ if (sp->put("{\"typeset\": [") < 0)
+ break;
+ }
+
+ if (!sp->jsprintf("{\"keyedBy\":\"%s\"", keyedBy))
+ break;
+
+ if (name) {
+ if (!sp->jsprintf(",\"name\":\"%s\"", name))
+ break;
+ }
+
+ if (location) {
+ char buf[512];
+ PutEscapedString(buf, mozilla::ArrayLength(buf), location, strlen(location), '"');
+ if (!sp->jsprintf(",\"location\":%s", buf))
+ break;
+ }
+ if (lineno.isSome()) {
+ if (!sp->jsprintf(",\"line\":%u", *lineno))
+ break;
+ }
+ if (sp->put("},") < 0)
+ break;
+
+ return;
+ } while (false);
+
+ hadError_ = true;
+ }
+
+ void operator()(JS::TrackedTypeSite site, const char* mirType) override {
+ if (hadError_)
+ return;
+
+ do {
+ if (startedTypes_) {
+ // Clear trailing ,
+ if ((*sp)[sp->getOffset() - 1] == ',')
+ (*sp)[sp->getOffset() - 1] = ' ';
+ if (sp->put("],") < 0)
+ break;
+
+ startedTypes_ = false;
+ } else {
+ if (sp->put("{") < 0)
+ break;
+ }
+
+ if (!sp->jsprintf("\"site\":\"%s\",\"mirType\":\"%s\"},",
+ TrackedTypeSiteString(site), mirType))
+ {
+ break;
+ }
+
+ return;
+ } while (false);
+
+ hadError_ = true;
+ }
+
+ bool hadError() const {
+ return hadError_;
+ }
+};
+
+class SprintOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
+{
+ Sprinter* sp;
+ bool hadError_;
+
+ public:
+ explicit SprintOptimizationAttemptsOp(Sprinter* sp)
+ : sp(sp), hadError_(false)
+ { }
+
+ void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
+ if (hadError_)
+ return;
+
+ hadError_ = !sp->jsprintf("{\"strategy\":\"%s\",\"outcome\":\"%s\"},",
+ TrackedStrategyString(strategy), TrackedOutcomeString(outcome));
+ }
+
+ bool hadError() const {
+ return hadError_;
+ }
+};
+
+static bool
+ReflectTrackedOptimizations(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+ JSRuntime* rt = cx->runtime();
+
+ if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(rt)) {
+ JS_ReportErrorASCII(cx, "Optimization tracking is off.");
+ return false;
+ }
+
+ if (args.length() != 1) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+ ReportUsageErrorASCII(cx, callee, "Argument must be a function");
+ return false;
+ }
+
+ RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
+ if (!fun->hasScript() || !fun->nonLazyScript()->hasIonScript()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // Suppress GC for the unrooted JitcodeGlobalEntry below.
+ gc::AutoSuppressGC suppress(cx);
+
+ jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
+ jit::IonScript* ion = fun->nonLazyScript()->ionScript();
+ jit::JitcodeGlobalEntry& entry = table->lookupInfallible(ion->method()->raw());
+
+ if (!entry.hasTrackedOptimizations()) {
+ JSObject* obj = JS_NewPlainObject(cx);
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ Sprinter sp(cx);
+ if (!sp.init())
+ return false;
+
+ const jit::IonTrackedOptimizationsRegionTable* regions =
+ entry.ionEntry().trackedOptimizationsRegionTable();
+
+ if (sp.put("{\"regions\": [") < 0)
+ return false;
+
+ for (uint32_t i = 0; i < regions->numEntries(); i++) {
+ jit::IonTrackedOptimizationsRegion region = regions->entry(i);
+ jit::IonTrackedOptimizationsRegion::RangeIterator iter = region.ranges();
+ while (iter.more()) {
+ uint32_t startOffset, endOffset;
+ uint8_t index;
+ iter.readNext(&startOffset, &endOffset, &index);
+
+ JSScript* script;
+ jsbytecode* pc;
+ // Use endOffset, as startOffset may be associated with a
+ // previous, adjacent region ending exactly at startOffset. That
+ // is, suppose we have two regions [0, startOffset], [startOffset,
+ // endOffset]. Since we are not querying a return address, we want
+ // the second region and not the first.
+ uint8_t* addr = ion->method()->raw() + endOffset;
+ entry.youngestFrameLocationAtAddr(rt, addr, &script, &pc);
+
+ if (!sp.jsprintf("{\"location\":\"%s:%" PRIuSIZE "\",\"offset\":%" PRIuSIZE ",\"index\":%u}%s",
+ script->filename(), script->lineno(), script->pcToOffset(pc), index,
+ iter.more() ? "," : ""))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (sp.put("],") < 0)
+ return false;
+
+ if (sp.put("\"opts\": [") < 0)
+ return false;
+
+ for (uint8_t i = 0; i < entry.ionEntry().numOptimizationAttempts(); i++) {
+ if (!sp.jsprintf("%s{\"typeinfo\":[", i == 0 ? "" : ","))
+ return false;
+
+ SprintOptimizationTypeInfoOp top(&sp);
+ jit::IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(top);
+ entry.trackedOptimizationTypeInfo(i).forEach(adapter, entry.allTrackedTypes());
+ if (top.hadError())
+ return false;
+
+ // Clear the trailing ,
+ if (sp[sp.getOffset() - 1] == ',')
+ sp[sp.getOffset() - 1] = ' ';
+
+ if (sp.put("],\"attempts\":[") < 0)
+ return false;
+
+ SprintOptimizationAttemptsOp aop(&sp);
+ entry.trackedOptimizationAttempts(i).forEach(aop);
+ if (aop.hadError())
+ return false;
+
+ // Clear the trailing ,
+ if (sp[sp.getOffset() - 1] == ',')
+ sp[sp.getOffset() - 1] = ' ';
+
+ if (sp.put("]}") < 0)
+ return false;
+ }
+
+ if (sp.put("]}") < 0)
+ return false;
+
+ if (sp.hadOutOfMemory())
+ return false;
+
+ RootedString str(cx, JS_NewStringCopyZ(cx, sp.string()));
+ if (!str)
+ return false;
+ RootedValue jsonVal(cx);
+ if (!JS_ParseJSON(cx, str, &jsonVal))
+ return false;
+
+ args.rval().set(jsonVal);
+ return true;
+}
+
+static bool
+DumpScopeChain(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() != 1) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isObject() ||
+ !(args[0].toObject().is<JSFunction>() || args[0].toObject().is<ModuleObject>()))
+ {
+ ReportUsageErrorASCII(cx, callee, "Argument must be an interpreted function or a module");
+ return false;
+ }
+
+ RootedObject obj(cx, &args[0].toObject());
+ RootedScript script(cx);
+
+ if (obj->is<JSFunction>()) {
+ RootedFunction fun(cx, &obj->as<JSFunction>());
+ if (!fun->isInterpreted()) {
+ ReportUsageErrorASCII(cx, callee, "Argument must be an interpreted function");
+ return false;
+ }
+ script = fun->getOrCreateScript(cx);
+ } else {
+ script = obj->as<ModuleObject>().script();
+ }
+
+ script->bodyScope()->dump();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+namespace js {
+namespace shell {
+
+class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
+ Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
+ bool oom;
+ bool enteredWithoutExit;
+
+ public:
+ explicit ShellAutoEntryMonitor(JSContext *cx)
+ : AutoEntryMonitor(cx),
+ oom(false),
+ enteredWithoutExit(false)
+ { }
+
+ ~ShellAutoEntryMonitor() {
+ MOZ_ASSERT(!enteredWithoutExit);
+ }
+
+ void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
+ const char* asyncCause) override {
+ MOZ_ASSERT(!enteredWithoutExit);
+ enteredWithoutExit = true;
+
+ RootedString displayId(cx, JS_GetFunctionDisplayId(function));
+ if (displayId) {
+ UniqueChars displayIdStr(JS_EncodeStringToUTF8(cx, displayId));
+ if (!displayIdStr) {
+ // We report OOM in buildResult.
+ cx->recoverFromOutOfMemory();
+ oom = true;
+ return;
+ }
+ oom = !log.append(mozilla::Move(displayIdStr));
+ return;
+ }
+
+ oom = !log.append(DuplicateString("anonymous"));
+ }
+
+ void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
+ const char* asyncCause) override {
+ MOZ_ASSERT(!enteredWithoutExit);
+ enteredWithoutExit = true;
+
+ UniqueChars label(JS_smprintf("eval:%s", JS_GetScriptFilename(script)));
+ oom = !label || !log.append(mozilla::Move(label));
+ }
+
+ void Exit(JSContext* cx) override {
+ MOZ_ASSERT(enteredWithoutExit);
+ enteredWithoutExit = false;
+ }
+
+ bool buildResult(JSContext *cx, MutableHandleValue resultValue) {
+ if (oom) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ RootedObject result(cx, JS_NewArrayObject(cx, log.length()));
+ if (!result)
+ return false;
+
+ for (size_t i = 0; i < log.length(); i++) {
+ char *name = log[i].get();
+ RootedString string(cx, Atomize(cx, name, strlen(name)));
+ if (!string)
+ return false;
+ RootedValue value(cx, StringValue(string));
+ if (!JS_SetElement(cx, result, i, value))
+ return false;
+ }
+
+ resultValue.setObject(*result.get());
+ return true;
+ }
+};
+
+} // namespace shell
+} // namespace js
+
+static bool
+EntryPoints(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedObject opts(cx, ToObject(cx, args[0]));
+ if (!opts)
+ return false;
+
+ // { function: f } --- Call f.
+ {
+ RootedValue fun(cx), dummy(cx);
+
+ if (!JS_GetProperty(cx, opts, "function", &fun))
+ return false;
+ if (!fun.isUndefined()) {
+ js::shell::ShellAutoEntryMonitor sarep(cx);
+ if (!Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), &dummy))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { object: o, property: p, value: v } --- Fetch o[p], or if
+ // v is present, assign o[p] = v.
+ {
+ RootedValue objectv(cx), propv(cx), valuev(cx);
+
+ if (!JS_GetProperty(cx, opts, "object", &objectv) ||
+ !JS_GetProperty(cx, opts, "property", &propv))
+ return false;
+ if (!objectv.isUndefined() && !propv.isUndefined()) {
+ RootedObject object(cx, ToObject(cx, objectv));
+ if (!object)
+ return false;
+
+ RootedString string(cx, ToString(cx, propv));
+ if (!string)
+ return false;
+ RootedId id(cx);
+ if (!JS_StringToId(cx, string, &id))
+ return false;
+
+ if (!JS_GetProperty(cx, opts, "value", &valuev))
+ return false;
+
+ js::shell::ShellAutoEntryMonitor sarep(cx);
+
+ if (!valuev.isUndefined()) {
+ if (!JS_SetPropertyById(cx, object, id, valuev))
+ return false;
+ } else {
+ if (!JS_GetPropertyById(cx, object, id, &valuev))
+ return false;
+ }
+
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { ToString: v } --- Apply JS::ToString to v.
+ {
+ RootedValue v(cx);
+
+ if (!JS_GetProperty(cx, opts, "ToString", &v))
+ return false;
+ if (!v.isUndefined()) {
+ js::shell::ShellAutoEntryMonitor sarep(cx);
+ if (!JS::ToString(cx, v))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { ToNumber: v } --- Apply JS::ToNumber to v.
+ {
+ RootedValue v(cx);
+ double dummy;
+
+ if (!JS_GetProperty(cx, opts, "ToNumber", &v))
+ return false;
+ if (!v.isUndefined()) {
+ js::shell::ShellAutoEntryMonitor sarep(cx);
+ if (!JS::ToNumber(cx, v, &dummy))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ // { eval: code } --- Apply ToString and then Evaluate to code.
+ {
+ RootedValue code(cx), dummy(cx);
+
+ if (!JS_GetProperty(cx, opts, "eval", &code))
+ return false;
+ if (!code.isUndefined()) {
+ RootedString codeString(cx, ToString(cx, code));
+ if (!codeString || !codeString->ensureFlat(cx))
+ return false;
+
+ AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, codeString))
+ return false;
+ const char16_t* chars = stableChars.twoByteRange().begin().get();
+ size_t length = codeString->length();
+
+ CompileOptions options(cx);
+ options.setIntroductionType("entryPoint eval")
+ .setFileAndLine("entryPoint eval", 1);
+
+ js::shell::ShellAutoEntryMonitor sarep(cx);
+ if (!JS::Evaluate(cx, options, chars, length, &dummy))
+ return false;
+ return sarep.buildResult(cx, args.rval());
+ }
+ }
+
+ JS_ReportErrorASCII(cx, "bad 'params' object");
+ return false;
+}
+
+static bool
+SetARMHwCapFlags(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 1) {
+ JS_ReportErrorASCII(cx, "Wrong number of arguments");
+ return false;
+ }
+
+ RootedString flagsListString(cx, JS::ToString(cx, args.get(0)));
+ if (!flagsListString)
+ return false;
+
+#if defined(JS_CODEGEN_ARM)
+ JSAutoByteString flagsList(cx, flagsListString);
+ if (!flagsList)
+ return false;
+
+ jit::ParseARMHwCapFlags(flagsList.ptr());
+#endif
+
+ args.rval().setUndefined();
+ return true;
+}
+
+#ifndef __AFL_HAVE_MANUAL_CONTROL
+# define __AFL_LOOP(x) true
+#endif
+
+static bool
+WasmLoop(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+
+ if (args.length() < 1 || args.length() > 2) {
+ ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ ReportUsageErrorASCII(cx, callee, "First argument must be a String");
+ return false;
+ }
+
+ RootedObject importObj(cx);
+ if (!args.get(1).isUndefined()) {
+ if (!args.get(1).isObject()) {
+ ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be an Object");
+ return false;
+ }
+ importObj = &args[1].toObject();
+ }
+
+ RootedString givenPath(cx, args[0].toString());
+ RootedString filename(cx, ResolvePath(cx, givenPath, RootRelative));
+ if (!filename)
+ return false;
+
+ while (__AFL_LOOP(1000)) {
+ Rooted<JSObject*> ret(cx, FileAsTypedArray(cx, filename));
+ if (!ret)
+ return false;
+
+ Rooted<TypedArrayObject*> typedArray(cx, &ret->as<TypedArrayObject>());
+ RootedWasmInstanceObject instanceObj(cx);
+ if (!wasm::Eval(cx, typedArray, importObj, &instanceObj)) {
+ // Clear any pending exceptions, we don't care about them
+ cx->clearPendingException();
+ }
+ }
+
+ return true;
+}
+
+static const JSFunctionSpecWithHelp shell_functions[] = {
+ JS_FN_HELP("version", Version, 0, 0,
+"version([number])",
+" Get or force a script compilation version number."),
+
+ JS_FN_HELP("options", Options, 0, 0,
+"options([option ...])",
+" Get or toggle JavaScript options."),
+
+ JS_FN_HELP("load", Load, 1, 0,
+"load(['foo.js' ...])",
+" Load files named by string arguments. Filename is relative to the\n"
+" current working directory."),
+
+ JS_FN_HELP("loadRelativeToScript", LoadScriptRelativeToScript, 1, 0,
+"loadRelativeToScript(['foo.js' ...])",
+" Load files named by string arguments. Filename is relative to the\n"
+" calling script."),
+
+ JS_FN_HELP("evaluate", Evaluate, 2, 0,
+"evaluate(code[, options])",
+" Evaluate code as though it were the contents of a file.\n"
+" options is an optional object that may have these properties:\n"
+" isRunOnce: use the isRunOnce compiler option (default: false)\n"
+" noScriptRval: use the no-script-rval compiler option (default: false)\n"
+" fileName: filename for error messages and debug info\n"
+" lineNumber: starting line number for error messages and debug info\n"
+" columnNumber: starting column number for error messages and debug info\n"
+" global: global in which to execute the code\n"
+" newContext: if true, create and use a new cx (default: false)\n"
+" catchTermination: if true, catch termination (failure without\n"
+" an exception value, as for slow scripts or out-of-memory)\n"
+" and return 'terminated'\n"
+" element: if present with value |v|, convert |v| to an object |o| and\n"
+" mark the source as being attached to the DOM element |o|. If the\n"
+" property is omitted or |v| is null, don't attribute the source to\n"
+" any DOM element.\n"
+" elementAttributeName: if present and not undefined, the name of\n"
+" property of 'element' that holds this code. This is what\n"
+" Debugger.Source.prototype.elementAttributeName returns.\n"
+" sourceMapURL: if present with value |v|, convert |v| to a string, and\n"
+" provide that as the code's source map URL. If omitted, attach no\n"
+" source map URL to the code (although the code may provide one itself,\n"
+" via a //#sourceMappingURL comment).\n"
+" sourceIsLazy: if present and true, indicates that, after compilation, \n"
+ "script source should not be cached by the JS engine and should be \n"
+ "lazily loaded from the embedding as-needed.\n"
+" loadBytecode: if true, and if the source is a CacheEntryObject,\n"
+" the bytecode would be loaded and decoded from the cache entry instead\n"
+" of being parsed, then it would be executed as usual.\n"
+" saveBytecode: if true, and if the source is a CacheEntryObject,\n"
+" the bytecode would be encoded and saved into the cache entry after\n"
+" the script execution.\n"
+" assertEqBytecode: if true, and if both loadBytecode and saveBytecode are \n"
+" true, then the loaded bytecode and the encoded bytecode are compared.\n"
+" and an assertion is raised if they differ.\n"
+),
+
+ JS_FN_HELP("run", Run, 1, 0,
+"run('foo.js')",
+" Run the file named by the first argument, returning the number of\n"
+" of milliseconds spent compiling and executing it."),
+
+ JS_FN_HELP("readline", ReadLine, 0, 0,
+"readline()",
+" Read a single line from stdin."),
+
+ JS_FN_HELP("readlineBuf", ReadLineBuf, 1, 0,
+"readlineBuf([ buf ])",
+" Emulate readline() on the specified string. The first call with a string\n"
+" argument sets the source buffer. Subsequent calls without an argument\n"
+" then read from this buffer line by line.\n"),
+
+ JS_FN_HELP("print", Print, 0, 0,
+"print([exp ...])",
+" Evaluate and print expressions to stdout."),
+
+ JS_FN_HELP("printErr", PrintErr, 0, 0,
+"printErr([exp ...])",
+" Evaluate and print expressions to stderr."),
+
+ JS_FN_HELP("putstr", PutStr, 0, 0,
+"putstr([exp])",
+" Evaluate and print expression without newline."),
+
+ JS_FN_HELP("dateNow", Now, 0, 0,
+"dateNow()",
+" Return the current time with sub-ms precision."),
+
+ JS_FN_HELP("help", Help, 0, 0,
+"help([name ...])",
+" Display usage and help messages."),
+
+ JS_FN_HELP("quit", Quit, 0, 0,
+"quit()",
+" Quit the shell."),
+
+ JS_FN_HELP("assertEq", AssertEq, 2, 0,
+"assertEq(actual, expected[, msg])",
+" Throw if the first two arguments are not the same (both +0 or both -0,\n"
+" both NaN, or non-zero and ===)."),
+
+ JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0,
+"startTimingMutator()",
+" Start accounting time to mutator vs GC."),
+
+ JS_FN_HELP("stopTimingMutator", StopTimingMutator, 0, 0,
+"stopTimingMutator()",
+" Stop accounting time to mutator vs GC and dump the results."),
+
+ JS_FN_HELP("throwError", ThrowError, 0, 0,
+"throwError()",
+" Throw an error from JS_ReportError."),
+
+#ifdef DEBUG
+ JS_FN_HELP("disassemble", DisassembleToString, 1, 0,
+"disassemble([fun/code])",
+" Return the disassembly for the given function or code.\n"
+" All disassembly functions take these options as leading string arguments:\n"
+" \"-r\" (disassemble recursively)\n"
+" \"-l\" (show line numbers)\n"
+" \"-S\" (omit source notes)"),
+
+ JS_FN_HELP("dis", Disassemble, 1, 0,
+"dis([fun/code])",
+" Disassemble functions into bytecodes."),
+
+ JS_FN_HELP("disfile", DisassFile, 1, 0,
+"disfile('foo.js')",
+" Disassemble script file into bytecodes.\n"),
+
+ JS_FN_HELP("dissrc", DisassWithSrc, 1, 0,
+"dissrc([fun/code])",
+" Disassemble functions with source lines."),
+
+ JS_FN_HELP("notes", Notes, 1, 0,
+"notes([fun])",
+" Show source notes for functions."),
+
+ JS_FN_HELP("stackDump", StackDump, 3, 0,
+"stackDump(showArgs, showLocals, showThisProps)",
+" Tries to print a lot of information about the current stack. \n"
+" Similar to the DumpJSStack() function in the browser."),
+
+#endif
+ JS_FN_HELP("intern", Intern, 1, 0,
+"intern(str)",
+" Internalize str in the atom table."),
+
+ JS_FN_HELP("getslx", GetSLX, 1, 0,
+"getslx(obj)",
+" Get script line extent."),
+
+ JS_FN_HELP("evalcx", EvalInContext, 1, 0,
+"evalcx(s[, o])",
+" Evaluate s in optional sandbox object o.\n"
+" if (s == '' && !o) return new o with eager standard classes\n"
+" if (s == 'lazy' && !o) return new o with lazy standard classes"),
+
+ JS_FN_HELP("evalInWorker", EvalInWorker, 1, 0,
+"evalInWorker(str)",
+" Evaluate 'str' in a separate thread with its own runtime.\n"),
+
+ JS_FN_HELP("getSharedArrayBuffer", GetSharedArrayBuffer, 0, 0,
+"getSharedArrayBuffer()",
+" Retrieve the SharedArrayBuffer object from the cross-worker mailbox.\n"
+" The object retrieved may not be identical to the object that was\n"
+" installed, but it references the same shared memory.\n"
+" getSharedArrayBuffer performs an ordering memory barrier.\n"),
+
+ JS_FN_HELP("setSharedArrayBuffer", SetSharedArrayBuffer, 0, 0,
+"setSharedArrayBuffer()",
+" Install the SharedArrayBuffer object in the cross-worker mailbox.\n"
+" setSharedArrayBuffer performs an ordering memory barrier.\n"),
+
+ JS_FN_HELP("shapeOf", ShapeOf, 1, 0,
+"shapeOf(obj)",
+" Get the shape of obj (an implementation detail)."),
+
+ JS_FN_HELP("groupOf", GroupOf, 1, 0,
+"groupOf(obj)",
+" Get the group of obj (an implementation detail)."),
+
+ JS_FN_HELP("unwrappedObjectsHaveSameShape", UnwrappedObjectsHaveSameShape, 2, 0,
+"unwrappedObjectsHaveSameShape(obj1, obj2)",
+" Returns true iff obj1 and obj2 have the same shape, false otherwise. Both\n"
+" objects are unwrapped first, so this can be used on objects from different\n"
+" globals."),
+
+#ifdef DEBUG
+ JS_FN_HELP("arrayInfo", ArrayInfo, 1, 0,
+"arrayInfo(a1, a2, ...)",
+" Report statistics about arrays."),
+#endif
+
+ JS_FN_HELP("sleep", Sleep_fn, 1, 0,
+"sleep(dt)",
+" Sleep for dt seconds."),
+
+ JS_FN_HELP("compile", Compile, 1, 0,
+"compile(code)",
+" Compiles a string to bytecode, potentially throwing."),
+
+ JS_FN_HELP("parseModule", ParseModule, 1, 0,
+"parseModule(code)",
+" Parses source text as a module and returns a Module object."),
+
+ JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0,
+"setModuleResolveHook(function(module, specifier) {})",
+" Set the HostResolveImportedModule hook to |function|.\n"
+" This hook is used to look up a previously loaded module object. It should\n"
+" be implemented by the module loader."),
+
+ JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0,
+"getModuleLoadPath()",
+" Return any --module-load-path argument passed to the shell. Used by the\n"
+" module loader.\n"),
+
+ JS_FN_HELP("parse", Parse, 1, 0,
+"parse(code)",
+" Parses a string, potentially throwing."),
+
+ JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
+"syntaxParse(code)",
+" Check the syntax of a string, returning success value"),
+
+ JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
+"offThreadCompileScript(code[, options])",
+" Compile |code| on a helper thread. To wait for the compilation to finish\n"
+" and run the code, call |runOffThreadScript|. If present, |options| may\n"
+" have properties saying how the code should be compiled:\n"
+" noScriptRval: use the no-script-rval compiler option (default: false)\n"
+" fileName: filename for error messages and debug info\n"
+" lineNumber: starting line number for error messages and debug info\n"
+" columnNumber: starting column number for error messages and debug info\n"
+" element: if present with value |v|, convert |v| to an object |o| and\n"
+" mark the source as being attached to the DOM element |o|. If the\n"
+" property is omitted or |v| is null, don't attribute the source to\n"
+" any DOM element.\n"
+" elementAttributeName: if present and not undefined, the name of\n"
+" property of 'element' that holds this code. This is what\n"
+" Debugger.Source.prototype.elementAttributeName returns.\n"),
+
+ JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
+"runOffThreadScript()",
+" Wait for off-thread compilation to complete. If an error occurred,\n"
+" throw the appropriate exception; otherwise, run the script and return\n"
+" its value."),
+
+ JS_FN_HELP("offThreadCompileModule", OffThreadCompileModule, 1, 0,
+"offThreadCompileModule(code)",
+" Compile |code| on a helper thread. To wait for the compilation to finish\n"
+" and get the module object, call |finishOffThreadModule|."),
+
+ JS_FN_HELP("finishOffThreadModule", FinishOffThreadModule, 0, 0,
+"finishOffThreadModule()",
+" Wait for off-thread compilation to complete. If an error occurred,\n"
+" throw the appropriate exception; otherwise, return the module object"),
+
+ JS_FN_HELP("timeout", Timeout, 1, 0,
+"timeout([seconds], [func])",
+" Get/Set the limit in seconds for the execution time for the current context.\n"
+" A negative value (default) means that the execution time is unlimited.\n"
+" If a second argument is provided, it will be invoked when the timer elapses.\n"
+" Calling this function will replace any callback set by |setInterruptCallback|.\n"),
+
+ JS_FN_HELP("interruptIf", InterruptIf, 1, 0,
+"interruptIf(cond)",
+" Requests interrupt callback if cond is true. If a callback function is set via\n"
+" |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."),
+
+ JS_FN_HELP("invokeInterruptCallback", InvokeInterruptCallbackWrapper, 0, 0,
+"invokeInterruptCallback(fun)",
+" Forcefully set the interrupt flag and invoke the interrupt handler. If a\n"
+" callback function is set via |timeout| or |setInterruptCallback|, it will\n"
+" be called. Before returning, fun is called with the return value of the\n"
+" interrupt handler."),
+
+ JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0,
+"setInterruptCallback(func)",
+" Sets func as the interrupt callback function.\n"
+" Calling this function will replace any callback set by |timeout|.\n"),
+
+ JS_FN_HELP("enableLastWarning", EnableLastWarning, 0, 0,
+"enableLastWarning()",
+" Enable storing the last warning."),
+
+ JS_FN_HELP("disableLastWarning", DisableLastWarning, 0, 0,
+"disableLastWarning()",
+" Disable storing the last warning."),
+
+ JS_FN_HELP("getLastWarning", GetLastWarning, 0, 0,
+"getLastWarning()",
+" Returns an object that represents the last warning."),
+
+ JS_FN_HELP("clearLastWarning", ClearLastWarning, 0, 0,
+"clearLastWarning()",
+" Clear the last warning."),
+
+ JS_FN_HELP("elapsed", Elapsed, 0, 0,
+"elapsed()",
+" Execution time elapsed for the current thread."),
+
+ JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0,
+"decompileFunction(func)",
+" Decompile a function."),
+
+ JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0,
+"decompileThis()",
+" Decompile the currently executing script."),
+
+ JS_FN_HELP("thisFilename", ThisFilename, 0, 0,
+"thisFilename()",
+" Return the filename of the current script"),
+
+ JS_FN_HELP("newGlobal", NewGlobal, 1, 0,
+"newGlobal([options])",
+" Return a new global object in a new compartment. If options\n"
+" is given, it may have any of the following properties:\n"
+" sameZoneAs: the compartment will be in the same zone as the given object (defaults to a new zone)\n"
+" invisibleToDebugger: the global will be invisible to the debugger (default false)\n"
+" principal: if present, its value converted to a number must be an\n"
+" integer that fits in 32 bits; use that as the new compartment's\n"
+" principal. Shell principals are toys, meant only for testing; one\n"
+" shell principal subsumes another if its set bits are a superset of\n"
+" the other's. Thus, a principal of 0 subsumes nothing, while a\n"
+" principals of ~0 subsumes all other principals. The absence of a\n"
+" principal is treated as if its bits were 0xffff, for subsumption\n"
+" purposes. If this property is omitted, supply no principal."),
+
+ JS_FN_HELP("nukeCCW", NukeCCW, 1, 0,
+"nukeCCW(wrapper)",
+" Nuke a CrossCompartmentWrapper, which turns it into a DeadProxyObject."),
+
+ JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0,
+"createMappedArrayBuffer(filename, [offset, [size]])",
+" Create an array buffer that mmaps the given file."),
+
+ JS_FN_HELP("addPromiseReactions", AddPromiseReactions, 3, 0,
+"addPromiseReactions(promise, onResolve, onReject)",
+" Calls the JS::AddPromiseReactions JSAPI function with the given arguments."),
+
+ JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
+"getMaxArgs()",
+" Return the maximum number of supported args for a call."),
+
+ JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0,
+"objectEmulatingUndefined()",
+" Return a new object obj for which typeof obj === \"undefined\", obj == null\n"
+" and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"),
+
+ JS_FN_HELP("isCachingEnabled", IsCachingEnabled, 0, 0,
+"isCachingEnabled()",
+" Return whether JS caching is enabled."),
+
+ JS_FN_HELP("setCachingEnabled", SetCachingEnabled, 1, 0,
+"setCachingEnabled(b)",
+" Enable or disable JS caching."),
+
+ JS_FN_HELP("cacheEntry", CacheEntry, 1, 0,
+"cacheEntry(code)",
+" Return a new opaque object which emulates a cache entry of a script. This\n"
+" object encapsulates the code and its cached content. The cache entry is filled\n"
+" and read by the \"evaluate\" function by using it in place of the source, and\n"
+" by setting \"saveBytecode\" and \"loadBytecode\" options."),
+
+ JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0,
+"printProfilerEvents()",
+" Register a callback with the profiler that prints javascript profiler events\n"
+" to stderr. Callback is only registered if profiling is enabled."),
+
+ JS_FN_HELP("enableSingleStepProfiling", EnableSingleStepProfiling, 0, 0,
+"enableSingleStepProfiling()",
+" This function will fail on platforms that don't support single-step profiling\n"
+" (currently everything but ARM-simulator). When enabled, at every instruction a\n"
+" backtrace will be recorded and stored in an array. Adjacent duplicate backtraces\n"
+" are discarded."),
+
+ JS_FN_HELP("disableSingleStepProfiling", DisableSingleStepProfiling, 0, 0,
+"disableSingleStepProfiling()",
+" Return the array of backtraces recorded by enableSingleStepProfiling."),
+
+ JS_FN_HELP("enableSPSProfiling", EnableSPSProfiling, 0, 0,
+"enableSPSProfiling()",
+" Enables SPS instrumentation and corresponding assertions, with slow\n"
+" assertions disabled.\n"),
+
+ JS_FN_HELP("enableSPSProfilingWithSlowAssertions", EnableSPSProfilingWithSlowAssertions, 0, 0,
+"enableSPSProfilingWithSlowAssertions()",
+" Enables SPS instrumentation and corresponding assertions, with slow\n"
+" assertions enabled.\n"),
+
+ JS_FN_HELP("disableSPSProfiling", DisableSPSProfiling, 0, 0,
+"disableSPSProfiling()",
+" Disables SPS instrumentation"),
+
+ JS_FN_HELP("isLatin1", IsLatin1, 1, 0,
+"isLatin1(s)",
+" Return true iff the string's characters are stored as Latin1."),
+
+ JS_FN_HELP("stackPointerInfo", StackPointerInfo, 0, 0,
+"stackPointerInfo()",
+" Return an int32 value which corresponds to the offset of the latest stack\n"
+" pointer, such that one can take the differences of 2 to estimate a frame-size."),
+
+ JS_FN_HELP("entryPoints", EntryPoints, 1, 0,
+"entryPoints(params)",
+"Carry out some JSAPI operation as directed by |params|, and return an array of\n"
+"objects describing which JavaScript entry points were invoked as a result.\n"
+"|params| is an object whose properties indicate what operation to perform. Here\n"
+"are the recognized groups of properties:\n"
+"\n"
+"{ function }: Call the object |params.function| with no arguments.\n"
+"\n"
+"{ object, property }: Fetch the property named |params.property| of\n"
+"|params.object|.\n"
+"\n"
+"{ ToString }: Apply JS::ToString to |params.toString|.\n"
+"\n"
+"{ ToNumber }: Apply JS::ToNumber to |params.toNumber|.\n"
+"\n"
+"{ eval }: Apply JS::Evaluate to |params.eval|.\n"
+"\n"
+"The return value is an array of strings, with one element for each\n"
+"JavaScript invocation that occurred as a result of the given\n"
+"operation. Each element is the name of the function invoked, or the\n"
+"string 'eval:FILENAME' if the code was invoked by 'eval' or something\n"
+"similar.\n"),
+
+ JS_FN_HELP("drainJobQueue", DrainJobQueue, 0, 0,
+"drainJobQueue()",
+"Take jobs from the shell's job queue in FIFO order and run them until the\n"
+"queue is empty.\n"),
+
+ JS_FN_HELP("setPromiseRejectionTrackerCallback", SetPromiseRejectionTrackerCallback, 1, 0,
+"setPromiseRejectionTrackerCallback()",
+"Sets the callback to be invoked whenever a Promise rejection is unhandled\n"
+"or a previously-unhandled rejection becomes handled."),
+
+#ifdef ENABLE_INTL_API
+ JS_FN_HELP("addIntlExtras", AddIntlExtras, 1, 0,
+"addIntlExtras(obj)",
+"Adds various not-yet-standardized Intl functions as properties on the\n"
+"provided object (this should generally be Intl itself). The added\n"
+"functions and their behavior are experimental: don't depend upon them\n"
+"unless you're willing to update your code if these experimental APIs change\n"
+"underneath you."),
+#endif // ENABLE_INTL_API
+
+ JS_FS_HELP_END
+};
+
+static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
+ JS_FN_HELP("clone", Clone, 1, 0,
+"clone(fun[, scope])",
+" Clone function object."),
+
+ JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
+"getSelfHostedValue()",
+" Get a self-hosted value by its name. Note that these values don't get \n"
+" cached, so repeatedly getting the same value creates multiple distinct clones."),
+
+ JS_FN_HELP("line2pc", LineToPC, 0, 0,
+"line2pc([fun,] line)",
+" Map line number to PC."),
+
+ JS_FN_HELP("pc2line", PCToLine, 0, 0,
+"pc2line(fun[, pc])",
+" Map PC to line number."),
+
+ JS_FN_HELP("nestedShell", NestedShell, 0, 0,
+"nestedShell(shellArgs...)",
+" Execute the given code in a new JS shell process, passing this nested shell\n"
+" the arguments passed to nestedShell. argv[0] of the nested shell will be argv[0]\n"
+" of the current shell (which is assumed to be the actual path to the shell.\n"
+" arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n"
+" be argv[2], etc."),
+
+ JS_INLINABLE_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0, TestAssertFloat32,
+"assertFloat32(value, isFloat32)",
+" In IonMonkey only, asserts that value has (resp. hasn't) the MIRType::Float32 if isFloat32 is true (resp. false)."),
+
+ JS_INLINABLE_FN_HELP("assertRecoveredOnBailout", testingFunc_assertRecoveredOnBailout, 2, 0,
+TestAssertRecoveredOnBailout,
+"assertRecoveredOnBailout(var)",
+" In IonMonkey only, asserts that variable has RecoveredOnBailout flag."),
+
+ JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0,
+"withSourceHook(hook, fun)",
+" Set this JS runtime's lazy source retrieval hook (that is, the hook\n"
+" used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n"
+" |hook|; call |fun| with no arguments; and then restore the runtime's\n"
+" original hook. Return or throw whatever |fun| did. |hook| gets\n"
+" passed the requested code's URL, and should return a string.\n"
+"\n"
+" Notes:\n"
+"\n"
+" 1) SpiderMonkey may assert if the returned code isn't close enough\n"
+" to the script's real code, so this function is not fuzzer-safe.\n"
+"\n"
+" 2) The runtime can have only one source retrieval hook active at a\n"
+" time. If |fun| is not careful, |hook| could be asked to retrieve the\n"
+" source code for compilations that occurred long before it was set,\n"
+" and that it knows nothing about. The reverse applies as well: the\n"
+" original hook, that we reinstate after the call to |fun| completes,\n"
+" might be asked for the source code of compilations that |fun|\n"
+" performed, and which, presumably, only |hook| knows how to find.\n"),
+
+ JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0,
+"wrapWithProto(obj)",
+" Wrap an object into a noop wrapper with prototype semantics.\n"
+" Note: This is not fuzzing safe because it can be used to construct\n"
+" deeply nested wrapper chains that cannot exist in the wild."),
+
+ JS_FN_HELP("trackedOpts", ReflectTrackedOptimizations, 1, 0,
+"trackedOpts(fun)",
+" Returns an object describing the tracked optimizations of |fun|, if\n"
+" any. If |fun| is not a scripted function or has not been compiled by\n"
+" Ion, null is returned."),
+
+ JS_FN_HELP("dumpScopeChain", DumpScopeChain, 1, 0,
+"dumpScopeChain(obj)",
+" Prints the scope chain of an interpreted function or a module."),
+
+ JS_FN_HELP("crash", Crash, 0, 0,
+"crash([message])",
+" Crashes the process with a MOZ_CRASH."),
+
+ JS_FN_HELP("setARMHwCapFlags", SetARMHwCapFlags, 1, 0,
+"setARMHwCapFlags(\"flag1,flag2 flag3\")",
+" On non-ARM, no-op. On ARM, set the hardware capabilities. The list of \n"
+" flags is available by calling this function with \"help\" as the flag's name"),
+
+ JS_FN_HELP("wasmLoop", WasmLoop, 2, 0,
+"wasmLoop(filename, imports)",
+" Performs an AFL-style persistent loop reading data from the given file and passing it\n"
+" to the 'wasmEval' function together with the specified imports object."),
+
+ JS_FS_HELP_END
+};
+
+static const JSFunctionSpecWithHelp console_functions[] = {
+ JS_FN_HELP("log", Print, 0, 0,
+"log([exp ...])",
+" Evaluate and print expressions to stdout.\n"
+" This function is an alias of the print() function."),
+ JS_FS_HELP_END
+};
+
+bool
+DefineConsole(JSContext* cx, HandleObject global)
+{
+ RootedObject obj(cx, JS_NewPlainObject(cx));
+ return obj &&
+ JS_DefineFunctionsWithHelp(cx, obj, console_functions) &&
+ JS_DefineProperty(cx, global, "console", obj, 0);
+}
+
+#ifdef MOZ_PROFILING
+# define PROFILING_FUNCTION_COUNT 5
+# ifdef MOZ_CALLGRIND
+# define CALLGRIND_FUNCTION_COUNT 3
+# else
+# define CALLGRIND_FUNCTION_COUNT 0
+# endif
+# ifdef MOZ_VTUNE
+# define VTUNE_FUNCTION_COUNT 4
+# else
+# define VTUNE_FUNCTION_COUNT 0
+# endif
+# define EXTERNAL_FUNCTION_COUNT (PROFILING_FUNCTION_COUNT + CALLGRIND_FUNCTION_COUNT + VTUNE_FUNCTION_COUNT)
+#else
+# define EXTERNAL_FUNCTION_COUNT 0
+#endif
+
+#undef PROFILING_FUNCTION_COUNT
+#undef CALLGRIND_FUNCTION_COUNT
+#undef VTUNE_FUNCTION_COUNT
+#undef EXTERNAL_FUNCTION_COUNT
+
+static bool
+PrintHelpString(JSContext* cx, HandleValue v)
+{
+ JSString* str = v.toString();
+ MOZ_ASSERT(gOutFile->isOpen());
+
+ JSLinearString* linear = str->ensureLinear(cx);
+ if (!linear)
+ return false;
+
+ JS::AutoCheckCannotGC nogc;
+ if (linear->hasLatin1Chars()) {
+ for (const Latin1Char* p = linear->latin1Chars(nogc); *p; p++)
+ fprintf(gOutFile->fp, "%c", char(*p));
+ } else {
+ for (const char16_t* p = linear->twoByteChars(nogc); *p; p++)
+ fprintf(gOutFile->fp, "%c", char(*p));
+ }
+ fprintf(gOutFile->fp, "\n");
+
+ return true;
+}
+
+static bool
+PrintHelp(JSContext* cx, HandleObject obj)
+{
+ RootedValue usage(cx);
+ if (!JS_GetProperty(cx, obj, "usage", &usage))
+ return false;
+ RootedValue help(cx);
+ if (!JS_GetProperty(cx, obj, "help", &help))
+ return false;
+
+ if (!usage.isString() || !help.isString())
+ return true;
+
+ return PrintHelpString(cx, usage) && PrintHelpString(cx, help);
+}
+
+static bool
+PrintEnumeratedHelp(JSContext* cx, HandleObject obj, bool brief)
+{
+ AutoIdVector idv(cx);
+ if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv))
+ return false;
+
+ for (size_t i = 0; i < idv.length(); i++) {
+ RootedValue v(cx);
+ RootedId id(cx, idv[i]);
+ if (!JS_GetPropertyById(cx, obj, id, &v))
+ return false;
+ if (v.isObject()) {
+ RootedObject funcObj(cx, &v.toObject());
+ if (!PrintHelp(cx, funcObj))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+Help(JSContext* cx, unsigned argc, Value* vp)
+{
+ if (!gOutFile->isOpen()) {
+ JS_ReportErrorASCII(cx, "output file is closed");
+ return false;
+ }
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject obj(cx);
+ if (args.length() == 0) {
+ fprintf(gOutFile->fp, "%s\n", JS_GetImplementationVersion());
+
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!PrintEnumeratedHelp(cx, global, false))
+ return false;
+ } else {
+ for (unsigned i = 0; i < args.length(); i++) {
+ if (args[i].isPrimitive()) {
+ JS_ReportErrorASCII(cx, "primitive arg");
+ return false;
+ }
+ obj = args[i].toObjectOrNull();
+ if (!PrintHelp(cx, obj))
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
+#define MSG_DEF(name, count, exception, format) \
+ { #name, format, count, JSEXN_ERR } ,
+#include "jsshell.msg"
+#undef MSG_DEF
+};
+
+const JSErrorFormatString*
+js::shell::my_GetErrorMessage(void* userRef, const unsigned errorNumber)
+{
+ if (errorNumber == 0 || errorNumber >= JSShellErr_Limit)
+ return nullptr;
+
+ return &jsShell_ErrorFormatString[errorNumber];
+}
+
+static bool
+CreateLastWarningObject(JSContext* cx, JSErrorReport* report)
+{
+ RootedObject warningObj(cx, JS_NewObject(cx, nullptr));
+ if (!warningObj)
+ return false;
+
+ RootedString nameStr(cx);
+ if (report->exnType == JSEXN_WARN)
+ nameStr = JS_NewStringCopyZ(cx, "Warning");
+ else
+ nameStr = GetErrorTypeName(cx, report->exnType);
+ if (!nameStr)
+ return false;
+ RootedValue nameVal(cx, StringValue(nameStr));
+ if (!DefineProperty(cx, warningObj, cx->names().name, nameVal))
+ return false;
+
+ RootedString messageStr(cx, report->newMessageString(cx));
+ if (!messageStr)
+ return false;
+ RootedValue messageVal(cx, StringValue(messageStr));
+ if (!DefineProperty(cx, warningObj, cx->names().message, messageVal))
+ return false;
+
+ RootedValue linenoVal(cx, Int32Value(report->lineno));
+ if (!DefineProperty(cx, warningObj, cx->names().lineNumber, linenoVal))
+ return false;
+
+ RootedValue columnVal(cx, Int32Value(report->column));
+ if (!DefineProperty(cx, warningObj, cx->names().columnNumber, columnVal))
+ return false;
+
+ GetShellContext(cx)->lastWarning.setObject(*warningObj);
+ return true;
+}
+
+static FILE*
+ErrorFilePointer()
+{
+ if (gErrFile->isOpen())
+ return gErrFile->fp;
+
+ fprintf(stderr, "error file is closed; falling back to stderr\n");
+ return stderr;
+}
+
+static bool
+PrintStackTrace(JSContext* cx, HandleValue exn)
+{
+ if (!exn.isObject())
+ return false;
+
+ Maybe<JSAutoCompartment> ac;
+ RootedObject exnObj(cx, &exn.toObject());
+ if (IsCrossCompartmentWrapper(exnObj)) {
+ exnObj = UncheckedUnwrap(exnObj);
+ ac.emplace(cx, exnObj);
+ }
+
+ // Ignore non-ErrorObject thrown by |throw| statement.
+ if (!exnObj->is<ErrorObject>())
+ return true;
+
+ // Exceptions thrown while compiling top-level script have no stack.
+ RootedObject stackObj(cx, exnObj->as<ErrorObject>().stack());
+ if (!stackObj)
+ return true;
+
+ RootedString stackStr(cx);
+ if (!BuildStackString(cx, stackObj, &stackStr, 2))
+ return false;
+
+ UniqueChars stack(JS_EncodeStringToUTF8(cx, stackStr));
+ if (!stack)
+ return false;
+
+ FILE* fp = ErrorFilePointer();
+ fputs("Stack:\n", fp);
+ fputs(stack.get(), fp);
+
+ return true;
+}
+
+js::shell::AutoReportException::~AutoReportException()
+{
+ if (!JS_IsExceptionPending(cx))
+ return;
+
+ // Get exception object before printing and clearing exception.
+ RootedValue exn(cx);
+ (void) JS_GetPendingException(cx, &exn);
+
+ JS_ClearPendingException(cx);
+
+ ShellContext* sc = GetShellContext(cx);
+ js::ErrorReport report(cx);
+ if (!report.init(cx, exn, js::ErrorReport::WithSideEffects)) {
+ fprintf(stderr, "out of memory initializing ErrorReport\n");
+ fflush(stderr);
+ JS_ClearPendingException(cx);
+ return;
+ }
+
+ MOZ_ASSERT(!JSREPORT_IS_WARNING(report.report()->flags));
+
+ FILE* fp = ErrorFilePointer();
+ PrintError(cx, fp, report.toStringResult(), report.report(), reportWarnings);
+
+ {
+ JS::AutoSaveExceptionState savedExc(cx);
+ if (!PrintStackTrace(cx, exn))
+ fputs("(Unable to print stack trace)\n", fp);
+ savedExc.restore();
+ }
+
+ if (report.report()->errorNumber == JSMSG_OUT_OF_MEMORY)
+ sc->exitCode = EXITCODE_OUT_OF_MEMORY;
+ else
+ sc->exitCode = EXITCODE_RUNTIME_ERROR;
+
+ JS_ClearPendingException(cx);
+}
+
+void
+js::shell::WarningReporter(JSContext* cx, JSErrorReport* report)
+{
+ ShellContext* sc = GetShellContext(cx);
+ FILE* fp = ErrorFilePointer();
+
+ MOZ_ASSERT(report);
+ MOZ_ASSERT(JSREPORT_IS_WARNING(report->flags));
+
+ if (sc->lastWarningEnabled) {
+ JS::AutoSaveExceptionState savedExc(cx);
+ if (!CreateLastWarningObject(cx, report)) {
+ fputs("Unhandled error happened while creating last warning object.\n", fp);
+ fflush(fp);
+ }
+ savedExc.restore();
+ }
+
+ // Print the warning.
+ PrintError(cx, fp, JS::ConstUTF8CharsZ(), report, reportWarnings);
+}
+
+static bool
+global_enumerate(JSContext* cx, HandleObject obj)
+{
+#ifdef LAZY_STANDARD_CLASSES
+ return JS_EnumerateStandardClasses(cx, obj);
+#else
+ return true;
+#endif
+}
+
+static bool
+global_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
+{
+#ifdef LAZY_STANDARD_CLASSES
+ if (!JS_ResolveStandardClass(cx, obj, id, resolvedp))
+ return false;
+#endif
+ return true;
+}
+
+static bool
+global_mayResolve(const JSAtomState& names, jsid id, JSObject* maybeObj)
+{
+ return JS_MayResolveStandardClass(names, id, maybeObj);
+}
+
+static const JSClassOps global_classOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ global_enumerate, global_resolve, global_mayResolve,
+ nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+};
+
+static const JSClass global_class = {
+ "global", JSCLASS_GLOBAL_FLAGS,
+ &global_classOps
+};
+
+/*
+ * Define a FakeDOMObject constructor. It returns an object with a getter,
+ * setter and method with attached JitInfo. This object can be used to test
+ * IonMonkey DOM optimizations in the shell.
+ */
+static const uint32_t DOM_OBJECT_SLOT = 0;
+
+static bool
+dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
+
+static bool
+dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
+
+static bool
+dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
+
+#ifdef DEBUG
+static const JSClass* GetDomClass();
+#endif
+
+static bool
+dom_get_x(JSContext* cx, HandleObject obj, void* self, JSJitGetterCallArgs args)
+{
+ MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
+ MOZ_ASSERT(self == (void*)0x1234);
+ args.rval().set(JS_NumberValue(double(3.14)));
+ return true;
+}
+
+static bool
+dom_set_x(JSContext* cx, HandleObject obj, void* self, JSJitSetterCallArgs args)
+{
+ MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
+ MOZ_ASSERT(self == (void*)0x1234);
+ return true;
+}
+
+static bool
+dom_doFoo(JSContext* cx, HandleObject obj, void* self, const JSJitMethodCallArgs& args)
+{
+ MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
+ MOZ_ASSERT(self == (void*)0x1234);
+
+ /* Just return args.length(). */
+ args.rval().setInt32(args.length());
+ return true;
+}
+
+static const JSJitInfo dom_x_getterinfo = {
+ { (JSJitGetterOp)dom_get_x },
+ { 0 }, /* protoID */
+ { 0 }, /* depth */
+ JSJitInfo::AliasNone, /* aliasSet */
+ JSJitInfo::Getter,
+ JSVAL_TYPE_UNKNOWN, /* returnType */
+ true, /* isInfallible. False in setters. */
+ true, /* isMovable */
+ true, /* isEliminatable */
+ false, /* isAlwaysInSlot */
+ false, /* isLazilyCachedInSlot */
+ false, /* isTypedMethod */
+ 0 /* slotIndex */
+};
+
+static const JSJitInfo dom_x_setterinfo = {
+ { (JSJitGetterOp)dom_set_x },
+ { 0 }, /* protoID */
+ { 0 }, /* depth */
+ JSJitInfo::Setter,
+ JSJitInfo::AliasEverything, /* aliasSet */
+ JSVAL_TYPE_UNKNOWN, /* returnType */
+ false, /* isInfallible. False in setters. */
+ false, /* isMovable. */
+ false, /* isEliminatable. */
+ false, /* isAlwaysInSlot */
+ false, /* isLazilyCachedInSlot */
+ false, /* isTypedMethod */
+ 0 /* slotIndex */
+};
+
+static const JSJitInfo doFoo_methodinfo = {
+ { (JSJitGetterOp)dom_doFoo },
+ { 0 }, /* protoID */
+ { 0 }, /* depth */
+ JSJitInfo::Method,
+ JSJitInfo::AliasEverything, /* aliasSet */
+ JSVAL_TYPE_UNKNOWN, /* returnType */
+ false, /* isInfallible. False in setters. */
+ false, /* isMovable */
+ false, /* isEliminatable */
+ false, /* isAlwaysInSlot */
+ false, /* isLazilyCachedInSlot */
+ false, /* isTypedMethod */
+ 0 /* slotIndex */
+};
+
+static const JSPropertySpec dom_props[] = {
+ {"x",
+ JSPROP_SHARED | JSPROP_ENUMERATE,
+ { {
+ { { dom_genericGetter, &dom_x_getterinfo } },
+ { { dom_genericSetter, &dom_x_setterinfo } }
+ } },
+ },
+ JS_PS_END
+};
+
+static const JSFunctionSpec dom_methods[] = {
+ JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3, JSPROP_ENUMERATE),
+ JS_FS_END
+};
+
+static const JSClass dom_class = {
+ "FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2)
+};
+
+#ifdef DEBUG
+static const JSClass* GetDomClass() {
+ return &dom_class;
+}
+#endif
+
+static bool
+dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
+ if (!obj)
+ return false;
+
+ if (JS_GetClass(obj) != &dom_class) {
+ args.rval().set(UndefinedValue());
+ return true;
+ }
+
+ JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
+
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Getter);
+ JSJitGetterOp getter = info->getter;
+ return getter(cx, obj, val.toPrivate(), JSJitGetterCallArgs(args));
+}
+
+static bool
+dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
+ if (!obj)
+ return false;
+
+ MOZ_ASSERT(args.length() == 1);
+
+ if (JS_GetClass(obj) != &dom_class) {
+ args.rval().set(UndefinedValue());
+ return true;
+ }
+
+ JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
+
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Setter);
+ JSJitSetterOp setter = info->setter;
+ if (!setter(cx, obj, val.toPrivate(), JSJitSetterCallArgs(args)))
+ return false;
+ args.rval().set(UndefinedValue());
+ return true;
+}
+
+static bool
+dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
+ if (!obj)
+ return false;
+
+ if (JS_GetClass(obj) != &dom_class) {
+ args.rval().set(UndefinedValue());
+ return true;
+ }
+
+ JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
+
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Method);
+ JSJitMethodOp method = info->method;
+ return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args));
+}
+
+static void
+InitDOMObject(HandleObject obj)
+{
+ /* Fow now just initialize to a constant we can check. */
+ SetReservedSlot(obj, DOM_OBJECT_SLOT, PrivateValue((void*)0x1234));
+}
+
+static bool
+dom_constructor(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject callee(cx, &args.callee());
+ RootedValue protov(cx);
+ if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov))
+ return false;
+
+ if (!protov.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE,
+ "FakeDOMObject");
+ return false;
+ }
+
+ RootedObject proto(cx, &protov.toObject());
+ RootedObject domObj(cx, JS_NewObjectWithGivenProto(cx, &dom_class, proto));
+ if (!domObj)
+ return false;
+
+ InitDOMObject(domObj);
+
+ args.rval().setObject(*domObj);
+ return true;
+}
+
+static bool
+InstanceClassHasProtoAtDepth(const Class* clasp, uint32_t protoID, uint32_t depth)
+{
+ /* There's only a single (fake) DOM object in the shell, so just return true. */
+ return true;
+}
+
+class ScopedFileDesc
+{
+ intptr_t fd_;
+ public:
+ enum LockType { READ_LOCK, WRITE_LOCK };
+ ScopedFileDesc(int fd, LockType lockType)
+ : fd_(fd)
+ {
+ if (fd == -1)
+ return;
+ if (!jsCacheOpened.compareExchange(false, true)) {
+ close(fd_);
+ fd_ = -1;
+ return;
+ }
+ }
+ ~ScopedFileDesc() {
+ if (fd_ == -1)
+ return;
+ MOZ_ASSERT(jsCacheOpened == true);
+ jsCacheOpened = false;
+ close(fd_);
+ }
+ operator intptr_t() const {
+ return fd_;
+ }
+ intptr_t forget() {
+ intptr_t ret = fd_;
+ fd_ = -1;
+ return ret;
+ }
+};
+
+// To guard against corrupted cache files generated by previous crashes, write
+// asmJSCacheCookie to the first uint32_t of the file only after the file is
+// fully serialized and flushed to disk.
+static const uint32_t asmJSCacheCookie = 0xabbadaba;
+
+static bool
+ShellOpenAsmJSCacheEntryForRead(HandleObject global, const char16_t* begin, const char16_t* limit,
+ size_t* serializedSizeOut, const uint8_t** memoryOut,
+ intptr_t* handleOut)
+{
+ if (!jsCachingEnabled || !jsCacheAsmJSPath)
+ return false;
+
+ ScopedFileDesc fd(open(jsCacheAsmJSPath, O_RDWR), ScopedFileDesc::READ_LOCK);
+ if (fd == -1)
+ return false;
+
+ // Get the size and make sure we can dereference at least one uint32_t.
+ off_t off = lseek(fd, 0, SEEK_END);
+ if (off == -1 || off < (off_t)sizeof(uint32_t))
+ return false;
+
+ // Map the file into memory.
+ void* memory;
+#ifdef XP_WIN
+ HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd);
+ HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr);
+ if (!fileMapping)
+ return false;
+
+ memory = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0);
+ CloseHandle(fileMapping);
+ if (!memory)
+ return false;
+#else
+ memory = mmap(nullptr, off, PROT_READ, MAP_SHARED, fd, 0);
+ if (memory == MAP_FAILED)
+ return false;
+#endif
+
+ // Perform check described by asmJSCacheCookie comment.
+ if (*(uint32_t*)memory != asmJSCacheCookie) {
+#ifdef XP_WIN
+ UnmapViewOfFile(memory);
+#else
+ munmap(memory, off);
+#endif
+ return false;
+ }
+
+ // The embedding added the cookie so strip it off of the buffer returned to
+ // the JS engine.
+ *serializedSizeOut = off - sizeof(uint32_t);
+ *memoryOut = (uint8_t*)memory + sizeof(uint32_t);
+ *handleOut = fd.forget();
+ return true;
+}
+
+static void
+ShellCloseAsmJSCacheEntryForRead(size_t serializedSize, const uint8_t* memory, intptr_t handle)
+{
+ // Undo the cookie adjustment done when opening the file.
+ memory -= sizeof(uint32_t);
+ serializedSize += sizeof(uint32_t);
+
+ // Release the memory mapping and file.
+#ifdef XP_WIN
+ UnmapViewOfFile(const_cast<uint8_t*>(memory));
+#else
+ munmap(const_cast<uint8_t*>(memory), serializedSize);
+#endif
+
+ MOZ_ASSERT(jsCacheOpened == true);
+ jsCacheOpened = false;
+ close(handle);
+}
+
+static JS::AsmJSCacheResult
+ShellOpenAsmJSCacheEntryForWrite(HandleObject global, bool installed,
+ const char16_t* begin, const char16_t* end,
+ size_t serializedSize, uint8_t** memoryOut, intptr_t* handleOut)
+{
+ if (!jsCachingEnabled || !jsCacheAsmJSPath)
+ return JS::AsmJSCache_Disabled_ShellFlags;
+
+ // Create the cache directory if it doesn't already exist.
+ struct stat dirStat;
+ if (stat(jsCacheDir, &dirStat) == 0) {
+ if (!(dirStat.st_mode & S_IFDIR))
+ return JS::AsmJSCache_InternalError;
+ } else {
+#ifdef XP_WIN
+ if (mkdir(jsCacheDir) != 0)
+ return JS::AsmJSCache_InternalError;
+#else
+ if (mkdir(jsCacheDir, 0777) != 0)
+ return JS::AsmJSCache_InternalError;
+#endif
+ }
+
+ ScopedFileDesc fd(open(jsCacheAsmJSPath, O_CREAT|O_RDWR, 0660), ScopedFileDesc::WRITE_LOCK);
+ if (fd == -1)
+ return JS::AsmJSCache_InternalError;
+
+ // Include extra space for the asmJSCacheCookie.
+ serializedSize += sizeof(uint32_t);
+
+ // Resize the file to the appropriate size after zeroing their contents.
+#ifdef XP_WIN
+ if (chsize(fd, 0))
+ return JS::AsmJSCache_InternalError;
+ if (chsize(fd, serializedSize))
+ return JS::AsmJSCache_InternalError;
+#else
+ if (ftruncate(fd, 0))
+ return JS::AsmJSCache_InternalError;
+ if (ftruncate(fd, serializedSize))
+ return JS::AsmJSCache_InternalError;
+#endif
+
+ // Map the file into memory.
+ void* memory;
+#ifdef XP_WIN
+ HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd);
+ HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr);
+ if (!fileMapping)
+ return JS::AsmJSCache_InternalError;
+
+ memory = MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0);
+ CloseHandle(fileMapping);
+ if (!memory)
+ return JS::AsmJSCache_InternalError;
+ MOZ_ASSERT(*(uint32_t*)memory == 0);
+#else
+ memory = mmap(nullptr, serializedSize, PROT_READ, MAP_SHARED, fd, 0);
+ if (memory == MAP_FAILED)
+ return JS::AsmJSCache_InternalError;
+ MOZ_ASSERT(*(uint32_t*)memory == 0);
+ if (mprotect(memory, serializedSize, PROT_WRITE))
+ return JS::AsmJSCache_InternalError;
+#endif
+
+ // The embedding added the cookie so strip it off of the buffer returned to
+ // the JS engine. The asmJSCacheCookie will be written on close, below.
+ *memoryOut = (uint8_t*)memory + sizeof(uint32_t);
+ *handleOut = fd.forget();
+ return JS::AsmJSCache_Success;
+}
+
+static void
+ShellCloseAsmJSCacheEntryForWrite(size_t serializedSize, uint8_t* memory, intptr_t handle)
+{
+ // Undo the cookie adjustment done when opening the file.
+ memory -= sizeof(uint32_t);
+ serializedSize += sizeof(uint32_t);
+
+ // Write the magic cookie value after flushing the entire cache entry.
+#ifdef XP_WIN
+ FlushViewOfFile(memory, serializedSize);
+ FlushFileBuffers(HANDLE(_get_osfhandle(handle)));
+#else
+ msync(memory, serializedSize, MS_SYNC);
+#endif
+
+ MOZ_ASSERT(*(uint32_t*)memory == 0);
+ *(uint32_t*)memory = asmJSCacheCookie;
+
+ // Free the memory mapping and file.
+#ifdef XP_WIN
+ UnmapViewOfFile(const_cast<uint8_t*>(memory));
+#else
+ munmap(memory, serializedSize);
+#endif
+
+ MOZ_ASSERT(jsCacheOpened == true);
+ jsCacheOpened = false;
+ close(handle);
+}
+
+static bool
+ShellBuildId(JS::BuildIdCharVector* buildId)
+{
+ // The browser embeds the date into the buildid and the buildid is embedded
+ // in the binary, so every 'make' necessarily builds a new firefox binary.
+ // Fortunately, the actual firefox executable is tiny -- all the code is in
+ // libxul.so and other shared modules -- so this isn't a big deal. Not so
+ // for the statically-linked JS shell. To avoid recompiling js.cpp and
+ // re-linking 'js' on every 'make', we use a constant buildid and rely on
+ // the shell user to manually clear the cache (deleting the dir passed to
+ // --js-cache) between cache-breaking updates. Note: jit_tests.py does this
+ // on every run).
+ const char buildid[] = "JS-shell";
+ return buildId->append(buildid, sizeof(buildid));
+}
+
+static const JS::AsmJSCacheOps asmJSCacheOps = {
+ ShellOpenAsmJSCacheEntryForRead,
+ ShellCloseAsmJSCacheEntryForRead,
+ ShellOpenAsmJSCacheEntryForWrite,
+ ShellCloseAsmJSCacheEntryForWrite
+};
+
+static JSObject*
+NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
+ JSPrincipals* principals)
+{
+ RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, principals,
+ JS::DontFireOnNewGlobalHook, options));
+ if (!glob)
+ return nullptr;
+
+ {
+ JSAutoCompartment ac(cx, glob);
+
+#ifndef LAZY_STANDARD_CLASSES
+ if (!JS_InitStandardClasses(cx, glob))
+ return nullptr;
+#endif
+
+ bool succeeded;
+ if (!JS_SetImmutablePrototype(cx, glob, &succeeded))
+ return nullptr;
+ MOZ_ASSERT(succeeded,
+ "a fresh, unexposed global object is always capable of "
+ "having its [[Prototype]] be immutable");
+
+#ifdef JS_HAS_CTYPES
+ if (!JS_InitCTypesClass(cx, glob))
+ return nullptr;
+#endif
+ if (!JS_InitReflectParse(cx, glob))
+ return nullptr;
+ if (!JS_DefineDebuggerObject(cx, glob))
+ return nullptr;
+ if (!JS::RegisterPerfMeasurement(cx, glob))
+ return nullptr;
+ if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) ||
+ !JS_DefineProfilingFunctions(cx, glob))
+ {
+ return nullptr;
+ }
+ if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe, disableOOMFunctions))
+ return nullptr;
+
+ if (!fuzzingSafe) {
+ if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions))
+ return nullptr;
+ if (!DefineConsole(cx, glob))
+ return nullptr;
+ }
+
+ if (!DefineOS(cx, glob, fuzzingSafe, &gOutFile, &gErrFile))
+ return nullptr;
+
+ RootedObject performanceObj(cx, JS_NewObject(cx, nullptr));
+ if (!performanceObj)
+ return nullptr;
+ RootedObject mozMemoryObj(cx, JS_NewObject(cx, nullptr));
+ if (!mozMemoryObj)
+ return nullptr;
+ RootedObject gcObj(cx, gc::NewMemoryInfoObject(cx));
+ if (!gcObj)
+ return nullptr;
+ if (!JS_DefineProperty(cx, glob, "performance", performanceObj, JSPROP_ENUMERATE))
+ return nullptr;
+ if (!JS_DefineProperty(cx, performanceObj, "mozMemory", mozMemoryObj, JSPROP_ENUMERATE))
+ return nullptr;
+ if (!JS_DefineProperty(cx, mozMemoryObj, "gc", gcObj, JSPROP_ENUMERATE))
+ return nullptr;
+
+ /* Initialize FakeDOMObject. */
+ static const js::DOMCallbacks DOMcallbacks = {
+ InstanceClassHasProtoAtDepth
+ };
+ SetDOMCallbacks(cx, &DOMcallbacks);
+
+ RootedObject domProto(cx, JS_InitClass(cx, glob, nullptr, &dom_class, dom_constructor,
+ 0, dom_props, dom_methods, nullptr, nullptr));
+ if (!domProto)
+ return nullptr;
+
+ /* Initialize FakeDOMObject.prototype */
+ InitDOMObject(domProto);
+ }
+
+ JS_FireOnNewGlobalObject(cx, glob);
+
+ return glob;
+}
+
+static bool
+BindScriptArgs(JSContext* cx, OptionParser* op)
+{
+ AutoReportException are(cx);
+
+ MultiStringRange msr = op->getMultiStringArg("scriptArgs");
+ RootedObject scriptArgs(cx);
+ scriptArgs = JS_NewArrayObject(cx, 0);
+ if (!scriptArgs)
+ return false;
+
+ if (!JS_DefineProperty(cx, cx->global(), "scriptArgs", scriptArgs, 0))
+ return false;
+
+ for (size_t i = 0; !msr.empty(); msr.popFront(), ++i) {
+ const char* scriptArg = msr.front();
+ JS::RootedString str(cx, JS_NewStringCopyZ(cx, scriptArg));
+ if (!str ||
+ !JS_DefineElement(cx, scriptArgs, i, str, JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+ }
+
+ const char* scriptPath = op->getStringArg("script");
+ RootedValue scriptPathValue(cx);
+ if (scriptPath) {
+ RootedString scriptPathString(cx, JS_NewStringCopyZ(cx, scriptPath));
+ if (!scriptPathString)
+ return false;
+ scriptPathValue = StringValue(scriptPathString);
+ } else {
+ scriptPathValue = UndefinedValue();
+ }
+
+ if (!JS_DefineProperty(cx, cx->global(), "scriptPath", scriptPathValue, 0))
+ return false;
+
+ return true;
+}
+
+static bool
+OptionFailure(const char* option, const char* str)
+{
+ fprintf(stderr, "Unrecognized option for %s: %s\n", option, str);
+ return false;
+}
+
+static MOZ_MUST_USE bool
+ProcessArgs(JSContext* cx, OptionParser* op)
+{
+ ShellContext* sc = GetShellContext(cx);
+
+ if (op->getBoolOption('s'))
+ JS::ContextOptionsRef(cx).toggleExtraWarnings();
+
+ /* |scriptArgs| gets bound on the global before any code is run. */
+ if (!BindScriptArgs(cx, op))
+ return false;
+
+ MultiStringRange filePaths = op->getMultiStringOption('f');
+ MultiStringRange codeChunks = op->getMultiStringOption('e');
+ MultiStringRange modulePaths = op->getMultiStringOption('m');
+
+ if (filePaths.empty() &&
+ codeChunks.empty() &&
+ modulePaths.empty() &&
+ !op->getStringArg("script"))
+ {
+ return Process(cx, nullptr, true); /* Interactive. */
+ }
+
+ if (const char* path = op->getStringOption("module-load-path"))
+ moduleLoadPath = path;
+
+ if (!modulePaths.empty() && !InitModuleLoader(cx))
+ return false;
+
+ while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) {
+ size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno();
+ size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno();
+ size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno();
+
+ if (fpArgno < ccArgno && fpArgno < mpArgno) {
+ char* path = filePaths.front();
+ if (!Process(cx, path, false, FileScript))
+ return false;
+ filePaths.popFront();
+ } else if (ccArgno < fpArgno && ccArgno < mpArgno) {
+ const char* code = codeChunks.front();
+ RootedValue rval(cx);
+ JS::CompileOptions opts(cx);
+ opts.setFileAndLine("-e", 1);
+ if (!JS::Evaluate(cx, opts, code, strlen(code), &rval))
+ return false;
+ codeChunks.popFront();
+ if (sc->quitting)
+ break;
+ } else {
+ MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ccArgno);
+ char* path = modulePaths.front();
+ if (!Process(cx, path, false, FileModule))
+ return false;
+ modulePaths.popFront();
+ }
+ }
+
+ if (sc->quitting)
+ return false;
+
+ /* The |script| argument is processed after all options. */
+ if (const char* path = op->getStringArg("script")) {
+ if (!Process(cx, path, false))
+ return false;
+ }
+
+ DrainJobQueue(cx);
+
+ if (op->getBoolOption('i')) {
+ if (!Process(cx, nullptr, true))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+SetContextOptions(JSContext* cx, const OptionParser& op)
+{
+ enableBaseline = !op.getBoolOption("no-baseline");
+ enableIon = !op.getBoolOption("no-ion");
+ enableAsmJS = !op.getBoolOption("no-asmjs");
+ enableWasm = !op.getBoolOption("no-wasm");
+ enableNativeRegExp = !op.getBoolOption("no-native-regexp");
+ enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
+ enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
+
+ JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
+ .setIon(enableIon)
+ .setAsmJS(enableAsmJS)
+ .setWasm(enableWasm)
+ .setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
+ .setNativeRegExp(enableNativeRegExp)
+ .setUnboxedArrays(enableUnboxedArrays);
+
+ if (op.getBoolOption("wasm-check-bce"))
+ jit::JitOptions.wasmAlwaysCheckBounds = true;
+
+ if (op.getBoolOption("no-unboxed-objects"))
+ jit::JitOptions.disableUnboxedObjects = true;
+
+ if (const char* str = op.getStringOption("cache-ir-stubs")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableCacheIR = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableCacheIR = true;
+ else
+ return OptionFailure("cache-ir-stubs", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-scalar-replacement")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableScalarReplacement = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableScalarReplacement = true;
+ else
+ return OptionFailure("ion-scalar-replacement", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-shared-stubs")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableSharedStubs = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableSharedStubs = true;
+ else
+ return OptionFailure("ion-shared-stubs", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-gvn")) {
+ if (strcmp(str, "off") == 0) {
+ jit::JitOptions.disableGvn = true;
+ } else if (strcmp(str, "on") != 0 &&
+ strcmp(str, "optimistic") != 0 &&
+ strcmp(str, "pessimistic") != 0)
+ {
+ // We accept "pessimistic" and "optimistic" as synonyms for "on"
+ // for backwards compatibility.
+ return OptionFailure("ion-gvn", str);
+ }
+ }
+
+ if (const char* str = op.getStringOption("ion-aa")) {
+ if (strcmp(str, "flow-sensitive") == 0)
+ jit::JitOptions.disableFlowAA = false;
+ else if (strcmp(str, "flow-insensitive") == 0)
+ jit::JitOptions.disableFlowAA = true;
+ else
+ return OptionFailure("ion-aa", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-licm")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableLicm = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableLicm = true;
+ else
+ return OptionFailure("ion-licm", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-edgecase-analysis")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableEdgeCaseAnalysis = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableEdgeCaseAnalysis = true;
+ else
+ return OptionFailure("ion-edgecase-analysis", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-pgo")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disablePgo = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disablePgo = true;
+ else
+ return OptionFailure("ion-pgo", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-range-analysis")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableRangeAnalysis = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableRangeAnalysis = true;
+ else
+ return OptionFailure("ion-range-analysis", str);
+ }
+
+ if (const char *str = op.getStringOption("ion-sincos")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableSincos = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableSincos = true;
+ else
+ return OptionFailure("ion-sincos", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-sink")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableSink = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableSink = true;
+ else
+ return OptionFailure("ion-sink", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-loop-unrolling")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableLoopUnrolling = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableLoopUnrolling = true;
+ else
+ return OptionFailure("ion-loop-unrolling", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-instruction-reordering")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableInstructionReordering = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableInstructionReordering = true;
+ else
+ return OptionFailure("ion-instruction-reordering", str);
+ }
+
+ if (op.getBoolOption("ion-check-range-analysis"))
+ jit::JitOptions.checkRangeAnalysis = true;
+
+ if (op.getBoolOption("ion-extra-checks"))
+ jit::JitOptions.runExtraChecks = true;
+
+ if (const char* str = op.getStringOption("ion-inlining")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.disableInlining = false;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.disableInlining = true;
+ else
+ return OptionFailure("ion-inlining", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-osr")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.osr = true;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.osr = false;
+ else
+ return OptionFailure("ion-osr", str);
+ }
+
+ if (const char* str = op.getStringOption("ion-limit-script-size")) {
+ if (strcmp(str, "on") == 0)
+ jit::JitOptions.limitScriptSize = true;
+ else if (strcmp(str, "off") == 0)
+ jit::JitOptions.limitScriptSize = false;
+ else
+ return OptionFailure("ion-limit-script-size", str);
+ }
+
+ int32_t warmUpThreshold = op.getIntOption("ion-warmup-threshold");
+ if (warmUpThreshold >= 0)
+ jit::JitOptions.setCompilerWarmUpThreshold(warmUpThreshold);
+
+ warmUpThreshold = op.getIntOption("baseline-warmup-threshold");
+ if (warmUpThreshold >= 0)
+ jit::JitOptions.baselineWarmUpThreshold = warmUpThreshold;
+
+ if (op.getBoolOption("baseline-eager"))
+ jit::JitOptions.baselineWarmUpThreshold = 0;
+
+ if (const char* str = op.getStringOption("ion-regalloc")) {
+ jit::JitOptions.forcedRegisterAllocator = jit::LookupRegisterAllocator(str);
+ if (!jit::JitOptions.forcedRegisterAllocator.isSome())
+ return OptionFailure("ion-regalloc", str);
+ }
+
+ if (op.getBoolOption("ion-eager"))
+ jit::JitOptions.setEagerCompilation();
+
+ offthreadCompilation = true;
+ if (const char* str = op.getStringOption("ion-offthread-compile")) {
+ if (strcmp(str, "off") == 0)
+ offthreadCompilation = false;
+ else if (strcmp(str, "on") != 0)
+ return OptionFailure("ion-offthread-compile", str);
+ }
+ cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
+
+ if (op.getStringOption("ion-parallel-compile")) {
+ fprintf(stderr, "--ion-parallel-compile is deprecated. Please use --ion-offthread-compile instead.\n");
+ return false;
+ }
+
+#ifdef ENABLE_SHARED_ARRAY_BUFFER
+ if (const char* str = op.getStringOption("shared-memory")) {
+ if (strcmp(str, "off") == 0)
+ enableSharedMemory = false;
+ else if (strcmp(str, "on") == 0)
+ enableSharedMemory = true;
+ else
+ return OptionFailure("shared-memory", str);
+ }
+#endif
+
+#if defined(JS_CODEGEN_ARM)
+ if (const char* str = op.getStringOption("arm-hwcap"))
+ jit::ParseARMHwCapFlags(str);
+
+ int32_t fill = op.getIntOption("arm-asm-nop-fill");
+ if (fill >= 0)
+ jit::Assembler::NopFill = fill;
+
+ int32_t poolMaxOffset = op.getIntOption("asm-pool-max-offset");
+ if (poolMaxOffset >= 5 && poolMaxOffset <= 1024)
+ jit::Assembler::AsmPoolMaxOffset = poolMaxOffset;
+#endif
+
+#if defined(JS_SIMULATOR_ARM)
+ if (op.getBoolOption("arm-sim-icache-checks"))
+ jit::Simulator::ICacheCheckingEnabled = true;
+
+ int32_t stopAt = op.getIntOption("arm-sim-stop-at");
+ if (stopAt >= 0)
+ jit::Simulator::StopSimAt = stopAt;
+#elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
+ if (op.getBoolOption("mips-sim-icache-checks"))
+ jit::Simulator::ICacheCheckingEnabled = true;
+
+ int32_t stopAt = op.getIntOption("mips-sim-stop-at");
+ if (stopAt >= 0)
+ jit::Simulator::StopSimAt = stopAt;
+#endif
+
+ reportWarnings = op.getBoolOption('w');
+ compileOnly = op.getBoolOption('c');
+ printTiming = op.getBoolOption('b');
+ enableCodeCoverage = op.getBoolOption("code-coverage");
+ enableDisassemblyDumps = op.getBoolOption('D');
+ cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
+
+ jsCacheDir = op.getStringOption("js-cache");
+ if (jsCacheDir) {
+ if (!op.getBoolOption("no-js-cache-per-process"))
+ jsCacheDir = JS_smprintf("%s/%u", jsCacheDir, (unsigned)getpid());
+ else
+ jsCacheDir = JS_strdup(cx, jsCacheDir);
+ if (!jsCacheDir)
+ return false;
+ jsCacheAsmJSPath = JS_smprintf("%s/asmjs.cache", jsCacheDir);
+ }
+
+#ifdef DEBUG
+ dumpEntrainedVariables = op.getBoolOption("dump-entrained-variables");
+#endif
+
+#ifdef JS_GC_ZEAL
+ const char* zealStr = op.getStringOption("gc-zeal");
+ if (zealStr) {
+ if (!cx->gc.parseAndSetZeal(zealStr))
+ return false;
+ uint32_t nextScheduled;
+ cx->gc.getZealBits(&gZealBits, &gZealFrequency, &nextScheduled);
+ }
+#endif
+
+ return true;
+}
+
+static void
+SetWorkerContextOptions(JSContext* cx)
+{
+ // Copy option values from the main thread.
+ JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
+ .setIon(enableIon)
+ .setAsmJS(enableAsmJS)
+ .setWasm(enableWasm)
+ .setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
+ .setNativeRegExp(enableNativeRegExp)
+ .setUnboxedArrays(enableUnboxedArrays);
+ cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
+ cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
+
+#ifdef JS_GC_ZEAL
+ if (gZealBits && gZealFrequency) {
+#define ZEAL_MODE(_, value) \
+ if (gZealBits & (1 << value)) \
+ cx->gc.setZeal(value, gZealFrequency);
+ JS_FOR_EACH_ZEAL_MODE(ZEAL_MODE)
+#undef ZEAL_MODE
+ }
+#endif
+
+ JS_SetNativeStackQuota(cx, gMaxStackSize);
+}
+
+static int
+Shell(JSContext* cx, OptionParser* op, char** envp)
+{
+ Maybe<JS::AutoDisableGenerationalGC> noggc;
+ if (op->getBoolOption("no-ggc"))
+ noggc.emplace(cx->runtime());
+
+ Maybe<AutoDisableCompactingGC> nocgc;
+ if (op->getBoolOption("no-cgc"))
+ nocgc.emplace(cx);
+
+ JSAutoRequest ar(cx);
+
+ if (op->getBoolOption("fuzzing-safe"))
+ fuzzingSafe = true;
+ else
+ fuzzingSafe = (getenv("MOZ_FUZZING_SAFE") && getenv("MOZ_FUZZING_SAFE")[0] != '0');
+
+ if (op->getBoolOption("disable-oom-functions"))
+ disableOOMFunctions = true;
+
+ JS::CompartmentOptions options;
+ SetStandardCompartmentOptions(options);
+ RootedObject glob(cx, NewGlobalObject(cx, options, nullptr));
+ if (!glob)
+ return 1;
+
+ JSAutoCompartment ac(cx, glob);
+
+ ShellContext* sc = GetShellContext(cx);
+ int result = EXIT_SUCCESS;
+ {
+ AutoReportException are(cx);
+ if (!ProcessArgs(cx, op) && !sc->quitting)
+ result = EXITCODE_RUNTIME_ERROR;
+ }
+
+ if (sc->exitCode)
+ result = sc->exitCode;
+
+ if (enableDisassemblyDumps) {
+ AutoReportException are(cx);
+ if (!js::DumpCompartmentPCCounts(cx))
+ result = EXITCODE_OUT_OF_MEMORY;
+ }
+
+ if (!op->getBoolOption("no-js-cache-per-process")) {
+ if (jsCacheAsmJSPath) {
+ unlink(jsCacheAsmJSPath);
+ JS_free(cx, const_cast<char*>(jsCacheAsmJSPath));
+ }
+ if (jsCacheDir) {
+ rmdir(jsCacheDir);
+ JS_free(cx, const_cast<char*>(jsCacheDir));
+ }
+ }
+
+ return result;
+}
+
+static void
+SetOutputFile(const char* const envVar,
+ RCFile* defaultOut,
+ RCFile** outFileP)
+{
+ RCFile* outFile;
+
+ const char* outPath = getenv(envVar);
+ FILE* newfp;
+ if (outPath && *outPath && (newfp = fopen(outPath, "w")))
+ outFile = js_new<RCFile>(newfp);
+ else
+ outFile = defaultOut;
+
+ outFile->acquire();
+ *outFileP = outFile;
+}
+
+static void
+PreInit()
+{
+#ifdef XP_WIN
+ const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
+ if (crash_option && crash_option[0] == '1') {
+ // Disable the segfault dialog. We want to fail the tests immediately
+ // instead of hanging automation.
+ UINT newMode = SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
+ UINT prevMode = SetErrorMode(newMode);
+ SetErrorMode(prevMode | newMode);
+ }
+#endif
+}
+
+int
+main(int argc, char** argv, char** envp)
+{
+ PreInit();
+
+ sArgc = argc;
+ sArgv = argv;
+
+ int result;
+
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_ALL, "");
+#endif
+
+ // Special-case stdout and stderr. We bump their refcounts to prevent them
+ // from getting closed and then having some printf fail somewhere.
+ RCFile rcStdout(stdout);
+ rcStdout.acquire();
+ RCFile rcStderr(stderr);
+ rcStderr.acquire();
+
+ SetOutputFile("JS_STDOUT", &rcStdout, &gOutFile);
+ SetOutputFile("JS_STDERR", &rcStderr, &gErrFile);
+
+ OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]");
+
+ op.setDescription("The SpiderMonkey shell provides a command line interface to the "
+ "JavaScript engine. Code and file options provided via the command line are "
+ "run left to right. If provided, the optional script argument is run after "
+ "all options have been processed. Just-In-Time compilation modes may be enabled via "
+ "command line options.");
+ op.setDescriptionWidth(72);
+ op.setHelpWidth(80);
+ op.setVersion(JS_GetImplementationVersion());
+
+ if (!op.addMultiStringOption('f', "file", "PATH", "File path to run")
+ || !op.addMultiStringOption('m', "module", "PATH", "Module path to run")
+ || !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run")
+ || !op.addBoolOption('i', "shell", "Enter prompt after running code")
+ || !op.addBoolOption('c', "compileonly", "Only compile, don't run (syntax checking mode)")
+ || !op.addBoolOption('w', "warnings", "Emit warnings")
+ || !op.addBoolOption('W', "nowarnings", "Don't emit warnings")
+ || !op.addBoolOption('s', "strict", "Check strictness")
+ || !op.addBoolOption('D', "dump-bytecode", "Dump bytecode with exec count for all scripts")
+ || !op.addBoolOption('b', "print-timing", "Print sub-ms runtime for each file that's run")
+ || !op.addStringOption('\0', "js-cache", "[path]",
+ "Enable the JS cache by specifying the path of the directory to use "
+ "to hold cache files")
+ || !op.addBoolOption('\0', "no-js-cache-per-process",
+ "Deactivates cache per process. Otherwise, generate a separate cache"
+ "sub-directory for this process inside the cache directory"
+ "specified by --js-cache. This cache directory will be removed"
+ "when the js shell exits. This is useful for running tests in"
+ "parallel.")
+ || !op.addBoolOption('\0', "code-coverage", "Enable code coverage instrumentation.")
+#ifdef DEBUG
+ || !op.addBoolOption('O', "print-alloc", "Print the number of allocations at exit")
+#endif
+ || !op.addOptionalStringArg("script", "A script to execute (after all options)")
+ || !op.addOptionalMultiStringArg("scriptArgs",
+ "String arguments to bind as |scriptArgs| in the "
+ "shell's global")
+ || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads "
+ "(default: # of cores - 1)", -1)
+ || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)")
+ || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
+ || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
+ || !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation")
+ || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
+ || !op.addBoolOption('\0', "no-unboxed-objects", "Disable creating unboxed plain objects")
+ || !op.addBoolOption('\0', "unboxed-arrays", "Allow creating unboxed arrays")
+ || !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
+ || !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
+#ifdef ENABLE_SHARED_ARRAY_BUFFER
+ || !op.addStringOption('\0', "shared-memory", "on/off",
+ "SharedArrayBuffer and Atomics "
+# if SHARED_MEMORY_DEFAULT
+ "(default: on, off to disable)"
+# else
+ "(default: off, on to enable)"
+# endif
+ )
+#endif
+ || !op.addStringOption('\0', "cache-ir-stubs", "on/off",
+ "Use CacheIR stubs (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-shared-stubs", "on/off",
+ "Use shared stubs (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
+ "Scalar Replacement (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-gvn", "[mode]",
+ "Specify Ion global value numbering:\n"
+ " off: disable GVN\n"
+ " on: enable GVN (default)\n")
+ || !op.addStringOption('\0', "ion-licm", "on/off",
+ "Loop invariant code motion (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-aa", "flow-sensitive/flow-insensitive",
+ "Specify wheter or not to use flow sensitive Alias Analysis"
+ "(default: flow-insensitive)")
+ || !op.addStringOption('\0', "ion-edgecase-analysis", "on/off",
+ "Find edge cases where Ion can avoid bailouts (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-pgo", "on/off",
+ "Profile guided optimization (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-range-analysis", "on/off",
+ "Range analysis (default: on, off to disable)")
+#if defined(__APPLE__)
+ || !op.addStringOption('\0', "ion-sincos", "on/off",
+ "Replace sin(x)/cos(x) to sincos(x) (default: on, off to disable)")
+#else
+ || !op.addStringOption('\0', "ion-sincos", "on/off",
+ "Replace sin(x)/cos(x) to sincos(x) (default: off, on to enable)")
+#endif
+ || !op.addStringOption('\0', "ion-sink", "on/off",
+ "Sink code motion (default: off, on to enable)")
+ || !op.addStringOption('\0', "ion-loop-unrolling", "on/off",
+ "Loop unrolling (default: off, on to enable)")
+ || !op.addStringOption('\0', "ion-instruction-reordering", "on/off",
+ "Instruction reordering (default: off, on to enable)")
+ || !op.addBoolOption('\0', "ion-check-range-analysis",
+ "Range analysis checking")
+ || !op.addBoolOption('\0', "ion-extra-checks",
+ "Perform extra dynamic validation checks")
+ || !op.addStringOption('\0', "ion-inlining", "on/off",
+ "Inline methods where possible (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-osr", "on/off",
+ "On-Stack Replacement (default: on, off to disable)")
+ || !op.addStringOption('\0', "ion-limit-script-size", "on/off",
+ "Don't compile very large scripts (default: on, off to disable)")
+ || !op.addIntOption('\0', "ion-warmup-threshold", "COUNT",
+ "Wait for COUNT calls or iterations before compiling "
+ "(default: 1000)", -1)
+ || !op.addStringOption('\0', "ion-regalloc", "[mode]",
+ "Specify Ion register allocation:\n"
+ " backtracking: Priority based backtracking register allocation (default)\n"
+ " testbed: Backtracking allocator with experimental features\n"
+ " stupid: Simple block local register allocation")
+ || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods (implies --baseline-eager)")
+ || !op.addStringOption('\0', "ion-offthread-compile", "on/off",
+ "Compile scripts off thread (default: on)")
+ || !op.addStringOption('\0', "ion-parallel-compile", "on/off",
+ "--ion-parallel compile is deprecated. Use --ion-offthread-compile.")
+ || !op.addBoolOption('\0', "baseline", "Enable baseline compiler (default)")
+ || !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler")
+ || !op.addBoolOption('\0', "baseline-eager", "Always baseline-compile methods")
+ || !op.addIntOption('\0', "baseline-warmup-threshold", "COUNT",
+ "Wait for COUNT calls or iterations before baseline-compiling "
+ "(default: 10)", -1)
+ || !op.addBoolOption('\0', "non-writable-jitcode", "(NOP for fuzzers) Allocate JIT code as non-writable memory.")
+ || !op.addBoolOption('\0', "no-sse3", "Pretend CPU does not support SSE3 instructions and above "
+ "to test JIT codegen (no-op on platforms other than x86 and x64).")
+ || !op.addBoolOption('\0', "no-sse4", "Pretend CPU does not support SSE4 instructions"
+ "to test JIT codegen (no-op on platforms other than x86 and x64).")
+ || !op.addBoolOption('\0', "enable-avx", "AVX is disabled by default. Enable AVX. "
+ "(no-op on platforms other than x86 and x64).")
+ || !op.addBoolOption('\0', "no-avx", "No-op. AVX is currently disabled by default.")
+ || !op.addBoolOption('\0', "fuzzing-safe", "Don't expose functions that aren't safe for "
+ "fuzzers to call")
+ || !op.addBoolOption('\0', "disable-oom-functions", "Disable functions that cause "
+ "artificial OOMs")
+ || !op.addBoolOption('\0', "no-threads", "Disable helper threads")
+#ifdef DEBUG
+ || !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are "
+ "unnecessarily entrained by inner functions")
+#endif
+ || !op.addBoolOption('\0', "no-ggc", "Disable Generational GC")
+ || !op.addBoolOption('\0', "no-cgc", "Disable Compacting GC")
+ || !op.addBoolOption('\0', "no-incremental-gc", "Disable Incremental GC")
+ || !op.addIntOption('\0', "available-memory", "SIZE",
+ "Select GC settings based on available memory (MB)", 0)
+#if defined(JS_CODEGEN_ARM)
+ || !op.addStringOption('\0', "arm-hwcap", "[features]",
+ "Specify ARM code generation features, or 'help' to list all features.")
+ || !op.addIntOption('\0', "arm-asm-nop-fill", "SIZE",
+ "Insert the given number of NOP instructions at all possible pool locations.", 0)
+ || !op.addIntOption('\0', "asm-pool-max-offset", "OFFSET",
+ "The maximum pc relative OFFSET permitted in pool reference instructions.", 1024)
+#endif
+#if defined(JS_SIMULATOR_ARM)
+ || !op.addBoolOption('\0', "arm-sim-icache-checks", "Enable icache flush checks in the ARM "
+ "simulator.")
+ || !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER", "Stop the ARM simulator after the given "
+ "NUMBER of instructions.", -1)
+#elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
+ || !op.addBoolOption('\0', "mips-sim-icache-checks", "Enable icache flush checks in the MIPS "
+ "simulator.")
+ || !op.addIntOption('\0', "mips-sim-stop-at", "NUMBER", "Stop the MIPS simulator after the given "
+ "NUMBER of instructions.", -1)
+#endif
+ || !op.addIntOption('\0', "nursery-size", "SIZE-MB", "Set the maximum nursery size in MB", 16)
+#ifdef JS_GC_ZEAL
+ || !op.addStringOption('z', "gc-zeal", "LEVEL(;LEVEL)*[,N]", gc::ZealModeHelpText)
+#endif
+ || !op.addStringOption('\0', "module-load-path", "DIR", "Set directory to load modules from")
+ )
+ {
+ return EXIT_FAILURE;
+ }
+
+ op.setArgTerminatesOptions("script", true);
+ op.setArgCapturesRest("scriptArgs");
+
+ switch (op.parseArgs(argc, argv)) {
+ case OptionParser::EarlyExit:
+ return EXIT_SUCCESS;
+ case OptionParser::ParseError:
+ op.printHelp(argv[0]);
+ return EXIT_FAILURE;
+ case OptionParser::Fail:
+ return EXIT_FAILURE;
+ case OptionParser::Okay:
+ break;
+ }
+
+ if (op.getHelpOption())
+ return EXIT_SUCCESS;
+
+#ifdef DEBUG
+ /*
+ * Process OOM options as early as possible so that we can observe as many
+ * allocations as possible.
+ */
+ OOM_printAllocationCount = op.getBoolOption('O');
+#endif
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+ if (op.getBoolOption("no-sse3")) {
+ js::jit::CPUInfo::SetSSE3Disabled();
+ PropagateFlagToNestedShells("--no-sse3");
+ }
+ if (op.getBoolOption("no-sse4")) {
+ js::jit::CPUInfo::SetSSE4Disabled();
+ PropagateFlagToNestedShells("--no-sse4");
+ }
+ if (op.getBoolOption("enable-avx")) {
+ js::jit::CPUInfo::SetAVXEnabled();
+ PropagateFlagToNestedShells("--enable-avx");
+ }
+#endif
+
+ if (op.getBoolOption("no-threads"))
+ js::DisableExtraThreads();
+
+ // Start the engine.
+ if (!JS_Init())
+ return 1;
+
+ if (!InitSharedArrayBufferMailbox())
+ return 1;
+
+ // The fake thread count must be set before initializing the Runtime,
+ // which spins up the thread pool.
+ int32_t threadCount = op.getIntOption("thread-count");
+ if (threadCount >= 0)
+ SetFakeCPUCount(threadCount);
+
+ size_t nurseryBytes = JS::DefaultNurseryBytes;
+ nurseryBytes = op.getIntOption("nursery-size") * 1024L * 1024L;
+
+ /* Use the same parameters as the browser in xpcjsruntime.cpp. */
+ JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes, nurseryBytes);
+ if (!cx)
+ return 1;
+
+ UniquePtr<ShellContext> sc = MakeUnique<ShellContext>(cx);
+ if (!sc)
+ return 1;
+
+ JS_SetContextPrivate(cx, sc.get());
+ // Waiting is allowed on the shell's main thread, for now.
+ JS_SetFutexCanWait(cx);
+ JS::SetWarningReporter(cx, WarningReporter);
+ if (!SetContextOptions(cx, op))
+ return 1;
+
+ JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
+
+ size_t availMem = op.getIntOption("available-memory");
+ if (availMem > 0)
+ JS_SetGCParametersBasedOnAvailableMemory(cx, availMem);
+
+ JS_SetTrustedPrincipals(cx, &ShellPrincipals::fullyTrusted);
+ JS_SetSecurityCallbacks(cx, &ShellPrincipals::securityCallbacks);
+ JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
+
+ JS_AddInterruptCallback(cx, ShellInterruptCallback);
+ JS::SetBuildIdOp(cx, ShellBuildId);
+ JS::SetAsmJSCacheOps(cx, &asmJSCacheOps);
+
+ JS_SetNativeStackQuota(cx, gMaxStackSize);
+
+ JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
+
+ if (!JS::InitSelfHostedCode(cx))
+ return 1;
+
+#ifdef SPIDERMONKEY_PROMISE
+ sc->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
+ JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
+ JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
+ JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
+#endif // SPIDERMONKEY_PROMISE
+
+ EnvironmentPreparer environmentPreparer(cx);
+
+ JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+
+ JS::SetLargeAllocationFailureCallback(cx, my_LargeAllocFailCallback, (void*)cx);
+
+ // Set some parameters to allow incremental GC in low memory conditions,
+ // as is done for the browser, except in more-deterministic builds or when
+ // disabled by command line options.
+#ifndef JS_MORE_DETERMINISTIC
+ if (!op.getBoolOption("no-incremental-gc")) {
+ JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, 1);
+ JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, 1);
+ JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET, 10);
+ }
+#endif
+
+ js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
+
+ result = Shell(cx, &op, envp);
+
+#ifdef DEBUG
+ if (OOM_printAllocationCount)
+ printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
+#endif
+
+ JS::SetLargeAllocationFailureCallback(cx, nullptr, nullptr);
+
+#ifdef SPIDERMONKEY_PROMISE
+ JS::SetGetIncumbentGlobalCallback(cx, nullptr);
+ JS::SetEnqueuePromiseJobCallback(cx, nullptr);
+ sc->jobQueue.reset();
+#endif // SPIDERMONKEY_PROMISE
+
+ KillWatchdog(cx);
+
+ KillWorkerThreads();
+
+ DestructSharedArrayBufferMailbox();
+
+ JS_DestroyContext(cx);
+ JS_ShutDown();
+ return result;
+}
diff --git a/js/src/shell/jsoptparse.cpp b/js/src/shell/jsoptparse.cpp
new file mode 100644
index 000000000..eaf4042d3
--- /dev/null
+++ b/js/src/shell/jsoptparse.cpp
@@ -0,0 +1,644 @@
+/* -*- 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/. */
+
+#include "shell/jsoptparse.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "jsutil.h"
+
+using namespace js;
+using namespace js::cli;
+using namespace js::cli::detail;
+
+const char OptionParser::prognameMeta[] = "{progname}";
+
+#define OPTION_CONVERT_IMPL(__cls) \
+ bool \
+ Option::is##__cls##Option() const \
+ { \
+ return kind == OptionKind##__cls; \
+ } \
+ __cls##Option * \
+ Option::as##__cls##Option() \
+ { \
+ MOZ_ASSERT(is##__cls##Option()); \
+ return static_cast<__cls##Option*>(this); \
+ } \
+ const __cls##Option * \
+ Option::as##__cls##Option() const \
+ { \
+ return const_cast<Option*>(this)->as##__cls##Option(); \
+ }
+
+ValuedOption*
+Option::asValued()
+{
+ MOZ_ASSERT(isValued());
+ return static_cast<ValuedOption*>(this);
+}
+
+const ValuedOption*
+Option::asValued() const
+{
+ return const_cast<Option*>(this)->asValued();
+}
+
+OPTION_CONVERT_IMPL(Bool)
+OPTION_CONVERT_IMPL(String)
+OPTION_CONVERT_IMPL(Int)
+OPTION_CONVERT_IMPL(MultiString)
+
+void
+OptionParser::setArgTerminatesOptions(const char* name, bool enabled)
+{
+ findArgument(name)->setTerminatesOptions(enabled);
+}
+
+void
+OptionParser::setArgCapturesRest(const char* name)
+{
+ MOZ_ASSERT(restArgument == -1, "only one argument may be set to capture the rest");
+ restArgument = findArgumentIndex(name);
+ MOZ_ASSERT(restArgument != -1, "unknown argument name passed to setArgCapturesRest");
+}
+
+OptionParser::Result
+OptionParser::error(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "Error: ");
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fputs("\n\n", stderr);
+ return ParseError;
+}
+
+/* Quick and dirty paragraph printer. */
+static void
+PrintParagraph(const char* text, unsigned startColno, const unsigned limitColno, bool padFirstLine)
+{
+ unsigned colno = startColno;
+ unsigned indent = 0;
+ const char* it = text;
+
+ if (padFirstLine)
+ printf("%*s", startColno, "");
+
+ /* Skip any leading spaces. */
+ while (*it != '\0' && isspace(*it))
+ ++it;
+
+ while (*it != '\0') {
+ MOZ_ASSERT(!isspace(*it) || *it == '\n');
+
+ /* Delimit the current token. */
+ const char* limit = it;
+ while (!isspace(*limit) && *limit != '\0')
+ ++limit;
+
+ /*
+ * If the current token is longer than the available number of columns,
+ * then make a line break before printing the token.
+ */
+ size_t tokLen = limit - it;
+ if (tokLen + colno >= limitColno) {
+ printf("\n%*s%.*s", startColno + indent, "", int(tokLen), it);
+ colno = startColno + tokLen;
+ } else {
+ printf("%.*s", int(tokLen), it);
+ colno += tokLen;
+ }
+
+ switch (*limit) {
+ case '\0':
+ return;
+ case ' ':
+ putchar(' ');
+ colno += 1;
+ it = limit;
+ while (*it == ' ')
+ ++it;
+ break;
+ case '\n':
+ /* |text| wants to force a newline here. */
+ printf("\n%*s", startColno, "");
+ colno = startColno;
+ it = limit + 1;
+ /* Could also have line-leading spaces. */
+ indent = 0;
+ while (*it == ' ') {
+ putchar(' ');
+ ++colno;
+ ++indent;
+ ++it;
+ }
+ break;
+ default:
+ MOZ_CRASH("unhandled token splitting character in text");
+ }
+ }
+}
+
+static const char*
+OptionFlagsToFormatInfo(char shortflag, bool isValued, size_t* length)
+{
+ static const char * const fmt[4] = { " -%c --%s ",
+ " --%s ",
+ " -%c --%s=%s ",
+ " --%s=%s " };
+
+ /* How mny chars w/o longflag? */
+ size_t lengths[4] = { strlen(fmt[0]) - 3,
+ strlen(fmt[1]) - 3,
+ strlen(fmt[2]) - 5,
+ strlen(fmt[3]) - 5 };
+ int index = isValued ? 2 : 0;
+ if (!shortflag)
+ index++;
+
+ *length = lengths[index];
+ return fmt[index];
+}
+
+OptionParser::Result
+OptionParser::printHelp(const char* progname)
+{
+ const char* prefixEnd = strstr(usage, prognameMeta);
+ if (prefixEnd) {
+ printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
+ prefixEnd + sizeof(prognameMeta) - 1);
+ } else {
+ puts(usage);
+ }
+
+ if (descr) {
+ putchar('\n');
+ PrintParagraph(descr, 2, descrWidth, true);
+ putchar('\n');
+ }
+
+ if (version)
+ printf("\nVersion: %s\n\n", version);
+
+ if (!arguments.empty()) {
+ printf("Arguments:\n");
+
+ static const char fmt[] = " %s ";
+ size_t fmtChars = sizeof(fmt) - 2;
+ size_t lhsLen = 0;
+ for (Option* arg : arguments)
+ lhsLen = Max(lhsLen, strlen(arg->longflag) + fmtChars);
+
+ for (Option* arg : arguments) {
+ size_t chars = printf(fmt, arg->longflag);
+ for (; chars < lhsLen; ++chars)
+ putchar(' ');
+ PrintParagraph(arg->help, lhsLen, helpWidth, false);
+ putchar('\n');
+ }
+ putchar('\n');
+ }
+
+ if (!options.empty()) {
+ printf("Options:\n");
+
+ /* Calculate sizes for column alignment. */
+ size_t lhsLen = 0;
+ for (Option* opt : options) {
+ size_t longflagLen = strlen(opt->longflag);
+
+ size_t fmtLen;
+ OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
+
+ size_t len = fmtLen + longflagLen;
+ if (opt->isValued())
+ len += strlen(opt->asValued()->metavar);
+ lhsLen = Max(lhsLen, len);
+ }
+
+ /* Print option help text. */
+ for (Option* opt : options) {
+ size_t fmtLen;
+ const char* fmt = OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
+ size_t chars;
+ if (opt->isValued()) {
+ if (opt->shortflag)
+ chars = printf(fmt, opt->shortflag, opt->longflag, opt->asValued()->metavar);
+ else
+ chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
+ } else {
+ if (opt->shortflag)
+ chars = printf(fmt, opt->shortflag, opt->longflag);
+ else
+ chars = printf(fmt, opt->longflag);
+ }
+ for (; chars < lhsLen; ++chars)
+ putchar(' ');
+ PrintParagraph(opt->help, lhsLen, helpWidth, false);
+ putchar('\n');
+ }
+ }
+
+ return EarlyExit;
+}
+
+OptionParser::Result
+OptionParser::printVersion()
+{
+ MOZ_ASSERT(version);
+ printf("%s\n", version);
+ return EarlyExit;
+}
+
+OptionParser::Result
+OptionParser::extractValue(size_t argc, char** argv, size_t* i, char** value)
+{
+ MOZ_ASSERT(*i < argc);
+ char* eq = strchr(argv[*i], '=');
+ if (eq) {
+ *value = eq + 1;
+ if (*value[0] == '\0')
+ return error("A value is required for option %.*s", (int) (eq - argv[*i]), argv[*i]);
+ return Okay;
+ }
+
+ if (argc == *i + 1)
+ return error("Expected a value for option %s", argv[*i]);
+
+ *i += 1;
+ *value = argv[*i];
+ return Okay;
+}
+
+OptionParser::Result
+OptionParser::handleOption(Option* opt, size_t argc, char** argv, size_t* i, bool* optionsAllowed)
+{
+ if (opt->getTerminatesOptions())
+ *optionsAllowed = false;
+
+ switch (opt->kind) {
+ case OptionKindBool:
+ {
+ if (opt == &helpOption)
+ return printHelp(argv[0]);
+ if (opt == &versionOption)
+ return printVersion();
+ opt->asBoolOption()->value = true;
+ return Okay;
+ }
+ /*
+ * Valued options are allowed to specify their values either via
+ * successive arguments or a single --longflag=value argument.
+ */
+ case OptionKindString:
+ {
+ char* value = nullptr;
+ if (Result r = extractValue(argc, argv, i, &value))
+ return r;
+ opt->asStringOption()->value = value;
+ return Okay;
+ }
+ case OptionKindInt:
+ {
+ char* value = nullptr;
+ if (Result r = extractValue(argc, argv, i, &value))
+ return r;
+ opt->asIntOption()->value = atoi(value);
+ return Okay;
+ }
+ case OptionKindMultiString:
+ {
+ char* value = nullptr;
+ if (Result r = extractValue(argc, argv, i, &value))
+ return r;
+ StringArg arg(value, *i);
+ return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
+ }
+ default:
+ MOZ_CRASH("unhandled option kind");
+ }
+}
+
+OptionParser::Result
+OptionParser::handleArg(size_t argc, char** argv, size_t* i, bool* optionsAllowed)
+{
+ if (nextArgument >= arguments.length())
+ return error("Too many arguments provided");
+
+ Option* arg = arguments[nextArgument];
+
+ if (arg->getTerminatesOptions())
+ *optionsAllowed = false;
+
+ switch (arg->kind) {
+ case OptionKindString:
+ arg->asStringOption()->value = argv[*i];
+ nextArgument += 1;
+ return Okay;
+ case OptionKindMultiString:
+ {
+ /* Don't advance the next argument -- there can only be one (final) variadic argument. */
+ StringArg value(argv[*i], *i);
+ return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
+ }
+ default:
+ MOZ_CRASH("unhandled argument kind");
+ }
+}
+
+OptionParser::Result
+OptionParser::parseArgs(int inputArgc, char** argv)
+{
+ MOZ_ASSERT(inputArgc >= 0);
+ size_t argc = inputArgc;
+ /* Permit a "no more options" capability, like |--| offers in many shell interfaces. */
+ bool optionsAllowed = true;
+
+ for (size_t i = 1; i < argc; ++i) {
+ char* arg = argv[i];
+ Result r;
+ /* Note: solo dash option is actually a 'stdin' argument. */
+ if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
+ /* Option. */
+ Option* opt;
+ if (arg[1] == '-') {
+ if (arg[2] == '\0') {
+ /* End of options */
+ optionsAllowed = false;
+ nextArgument = restArgument;
+ continue;
+ } else {
+ /* Long option. */
+ opt = findOption(arg + 2);
+ if (!opt)
+ return error("Invalid long option: %s", arg);
+ }
+ } else {
+ /* Short option */
+ if (arg[2] != '\0')
+ return error("Short option followed by junk: %s", arg);
+ opt = findOption(arg[1]);
+ if (!opt)
+ return error("Invalid short option: %s", arg);
+ }
+
+ r = handleOption(opt, argc, argv, &i, &optionsAllowed);
+ } else {
+ /* Argument. */
+ r = handleArg(argc, argv, &i, &optionsAllowed);
+ }
+
+ if (r != Okay)
+ return r;
+ }
+ return Okay;
+}
+
+void
+OptionParser::setHelpOption(char shortflag, const char* longflag, const char* help)
+{
+ helpOption.setFlagInfo(shortflag, longflag, help);
+}
+
+bool
+OptionParser::getHelpOption() const
+{
+ return helpOption.value;
+}
+
+bool
+OptionParser::getBoolOption(char shortflag) const
+{
+ return findOption(shortflag)->asBoolOption()->value;
+}
+
+int
+OptionParser::getIntOption(char shortflag) const
+{
+ return findOption(shortflag)->asIntOption()->value;
+}
+
+const char*
+OptionParser::getStringOption(char shortflag) const
+{
+ return findOption(shortflag)->asStringOption()->value;
+}
+
+MultiStringRange
+OptionParser::getMultiStringOption(char shortflag) const
+{
+ const MultiStringOption* mso = findOption(shortflag)->asMultiStringOption();
+ return MultiStringRange(mso->strings.begin(), mso->strings.end());
+}
+
+bool
+OptionParser::getBoolOption(const char* longflag) const
+{
+ return findOption(longflag)->asBoolOption()->value;
+}
+
+int
+OptionParser::getIntOption(const char* longflag) const
+{
+ return findOption(longflag)->asIntOption()->value;
+}
+
+const char*
+OptionParser::getStringOption(const char* longflag) const
+{
+ return findOption(longflag)->asStringOption()->value;
+}
+
+MultiStringRange
+OptionParser::getMultiStringOption(const char* longflag) const
+{
+ const MultiStringOption* mso = findOption(longflag)->asMultiStringOption();
+ return MultiStringRange(mso->strings.begin(), mso->strings.end());
+}
+
+OptionParser::~OptionParser()
+{
+ for (Option* opt : options)
+ js_delete<Option>(opt);
+ for (Option* arg : arguments)
+ js_delete<Option>(arg);
+}
+
+Option*
+OptionParser::findOption(char shortflag)
+{
+ for (Option* opt : options) {
+ if (opt->shortflag == shortflag)
+ return opt;
+ }
+
+ if (versionOption.shortflag == shortflag)
+ return &versionOption;
+
+ return helpOption.shortflag == shortflag ? &helpOption : nullptr;
+}
+
+const Option*
+OptionParser::findOption(char shortflag) const
+{
+ return const_cast<OptionParser*>(this)->findOption(shortflag);
+}
+
+Option*
+OptionParser::findOption(const char* longflag)
+{
+ for (Option* opt : options) {
+ const char* target = opt->longflag;
+ if (opt->isValued()) {
+ size_t targetLen = strlen(target);
+ /* Permit a trailing equals sign on the longflag argument. */
+ for (size_t i = 0; i < targetLen; ++i) {
+ if (longflag[i] == '\0' || longflag[i] != target[i])
+ goto no_match;
+ }
+ if (longflag[targetLen] == '\0' || longflag[targetLen] == '=')
+ return opt;
+ } else {
+ if (strcmp(target, longflag) == 0)
+ return opt;
+ }
+ no_match:;
+ }
+
+ if (strcmp(versionOption.longflag, longflag) == 0)
+ return &versionOption;
+
+ return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
+}
+
+const Option*
+OptionParser::findOption(const char* longflag) const
+{
+ return const_cast<OptionParser*>(this)->findOption(longflag);
+}
+
+/* Argument accessors */
+
+int
+OptionParser::findArgumentIndex(const char* name) const
+{
+ for (Option * const* it = arguments.begin(); it != arguments.end(); ++it) {
+ const char* target = (*it)->longflag;
+ if (strcmp(target, name) == 0)
+ return it - arguments.begin();
+ }
+ return -1;
+}
+
+Option*
+OptionParser::findArgument(const char* name)
+{
+ int index = findArgumentIndex(name);
+ return (index == -1) ? nullptr : arguments[index];
+}
+
+const Option*
+OptionParser::findArgument(const char* name) const
+{
+ int index = findArgumentIndex(name);
+ return (index == -1) ? nullptr : arguments[index];
+}
+
+const char*
+OptionParser::getStringArg(const char* name) const
+{
+ return findArgument(name)->asStringOption()->value;
+}
+
+MultiStringRange
+OptionParser::getMultiStringArg(const char* name) const
+{
+ const MultiStringOption* mso = findArgument(name)->asMultiStringOption();
+ return MultiStringRange(mso->strings.begin(), mso->strings.end());
+}
+
+/* Option builders */
+
+bool
+OptionParser::addIntOption(char shortflag, const char* longflag, const char* metavar,
+ const char* help, int defaultValue)
+{
+ if (!options.reserve(options.length() + 1))
+ return false;
+ IntOption* io = js_new<IntOption>(shortflag, longflag, help, metavar, defaultValue);
+ if (!io)
+ return false;
+ options.infallibleAppend(io);
+ return true;
+}
+
+bool
+OptionParser::addBoolOption(char shortflag, const char* longflag, const char* help)
+{
+ if (!options.reserve(options.length() + 1))
+ return false;
+ BoolOption* bo = js_new<BoolOption>(shortflag, longflag, help);
+ if (!bo)
+ return false;
+ options.infallibleAppend(bo);
+ return true;
+}
+
+bool
+OptionParser::addStringOption(char shortflag, const char* longflag, const char* metavar,
+ const char* help)
+{
+ if (!options.reserve(options.length() + 1))
+ return false;
+ StringOption* so = js_new<StringOption>(shortflag, longflag, help, metavar);
+ if (!so)
+ return false;
+ options.infallibleAppend(so);
+ return true;
+}
+
+bool
+OptionParser::addMultiStringOption(char shortflag, const char* longflag, const char* metavar,
+ const char* help)
+{
+ if (!options.reserve(options.length() + 1))
+ return false;
+ MultiStringOption* mso = js_new<MultiStringOption>(shortflag, longflag, help, metavar);
+ if (!mso)
+ return false;
+ options.infallibleAppend(mso);
+ return true;
+}
+
+/* Argument builders */
+
+bool
+OptionParser::addOptionalStringArg(const char* name, const char* help)
+{
+ if (!arguments.reserve(arguments.length() + 1))
+ return false;
+ StringOption* so = js_new<StringOption>(1, name, help, (const char*) nullptr);
+ if (!so)
+ return false;
+ arguments.infallibleAppend(so);
+ return true;
+}
+
+bool
+OptionParser::addOptionalMultiStringArg(const char* name, const char* help)
+{
+ MOZ_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
+ if (!arguments.reserve(arguments.length() + 1))
+ return false;
+ MultiStringOption* mso = js_new<MultiStringOption>(1, name, help, (const char*) nullptr);
+ if (!mso)
+ return false;
+ arguments.infallibleAppend(mso);
+ return true;
+}
diff --git a/js/src/shell/jsoptparse.h b/js/src/shell/jsoptparse.h
new file mode 100644
index 000000000..274978130
--- /dev/null
+++ b/js/src/shell/jsoptparse.h
@@ -0,0 +1,298 @@
+/* -*- 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/. */
+
+#ifndef shell_jsoptparse_h
+#define shell_jsoptparse_h
+
+#include <stdio.h>
+
+#include "jsalloc.h"
+#include "jsutil.h"
+
+#include "js/Vector.h"
+
+namespace js {
+namespace cli {
+
+namespace detail {
+
+struct BoolOption;
+struct MultiStringOption;
+struct ValuedOption;
+struct StringOption;
+struct IntOption;
+
+enum OptionKind
+{
+ OptionKindBool,
+ OptionKindString,
+ OptionKindInt,
+ OptionKindMultiString,
+ OptionKindInvalid
+};
+
+struct Option
+{
+ const char* longflag;
+ const char* help;
+ OptionKind kind;
+ char shortflag;
+ bool terminatesOptions;
+
+ Option(OptionKind kind, char shortflag, const char* longflag, const char* help)
+ : longflag(longflag), help(help), kind(kind), shortflag(shortflag), terminatesOptions(false)
+ {}
+
+ virtual ~Option() = 0;
+
+ void setTerminatesOptions(bool enabled) { terminatesOptions = enabled; }
+ bool getTerminatesOptions() const { return terminatesOptions; }
+
+ virtual bool isValued() const { return false; }
+
+ /* Only some valued options are variadic (like MultiStringOptions). */
+ virtual bool isVariadic() const { return false; }
+
+ /*
+ * For arguments, the shortflag field is used to indicate whether the
+ * argument is optional.
+ */
+ bool isOptional() { return shortflag; }
+
+ void setFlagInfo(char shortflag, const char* longflag, const char* help) {
+ this->shortflag = shortflag;
+ this->longflag = longflag;
+ this->help = help;
+ }
+
+ ValuedOption* asValued();
+ const ValuedOption* asValued() const;
+
+#define OPTION_CONVERT_DECL(__cls) \
+ bool is##__cls##Option() const; \
+ __cls##Option* as##__cls##Option(); \
+ const __cls##Option* as##__cls##Option() const;
+
+ OPTION_CONVERT_DECL(Bool)
+ OPTION_CONVERT_DECL(String)
+ OPTION_CONVERT_DECL(Int)
+ OPTION_CONVERT_DECL(MultiString)
+};
+
+inline Option::~Option() {}
+
+struct BoolOption : public Option
+{
+ size_t argno;
+ bool value;
+
+ BoolOption(char shortflag, const char* longflag, const char* help)
+ : Option(OptionKindBool, shortflag, longflag, help), value(false)
+ {}
+
+ virtual ~BoolOption() {}
+};
+
+struct ValuedOption : public Option
+{
+ const char* metavar;
+
+ ValuedOption(OptionKind kind, char shortflag, const char* longflag, const char* help,
+ const char* metavar)
+ : Option(kind, shortflag, longflag, help), metavar(metavar)
+ {}
+
+ virtual ~ValuedOption() = 0;
+ virtual bool isValued() const { return true; }
+};
+
+inline ValuedOption::~ValuedOption() {}
+
+struct StringOption : public ValuedOption
+{
+ const char* value;
+
+ StringOption(char shortflag, const char* longflag, const char* help, const char* metavar)
+ : ValuedOption(OptionKindString, shortflag, longflag, help, metavar), value(nullptr)
+ {}
+
+ virtual ~StringOption() {}
+};
+
+struct IntOption : public ValuedOption
+{
+ int value;
+
+ IntOption(char shortflag, const char* longflag, const char* help, const char* metavar,
+ int defaultValue)
+ : ValuedOption(OptionKindInt, shortflag, longflag, help, metavar), value(defaultValue)
+ {}
+
+ virtual ~IntOption() {}
+};
+
+struct StringArg
+{
+ char* value;
+ size_t argno;
+
+ StringArg(char* value, size_t argno) : value(value), argno(argno) {}
+};
+
+struct MultiStringOption : public ValuedOption
+{
+ Vector<StringArg, 0, SystemAllocPolicy> strings;
+
+ MultiStringOption(char shortflag, const char* longflag, const char* help, const char* metavar)
+ : ValuedOption(OptionKindMultiString, shortflag, longflag, help, metavar)
+ {}
+
+ virtual ~MultiStringOption() {}
+
+ virtual bool isVariadic() const { return true; }
+};
+
+} /* namespace detail */
+
+class MultiStringRange
+{
+ typedef detail::StringArg StringArg;
+ const StringArg* cur;
+ const StringArg* end;
+
+ public:
+ explicit MultiStringRange(const StringArg* cur, const StringArg* end)
+ : cur(cur), end(end) {
+ MOZ_ASSERT(end - cur >= 0);
+ }
+
+ bool empty() const { return cur == end; }
+ void popFront() { MOZ_ASSERT(!empty()); ++cur; }
+ char* front() const { MOZ_ASSERT(!empty()); return cur->value; }
+ size_t argno() const { MOZ_ASSERT(!empty()); return cur->argno; }
+};
+
+/*
+ * Builder for describing a command line interface and parsing the resulting
+ * specification.
+ *
+ * - A multi-option is an option that can appear multiple times and still
+ * parse as valid command line arguments.
+ * - An "optional argument" is supported for backwards compatibility with prior
+ * command line interface usage. Once one optional argument has been added,
+ * *only* optional arguments may be added.
+ */
+class OptionParser
+{
+ public:
+ enum Result
+ {
+ Okay = 0,
+ Fail, /* As in, allocation fail. */
+ ParseError, /* Successfully parsed but with an error. */
+ EarlyExit /* Successfully parsed but exits the program,
+ * for example with --help and --version. */
+ };
+
+ private:
+ typedef Vector<detail::Option*, 0, SystemAllocPolicy> Options;
+ typedef detail::Option Option;
+ typedef detail::BoolOption BoolOption;
+
+ Options options;
+ Options arguments;
+ BoolOption helpOption;
+ BoolOption versionOption;
+ const char* usage;
+ const char* version;
+ const char* descr;
+ size_t descrWidth;
+ size_t helpWidth;
+ size_t nextArgument;
+
+ // If '--' is passed, all remaining arguments should be interpreted as the
+ // argument at index 'restArgument'. Defaults to the next unassigned
+ // argument.
+ int restArgument;
+
+ static const char prognameMeta[];
+
+ Option* findOption(char shortflag);
+ const Option* findOption(char shortflag) const;
+ Option* findOption(const char* longflag);
+ const Option* findOption(const char* longflag) const;
+ int findArgumentIndex(const char* name) const;
+ Option* findArgument(const char* name);
+ const Option* findArgument(const char* name) const;
+
+ Result error(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
+ Result extractValue(size_t argc, char** argv, size_t* i, char** value);
+ Result handleArg(size_t argc, char** argv, size_t* i, bool* optsAllowed);
+ Result handleOption(Option* opt, size_t argc, char** argv, size_t* i, bool* optsAllowed);
+
+ public:
+ explicit OptionParser(const char* usage)
+ : helpOption('h', "help", "Display help information"),
+ versionOption('v', "version", "Display version information and exit"),
+ usage(usage), version(nullptr), descr(nullptr), descrWidth(80), helpWidth(80),
+ nextArgument(0), restArgument(-1)
+ {}
+
+ ~OptionParser();
+
+ Result parseArgs(int argc, char** argv);
+ Result printHelp(const char* progname);
+ Result printVersion();
+
+ /* Metadata */
+
+ void setVersion(const char* v) { version = v; }
+ void setHelpWidth(size_t width) { helpWidth = width; }
+ void setDescriptionWidth(size_t width) { descrWidth = width; }
+ void setDescription(const char* description) { descr = description; }
+ void setHelpOption(char shortflag, const char* longflag, const char* help);
+ void setArgTerminatesOptions(const char* name, bool enabled);
+ void setArgCapturesRest(const char* name);
+
+ /* Arguments: no further arguments may be added after a variadic argument. */
+
+ bool addOptionalStringArg(const char* name, const char* help);
+ bool addOptionalMultiStringArg(const char* name, const char* help);
+
+ const char* getStringArg(const char* name) const;
+ MultiStringRange getMultiStringArg(const char* name) const;
+
+ /* Options */
+
+ bool addBoolOption(char shortflag, const char* longflag, const char* help);
+ bool addStringOption(char shortflag, const char* longflag, const char* help,
+ const char* metavar);
+ bool addIntOption(char shortflag, const char* longflag, const char* help,
+ const char* metavar, int defaultValue);
+ bool addMultiStringOption(char shortflag, const char* longflag, const char* help,
+ const char* metavar);
+ bool addOptionalVariadicArg(const char* name);
+
+ int getIntOption(char shortflag) const;
+ int getIntOption(const char* longflag) const;
+ const char* getStringOption(char shortflag) const;
+ const char* getStringOption(const char* longflag) const;
+ bool getBoolOption(char shortflag) const;
+ bool getBoolOption(const char* longflag) const;
+ MultiStringRange getMultiStringOption(char shortflag) const;
+ MultiStringRange getMultiStringOption(const char* longflag) const;
+
+ /*
+ * Return whether the help option was present (and thus help was already
+ * displayed during parse_args).
+ */
+ bool getHelpOption() const;
+};
+
+} /* namespace cli */
+} /* namespace js */
+
+#endif /* shell_jsoptparse_h */
diff --git a/js/src/shell/jsshell.cpp b/js/src/shell/jsshell.cpp
new file mode 100644
index 000000000..7f8e53b9a
--- /dev/null
+++ b/js/src/shell/jsshell.cpp
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+// jsshell.cpp - Utilities for the JS shell
+
+#include "shell/jsshell.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+#include "vm/StringBuffer.h"
+
+using namespace JS;
+
+namespace js {
+namespace shell {
+
+bool
+GenerateInterfaceHelp(JSContext* cx, HandleObject obj, const char* name)
+{
+ AutoIdVector idv(cx);
+ if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv))
+ return false;
+
+ StringBuffer buf(cx);
+ if (!buf.append(' '))
+ return false;
+
+ for (size_t i = 0; i < idv.length(); i++) {
+ RootedValue v(cx);
+ RootedId id(cx, idv[i]);
+ if (!JS_GetPropertyById(cx, obj, id, &v))
+ return false;
+ if (!v.isObject())
+ continue;
+ bool hasHelp = false;
+ RootedObject prop(cx, &v.toObject());
+ if (!JS_GetProperty(cx, prop, "usage", &v))
+ return false;
+ if (v.isString())
+ hasHelp = true;
+ if (!JS_GetProperty(cx, prop, "help", &v))
+ return false;
+ if (v.isString())
+ hasHelp = true;
+ if (hasHelp) {
+ if (!buf.append(' ') ||
+ !buf.append(name, strlen(name)) ||
+ !buf.append('.') ||
+ !buf.append(JSID_TO_FLAT_STRING(id)))
+ {
+ return false;
+ }
+ }
+ }
+
+ RootedString s(cx, buf.finishString());
+ if (!s || !JS_DefineProperty(cx, obj, "help", s, 0))
+ return false;
+
+ if (!buf.append(name, strlen(name)) || !buf.append(" - interface object", 20))
+ return false;
+ s = buf.finishString();
+ if (!s || !JS_DefineProperty(cx, obj, "usage", s, 0))
+ return false;
+
+ return true;
+}
+
+} // namespace shell
+} // namespace js
diff --git a/js/src/shell/jsshell.h b/js/src/shell/jsshell.h
new file mode 100644
index 000000000..a3ecbef99
--- /dev/null
+++ b/js/src/shell/jsshell.h
@@ -0,0 +1,84 @@
+/* -*- 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/. */
+
+#ifndef jsshell_js_h
+#define jsshell_js_h
+
+#include "jsapi.h"
+
+namespace js {
+namespace shell {
+
+enum JSShellErrNum {
+#define MSG_DEF(name, count, exception, format) \
+ name,
+#include "jsshell.msg"
+#undef MSG_DEF
+ JSShellErr_Limit
+};
+
+const JSErrorFormatString*
+my_GetErrorMessage(void* userRef, const unsigned errorNumber);
+
+void
+WarningReporter(JSContext* cx, JSErrorReport* report);
+
+class MOZ_STACK_CLASS AutoReportException
+{
+ JSContext* cx;
+ public:
+ explicit AutoReportException(JSContext* cx)
+ : cx(cx)
+ {}
+ ~AutoReportException();
+};
+
+bool
+GenerateInterfaceHelp(JSContext* cx, JS::HandleObject obj, const char* name);
+
+JSString*
+FileAsString(JSContext* cx, JS::HandleString pathnameStr);
+
+class AutoCloseFile
+{
+ private:
+ FILE* f_;
+ public:
+ explicit AutoCloseFile(FILE* f) : f_(f) {}
+ ~AutoCloseFile() {
+ (void) release();
+ }
+ bool release() {
+ bool success = true;
+ if (f_ && f_ != stdin && f_ != stdout && f_ != stderr)
+ success = !fclose(f_);
+ f_ = nullptr;
+ return success;
+ }
+};
+
+// Reference counted file.
+struct RCFile {
+ FILE* fp;
+ uint32_t numRefs;
+
+ RCFile() : fp(nullptr), numRefs(0) {}
+ explicit RCFile(FILE* fp) : fp(fp), numRefs(0) {}
+
+ void acquire() { numRefs++; }
+
+ // Starts out with a ref count of zero.
+ static RCFile* create(JSContext* cx, const char* filename, const char* mode);
+
+ void close();
+ bool isOpen() const { return fp; }
+ bool release();
+};
+
+} /* namespace shell */
+} /* namespace js */
+
+#endif
diff --git a/js/src/shell/moz.build b/js/src/shell/moz.build
new file mode 100644
index 000000000..72ea8145c
--- /dev/null
+++ b/js/src/shell/moz.build
@@ -0,0 +1,67 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG['JS_SHELL_NAME']:
+ GeckoProgram(CONFIG['JS_SHELL_NAME'], linkage=None)
+ if CONFIG['JS_BUNDLED_EDITLINE']:
+ USE_LIBS += ['editline']
+ USE_LIBS += ['static:js']
+
+UNIFIED_SOURCES += [
+ 'js.cpp',
+ 'jsoptparse.cpp',
+ 'jsshell.cpp',
+ 'OSObject.cpp'
+]
+
+DEFINES['EXPORT_JS_API'] = True
+
+# Also set in ../moz.build
+DEFINES['ENABLE_SHARED_ARRAY_BUFFER'] = True
+
+if CONFIG['_MSC_VER']:
+ # unnecessary PGO for js shell. But gcc cannot turn off pgo because it is
+ # necessary to link PGO lib on gcc when a object/static lib are compiled
+ # for PGO.
+ NO_PGO = True
+
+LOCAL_INCLUDES += [
+ '!..',
+ '..',
+]
+
+OS_LIBS += CONFIG['EDITLINE_LIBS']
+OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
+if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']:
+ # The ICU libraries linked into libmozjs will not include the ICU data,
+ # so link it directly.
+ USE_LIBS += ['icudata']
+
+# Prepare module loader JS code for embedding
+GENERATED_FILES += ['shellmoduleloader.out.h']
+shellmoduleloader = GENERATED_FILES['shellmoduleloader.out.h']
+shellmoduleloader.script = '../builtin/embedjs.py:generate_shellmoduleloader'
+shellmoduleloader.inputs = [
+ '../js.msg',
+ 'ModuleLoader.js',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow', '-Werror=format']
+
+# This is intended as a temporary workaround to enable VS2015.
+if CONFIG['_MSC_VER']:
+ CXXFLAGS += ['-wd4312']
+
+# Place a GDB Python auto-load file next to the shell executable, both in
+# the build directory and in the dist/bin directory.
+DEFINES['topsrcdir'] = '%s/js/src' % TOPSRCDIR
+FINAL_TARGET_PP_FILES += ['js-gdb.py.in']
+OBJDIR_FILES.js.src.shell += ['!/dist/bin/js-gdb.py']
+
+# People expect the js shell to wind up in the top-level JS dir.
+OBJDIR_FILES.js.src += ['!js%s' % CONFIG['BIN_SUFFIX']]