/* -*- 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 TimeDuration MAX_TIMEOUT_INTERVAL = TimeDuration::FromSeconds(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));
        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;
    }
    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;
}
