js/src/shell/js.cpp
author Jeff Walden <jwalden@mit.edu>
Sun, 12 May 2019 01:39:15 +0000
changeset 532355 b435e9bb58cc70ce432f6f3fc837fa318d35a637
parent 532307 776f4df8561d0d4d106aa803e246ad33884493f3
child 532357 464bde42a1588d89f7822d175f86db7f93d211ea
permissions -rw-r--r--
Bug 1550616 - Move ModuleBuilder into its own header so that module object-related data structures don't depend on EitherParser.h (and therefore all of parsing and tokenizing). r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D30578

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/Sprintf.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/Variant.h"

#include <chrono>
#ifdef JS_POSIX_NSPR
#  include <dlfcn.h>
#endif
#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>
#include <thread>
#include <utility>
#ifdef XP_UNIX
#  include <sys/mman.h>
#  include <sys/stat.h>
#  include <sys/wait.h>
#  include <unistd.h>
#endif
#ifdef XP_LINUX
#  include <sys/prctl.h>
#endif

#include "jsapi.h"
#include "jsfriendapi.h"
#include "jstypes.h"
#include "jsutil.h"
#ifndef JS_POSIX_NSPR
#  include "prerror.h"
#  include "prlink.h"
#endif
#include "shellmoduleloader.out.h"

#include "builtin/Array.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
#include "builtin/TestingFunctions.h"
#if defined(JS_BUILD_BINAST)
#  include "frontend/BinASTParser.h"
#endif  // defined(JS_BUILD_BINAST)
#include "frontend/ModuleSharedContext.h"
#include "frontend/Parser.h"
#include "gc/PublicIterators.h"
#include "jit/arm/Simulator-arm.h"
#include "jit/InlinableNatives.h"
#include "jit/Ion.h"
#include "jit/JitcodeMap.h"
#include "jit/JitRealm.h"
#include "jit/shared/CodeGenerator-shared.h"
#include "js/ArrayBuffer.h"  // JS::{CreateMappedArrayBufferContents,NewMappedArrayBufferWithContents,IsArrayBufferObject,GetArrayBufferLengthAndData}
#include "js/BuildId.h"      // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/CompileOptions.h"
#include "js/ContextOptions.h"  // JS::ContextOptions{,Ref}
#include "js/Debug.h"
#include "js/Equality.h"  // JS::SameValue
#include "js/experimental/SourceHook.h"  // js::{Set,Forget,}SourceHook
#include "js/GCVector.h"
#include "js/Initialization.h"
#include "js/JSON.h"
#include "js/MemoryFunctions.h"
#include "js/Printf.h"
#include "js/PropertySpec.h"
#include "js/Realm.h"
#include "js/RegExp.h"  // JS::ObjectIsRegExp
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/StructuredClone.h"
#include "js/SweepingAPI.h"
#include "js/Warnings.h"  // JS::SetWarningReporter
#include "js/Wrapper.h"
#include "perf/jsperf.h"
#include "shell/jsoptparse.h"
#include "shell/jsshell.h"
#include "shell/OSObject.h"
#include "threading/ConditionVariable.h"
#include "threading/ExclusiveData.h"
#include "threading/LockGuard.h"
#include "threading/Thread.h"
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "util/Windows.h"
#include "vm/ArgumentsObject.h"
#include "vm/Compression.h"
#include "vm/Debugger.h"
#include "vm/HelperThreads.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "vm/ModuleBuilder.h"  // js::ModuleBuilder
#include "vm/Monitor.h"
#include "vm/MutexIDs.h"
#include "vm/Printer.h"
#include "vm/Shape.h"
#include "vm/SharedArrayObject.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "vm/WrapperObject.h"
#include "wasm/WasmJS.h"

#include "vm/Compartment-inl.h"
#include "vm/ErrorObject-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/Realm-inl.h"
#include "vm/Stack-inl.h"

using namespace js;
using namespace js::cli;
using namespace js::shell;

using JS::AutoStableStringChars;
using JS::CompileOptions;

using js::shell::RCFile;

using mozilla::ArrayEqual;
using mozilla::ArrayLength;
using mozilla::AsVariant;
using mozilla::Atomic;
using mozilla::MakeScopeExit;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::NumberEqualsInt32;
using mozilla::TimeDuration;
using mozilla::TimeStamp;
using mozilla::Utf8Unit;
using mozilla::Variant;

// Avoid an unnecessary NSPR dependency on Linux and OS X just for the shell.
#ifdef JS_POSIX_NSPR

enum PRLibSpecType { PR_LibSpec_Pathname };

struct PRLibSpec {
  PRLibSpecType type;
  union {
    const char* pathname;
  } value;
};

typedef void PRLibrary;

#  define PR_LD_NOW RTLD_NOW
#  define PR_LD_GLOBAL RTLD_GLOBAL

static PRLibrary* PR_LoadLibraryWithFlags(PRLibSpec libSpec, int flags) {
  return dlopen(libSpec.value.pathname, flags);
}

static void PR_UnloadLibrary(PRLibrary* dll) { dlclose(dll); }
#endif

enum JSShellExitCode {
  EXITCODE_RUNTIME_ERROR = 3,
  EXITCODE_FILE_NOT_FOUND = 4,
  EXITCODE_OUT_OF_MEMORY = 5,
  EXITCODE_TIMEOUT = 6
};

// Define use of application-specific slots on the shell's global object.
enum GlobalAppSlot {
  GlobalAppSlotModuleLoadHook,           // Shell-specific; load a module graph
  GlobalAppSlotModuleResolveHook,        // HostResolveImportedModule
  GlobalAppSlotModuleMetadataHook,       // HostPopulateImportMeta
  GlobalAppSlotModuleDynamicImportHook,  // HostImportModuleDynamically
  GlobalAppSlotCount
};
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
              "Too many applications slots defined for shell global");

/*
 * Note: This limit should match the stack limit set by the browser in
 *       js/xpconnect/src/XPCJSContext.cpp
 */
#if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
#else
static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024;
#endif

/*
 * Limit the timeout to 30 minutes to prevent an overflow on platfoms
 * that represent the time internally in microseconds using 32-bit int.
 */
static const double MAX_TIMEOUT_SECONDS = 1800.0;

// Not necessarily in sync with the browser
#define SHARED_MEMORY_DEFAULT 1

// Code to support GCOV code coverage measurements on standalone shell
#ifdef MOZ_CODE_COVERAGE
#  if defined(__GNUC__) && !defined(__clang__)
extern "C" void __gcov_dump();
extern "C" void __gcov_reset();

void counters_dump(int) { __gcov_dump(); }

void counters_reset(int) { __gcov_reset(); }
#  else
void counters_dump(int) { /* Do nothing */
}

void counters_reset(int) { /* Do nothing */
}
#  endif

static void InstallCoverageSignalHandlers() {
#  ifndef XP_WIN
  fprintf(stderr, "[CodeCoverage] Setting handlers for process %d.\n",
          getpid());

  struct sigaction dump_sa;
  dump_sa.sa_handler = counters_dump;
  dump_sa.sa_flags = SA_RESTART;
  sigemptyset(&dump_sa.sa_mask);
  mozilla::DebugOnly<int> r1 = sigaction(SIGUSR1, &dump_sa, nullptr);
  MOZ_ASSERT(r1 == 0, "Failed to install GCOV SIGUSR1 handler");

  struct sigaction reset_sa;
  reset_sa.sa_handler = counters_reset;
  reset_sa.sa_flags = SA_RESTART;
  sigemptyset(&reset_sa.sa_mask);
  mozilla::DebugOnly<int> r2 = sigaction(SIGUSR2, &reset_sa, nullptr);
  MOZ_ASSERT(r2 == 0, "Failed to install GCOV SIGUSR2 handler");
#  endif
}
#endif

// An off-thread parse or decode job.
class js::shell::OffThreadJob {
  enum State {
    RUNNING,   // Working; no token.
    DONE,      // Finished; have token.
    CANCELLED  // Cancelled due to error.
  };

 public:
  using Source = mozilla::Variant<JS::UniqueTwoByteChars, JS::TranscodeBuffer>;

  OffThreadJob(ShellContext* sc, ScriptKind kind, Source&& source);
  ~OffThreadJob();

  void cancel();
  void markDone(JS::OffThreadToken* newToken);
  JS::OffThreadToken* waitUntilDone(JSContext* cx);

  char16_t* sourceChars() { return source.as<UniqueTwoByteChars>().get(); }
  JS::TranscodeBuffer& xdrBuffer() { return source.as<JS::TranscodeBuffer>(); }

 public:
  const int32_t id;
  const ScriptKind kind;

 private:
  js::Monitor& monitor;
  State state;
  JS::OffThreadToken* token;
  Source source;
};

static OffThreadJob* NewOffThreadJob(JSContext* cx, ScriptKind kind,
                                     OffThreadJob::Source&& source) {
  ShellContext* sc = GetShellContext(cx);
  UniquePtr<OffThreadJob> job(
      cx->new_<OffThreadJob>(sc, kind, std::move(source)));
  if (!job) {
    return nullptr;
  }

  if (!sc->offThreadJobs.append(job.get())) {
    job->cancel();
    JS_ReportErrorASCII(cx, "OOM adding off-thread job");
    return nullptr;
  }

  return job.release();
}

static OffThreadJob* GetSingleOffThreadJob(JSContext* cx, ScriptKind kind) {
  ShellContext* sc = GetShellContext(cx);
  const auto& jobs = sc->offThreadJobs;
  if (jobs.empty()) {
    JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
    return nullptr;
  }

  if (jobs.length() > 1) {
    JS_ReportErrorASCII(
        cx, "Multiple off-thread jobs are pending: must specify job ID");
    return nullptr;
  }

  OffThreadJob* job = jobs[0];
  if (job->kind != kind) {
    JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
    return nullptr;
  }

  return job;
}

static OffThreadJob* LookupOffThreadJobByID(JSContext* cx, ScriptKind kind,
                                            int32_t id) {
  if (id <= 0) {
    JS_ReportErrorASCII(cx, "Bad off-thread job ID");
    return nullptr;
  }

  ShellContext* sc = GetShellContext(cx);
  const auto& jobs = sc->offThreadJobs;
  if (jobs.empty()) {
    JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
    return nullptr;
  }

  OffThreadJob* job = nullptr;
  for (auto someJob : jobs) {
    if (someJob->id == id) {
      job = someJob;
      break;
    }
  }

  if (!job) {
    JS_ReportErrorASCII(cx, "Off-thread job not found");
    return nullptr;
  }

  if (job->kind != kind) {
    JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
    return nullptr;
  }

  return job;
}

static OffThreadJob* LookupOffThreadJobForArgs(JSContext* cx, ScriptKind kind,
                                               const CallArgs& args,
                                               size_t arg) {
  // If the optional ID argument isn't present, get the single pending job.
  if (args.length() <= arg) {
    return GetSingleOffThreadJob(cx, kind);
  }

  // Lookup the job using the specified ID.
  int32_t id = 0;
  RootedValue value(cx, args[arg]);
  if (!ToInt32(cx, value, &id)) {
    return nullptr;
  }

  return LookupOffThreadJobByID(cx, kind, id);
}

static void DeleteOffThreadJob(JSContext* cx, OffThreadJob* job) {
  ShellContext* sc = GetShellContext(cx);
  for (size_t i = 0; i < sc->offThreadJobs.length(); i++) {
    if (sc->offThreadJobs[i] == job) {
      sc->offThreadJobs.erase(&sc->offThreadJobs[i]);
      js_delete(job);
      return;
    }
  }

  MOZ_CRASH("Off-thread job not found");
}

static void CancelOffThreadJobsForContext(JSContext* cx) {
  // Parse jobs may be blocked waiting on GC.
  gc::FinishGC(cx);

  // Wait for jobs belonging to this context.
  ShellContext* sc = GetShellContext(cx);
  while (!sc->offThreadJobs.empty()) {
    OffThreadJob* job = sc->offThreadJobs.popCopy();
    job->waitUntilDone(cx);
    js_delete(job);
  }
}

static void CancelOffThreadJobsForRuntime(JSContext* cx) {
  // Parse jobs may be blocked waiting on GC.
  gc::FinishGC(cx);

  // Cancel jobs belonging to this runtime.
  CancelOffThreadParses(cx->runtime());
  ShellContext* sc = GetShellContext(cx);
  while (!sc->offThreadJobs.empty()) {
    js_delete(sc->offThreadJobs.popCopy());
  }
}

mozilla::Atomic<int32_t> gOffThreadJobSerial(1);

OffThreadJob::OffThreadJob(ShellContext* sc, ScriptKind kind, Source&& source)
    : id(gOffThreadJobSerial++),
      kind(kind),
      monitor(sc->offThreadMonitor),
      state(RUNNING),
      token(nullptr),
      source(std::move(source)) {
  MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
}

OffThreadJob::~OffThreadJob() { MOZ_ASSERT(state != RUNNING); }

void OffThreadJob::cancel() {
  MOZ_ASSERT(state == RUNNING);
  MOZ_ASSERT(!token);

  state = CANCELLED;
}

void OffThreadJob::markDone(JS::OffThreadToken* newToken) {
  AutoLockMonitor alm(monitor);
  MOZ_ASSERT(state == RUNNING);
  MOZ_ASSERT(!token);
  MOZ_ASSERT(newToken);

  token = newToken;
  state = DONE;
  alm.notifyAll();
}

JS::OffThreadToken* OffThreadJob::waitUntilDone(JSContext* cx) {
  AutoLockMonitor alm(monitor);
  MOZ_ASSERT(state != CANCELLED);

  while (state != DONE) {
    alm.wait();
  }

  MOZ_ASSERT(token);
  return token;
}

struct ShellCompartmentPrivate {
  GCPtrObject grayRoot;
};

struct MOZ_STACK_CLASS EnvironmentPreparer
    : public js::ScriptEnvironmentPreparer {
  explicit EnvironmentPreparer(JSContext* cx) {
    js::SetScriptEnvironmentPreparer(cx, this);
  }
  void invoke(JS::HandleObject global, 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 enableSharedMemory = SHARED_MEMORY_DEFAULT;
static bool enableWasmBaseline = false;
static bool enableWasmIon = false;
static bool enableWasmCranelift = false;
#ifdef ENABLE_WASM_GC
static bool enableWasmGc = false;
#endif
static bool enableWasmVerbose = false;
static bool enableTestWasmAwaitTier2 = false;
static bool enableAsyncStacks = false;
static bool enableStreams = false;
static bool enableBigInt = false;
static bool enableFields = false;
static bool enableAwaitFix = false;
#ifdef JS_GC_ZEAL
static uint32_t gZealBits = 0;
static uint32_t gZealFrequency = 0;
#endif
static bool printTiming = false;
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 bool defaultToSameCompartment = true;

#ifdef DEBUG
static bool dumpEntrainedVariables = false;
static bool OOM_printAllocationCount = false;
#endif

static bool SetTimeoutValue(JSContext* cx, double t);

static void KillWatchdog(JSContext* cx);

static bool ScheduleWatchdog(JSContext* cx, double t);

static void CancelExecution(JSContext* cx);

enum class ShellGlobalKind {
  GlobalObject,
  WindowProxy,
};

static JSObject* NewGlobalObject(JSContext* cx, JS::RealmOptions& options,
                                 JSPrincipals* principals,
                                 ShellGlobalKind kind);

/*
 * A toy WindowProxy class for the shell. This is intended for testing code
 * where global |this| is a WindowProxy. All requests are forwarded to the
 * underlying global and no navigation is supported.
 */
const js::Class ShellWindowProxyClass =
    PROXY_CLASS_DEF("ShellWindowProxy", JSCLASS_HAS_RESERVED_SLOTS(1));

JSObject* NewShellWindowProxy(JSContext* cx, JS::HandleObject global) {
  MOZ_ASSERT(global->is<GlobalObject>());

  js::WrapperOptions options;
  options.setClass(&ShellWindowProxyClass);
  options.setSingleton(true);

  JSAutoRealm ar(cx, global);
  JSObject* obj =
      js::Wrapper::New(cx, global, &js::Wrapper::singleton, options);
  MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
  return obj;
}

/*
 * 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 MOZ_EXPORT char* readline(const char* prompt);
extern MOZ_EXPORT void add_history(char* line);
}  // extern "C"
#endif

ShellContext::ShellContext(JSContext* cx)
    : isWorker(false),
      lastWarningEnabled(false),
      trackUnhandledRejections(true),
      timeoutInterval(-1.0),
      startTime(PRMJ_Now()),
      serviceInterrupt(false),
      haveInterruptFunc(false),
      interruptFunc(cx, NullValue()),
      lastWarning(cx, NullValue()),
      promiseRejectionTrackerCallback(cx, NullValue()),
      unhandledRejectedPromises(cx),
      watchdogLock(mutexid::ShellContextWatchdog),
      exitCode(0),
      quitting(false),
      readLineBufPos(0),
      errFilePtr(nullptr),
      outFilePtr(nullptr),
      offThreadMonitor(mutexid::ShellOffThreadState) {}

ShellContext::~ShellContext() { MOZ_ASSERT(offThreadJobs.empty()); }

ShellContext* js::shell::GetShellContext(JSContext* cx) {
  ShellContext* sc = static_cast<ShellContext*>(JS_GetContextPrivate(cx));
  MOZ_ASSERT(sc);
  return sc;
}

static void TraceGrayRoots(JSTracer* trc, void* data) {
  JSRuntime* rt = trc->runtime();
  for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
      auto priv = static_cast<ShellCompartmentPrivate*>(
          JS_GetCompartmentPrivate(comp.get()));
      if (priv) {
        TraceNullableEdge(trc, &priv->grayRoot, "test gray root");
      }
    }
  }
}

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';
      break;
    }

    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 buffer;
}

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);
    JSAutoRealm ar(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) {
    static const char msg[] = "Script terminated by interrupt handler.\n";
    fputs(msg, stderr);

    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 global, Closure& closure) {
  MOZ_ASSERT(JS_IsGlobalObject(global));

  JSContext* cx = TlsContext.get();
  MOZ_ASSERT(!JS_IsExceptionPending(cx));

  AutoRealm ar(cx, global);
  AutoReportException are(cx);
  if (!closure(cx)) {
    return;
  }
}

static bool RegisterScriptPathWithModuleLoader(JSContext* cx,
                                               HandleScript script,
                                               const char* filename) {
  // Set the private value associated with a script to a object containing the
  // script's filename so that the module loader can use it to resolve
  // relative imports.

  RootedString path(cx, JS_NewStringCopyZ(cx, filename));
  if (!path) {
    return false;
  }

  RootedObject infoObject(cx, JS_NewPlainObject(cx));
  if (!infoObject) {
    return false;
  }

  RootedValue pathValue(cx, StringValue(path));
  if (!JS_DefineProperty(cx, infoObject, "path", pathValue, 0)) {
    return false;
  }

  JS::SetScriptPrivate(script, ObjectValue(*infoObject));
  return true;
}

enum class CompileUtf8 {
  InflateToUtf16,
  DontInflate,
};

static MOZ_MUST_USE bool RunFile(JSContext* cx, const char* filename,
                                 FILE* file, CompileUtf8 compileMethod,
                                 bool compileOnly) {
  SkipUTF8BOM(file);

  int64_t t1 = PRMJ_Now();
  RootedScript script(cx);

  {
    CompileOptions options(cx);
    options.setIntroductionType("js shell file")
        .setFileAndLine(filename, 1)
        .setIsRunOnce(true)
        .setNoScriptRval(true);

    if (compileMethod == CompileUtf8::DontInflate) {
      fprintf(stderr, "(compiling '%s' as UTF-8 without inflating)\n",
              filename);

      script = JS::CompileUtf8FileDontInflate(cx, options, file);
    } else {
      script = JS::CompileUtf8File(cx, options, file);
    }

    if (!script) {
      return false;
    }
  }

  if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
    return false;
  }

#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;
}

#if defined(JS_BUILD_BINAST)

static MOZ_MUST_USE bool RunBinAST(JSContext* cx, const char* filename,
                                   FILE* file, bool compileOnly) {
  RootedScript script(cx);

  {
    CompileOptions options(cx);
    options.setFileAndLine(filename, 0)
        .setIsRunOnce(true)
        .setNoScriptRval(true);

    script = JS::DecodeBinAST(cx, options, file);
    if (!script) {
      return false;
    }
  }

  if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
    return false;
  }

  if (compileOnly) {
    return true;
  }

  return JS_ExecuteScript(cx, script);
}

#endif  // JS_BUILD_BINAST

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();
  auto src = cx->make_pod_array<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.werrorOption = true;
  options.strictOption = true;

  JS::SourceText<Utf8Unit> srcBuf;
  if (!srcBuf.init(cx, std::move(src), srcLen)) {
    return false;
  }

  RootedValue rv(cx);
  return JS::Evaluate(cx, options, srcBuf, &rv);
}

static bool GetModuleImportHook(JSContext* cx,
                                MutableHandleFunction resultOut) {
  Handle<GlobalObject*> global = cx->global();
  RootedValue hookValue(cx,
                        global->getReservedSlot(GlobalAppSlotModuleLoadHook));
  if (hookValue.isUndefined()) {
    JS_ReportErrorASCII(cx, "Module load hook not set");
    return false;
  }

  if (!hookValue.isObject() || !hookValue.toObject().is<JSFunction>()) {
    JS_ReportErrorASCII(cx, "Module load hook is not a function");
    return false;
  }

  resultOut.set(&hookValue.toObject().as<JSFunction>());
  return true;
}

static MOZ_MUST_USE bool RunModule(JSContext* cx, const char* filename,
                                   FILE* file, bool compileOnly) {
  // Execute a module by calling the module loader's import hook on the
  // resolved filename.

  RootedFunction importFun(cx);
  if (!GetModuleImportHook(cx, &importFun)) {
    return false;
  }

  RootedString path(cx, JS_NewStringCopyZ(cx, filename));
  if (!path) {
    return false;
  }

  path = ResolvePath(cx, path, RootRelative);
  if (!path) {
    return false;
  }

  JS::AutoValueArray<1> args(cx);
  args[0].setString(path);

  RootedValue value(cx);
  return JS_CallFunction(cx, nullptr, importFun, args, &value);
}

static bool EnqueueJob(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!IsFunctionObject(args.get(0))) {
    JS_ReportErrorASCII(cx, "EnqueueJob's first argument must be a function");
    return false;
  }

  args.rval().setUndefined();

  RootedObject job(cx, &args[0].toObject());
  return js::EnqueueJob(cx, job);
}

static bool DrainJobQueue(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (GetShellContext(cx)->quitting) {
    JS_ReportErrorASCII(
        cx, "Mustn't drain the job queue when the shell is quitting");
    return false;
  }

  js::RunJobs(cx);

  if (GetShellContext(cx)->quitting) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

static bool GlobalOfFirstJobInQueue(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  RootedObject job(cx, cx->internalJobQueue->maybeFront());
  if (!job) {
    JS_ReportErrorASCII(cx, "Job queue is empty");
    return false;
  }

  RootedObject global(cx, &job->nonCCWGlobal());
  if (!cx->compartment()->wrap(cx, &global)) {
    return false;
  }

  args.rval().setObject(*global);
  return true;
}

static bool TrackUnhandledRejections(JSContext* cx, JS::HandleObject promise,
                                     JS::PromiseRejectionHandlingState state) {
  ShellContext* sc = GetShellContext(cx);
  if (!sc->trackUnhandledRejections) {
    return true;
  }

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
  if (cx->runningOOMTest) {
    // When OOM happens, we cannot reliably track the set of unhandled
    // promise rejections. Throw error only when simulated OOM is used
    // *and* promises are used in the test.
    JS_ReportErrorASCII(
        cx,
        "Can't track unhandled rejections while running simulated OOM "
        "test. Call ignoreUnhandledRejections before using oomTest etc.");
    return false;
  }
#endif

  if (!sc->unhandledRejectedPromises) {
    sc->unhandledRejectedPromises = SetObject::create(cx);
    if (!sc->unhandledRejectedPromises) {
      return false;
    }
  }

  RootedValue promiseVal(cx, ObjectValue(*promise));

  AutoRealm ar(cx, sc->unhandledRejectedPromises);
  if (!cx->compartment()->wrap(cx, &promiseVal)) {
    return false;
  }

  switch (state) {
    case JS::PromiseRejectionHandlingState::Unhandled:
      if (!SetObject::add(cx, sc->unhandledRejectedPromises, promiseVal)) {
        return false;
      }
      break;
    case JS::PromiseRejectionHandlingState::Handled:
      bool deleted = false;
      if (!SetObject::delete_(cx, sc->unhandledRejectedPromises, promiseVal,
                              &deleted)) {
        return false;
      }
      // We can't MOZ_ASSERT(deleted) here, because it's possible we failed to
      // add the promise in the first place, due to OOM.
      break;
  }

  return true;
}

static void ForwardingPromiseRejectionTrackerCallback(
    JSContext* cx, JS::HandleObject promise,
    JS::PromiseRejectionHandlingState state, void* data) {
  AutoReportException are(cx);

  if (!TrackUnhandledRejections(cx, promise, state)) {
    return;
  }

  RootedValue callback(cx,
                       GetShellContext(cx)->promiseRejectionTrackerCallback);
  if (callback.isNull()) {
    return;
  }

  AutoRealm ar(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);
  (void)Call(cx, callback, UndefinedHandleValue, args, &rval);
}

static bool SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc,
                                               Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!IsFunctionObject(args.get(0))) {
    JS_ReportErrorASCII(
        cx,
        "setPromiseRejectionTrackerCallback expects a function as its sole "
        "argument");
    return false;
  }

  GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];

  args.rval().setUndefined();
  return true;
}

static bool BoundToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  RootedValue function(cx, GetFunctionNativeReserved(&args.callee(), 0));
  RootedObject options(
      cx, &GetFunctionNativeReserved(&args.callee(), 1).toObject());

  RootedSavedFrame stack(cx, nullptr);
  bool isExplicit;

  RootedValue v(cx);

  if (!JS_GetProperty(cx, options, "stack", &v)) {
    return false;
  }
  if (!v.isObject() || !v.toObject().is<SavedFrame>()) {
    JS_ReportErrorASCII(cx,
                        "The 'stack' property must be a SavedFrame object.");
    return false;
  }
  stack = &v.toObject().as<SavedFrame>();

  if (!JS_GetProperty(cx, options, "cause", &v)) {
    return false;
  }
  RootedString causeString(cx, ToString(cx, v));
  if (!causeString) {
    MOZ_ASSERT(cx->isExceptionPending());
    return false;
  }

  UniqueChars cause = JS_EncodeStringToUTF8(cx, causeString);
  if (!cause) {
    MOZ_ASSERT(cx->isExceptionPending());
    return false;
  }

  if (!JS_GetProperty(cx, options, "explicit", &v)) {
    return false;
  }
  isExplicit = v.isUndefined() ? true : ToBoolean(v);

  auto kind =
      (isExplicit ? JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT
                  : JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);

  JS::AutoSetAsyncStackForNewCalls asasfnckthxbye(cx, stack, cause.get(), kind);
  return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
              args.rval());
}

static bool BindToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (args.length() != 2) {
    JS_ReportErrorASCII(cx, "bindToAsyncStack takes exactly two arguments.");
    return false;
  }

  if (!args[0].isObject() || !IsCallable(args[0])) {
    JS_ReportErrorASCII(
        cx, "bindToAsyncStack's first argument should be a function.");
    return false;
  }

  if (!args[1].isObject()) {
    JS_ReportErrorASCII(
        cx, "bindToAsyncStack's second argument should be an object.");
    return false;
  }

  RootedFunction bound(cx, NewFunctionWithReserved(cx, BoundToAsyncStack, 0, 0,
                                                   "bindToAsyncStack thunk"));
  if (!bound) {
    return false;
  }
  SetFunctionNativeReserved(bound, 0, args[0]);
  SetFunctionNativeReserved(bound, 1, args[1]);

  args.rval().setObject(*bound);
  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_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
      JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
      JS_FS_END};

  if (!JS_DefineFunctions(cx, intl, funcs)) {
    return false;
  }

  if (!js::AddMozDateTimeFormatConstructor(cx, intl)) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}
#endif  // ENABLE_INTL_API

static MOZ_MUST_USE bool EvalUtf8AndPrint(JSContext* cx, const char* bytes,
                                          size_t length, int lineno,
                                          bool compileOnly) {
  // Eval.
  JS::CompileOptions options(cx);
  options.setIntroductionType("js shell interactive")
      .setIsRunOnce(true)
      .setFileAndLine("typein", lineno);

  JS::SourceText<Utf8Unit> srcBuf;
  if (!srcBuf.init(cx, bytes, length, JS::SourceOwnership::Borrowed)) {
    return false;
  }

  RootedScript script(cx, JS::CompileDontInflate(cx, options, srcBuf));
  if (!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, JS_ValueToSource(cx, result));
    if (!str) {
      return false;
    }

    UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
    if (!utf8chars) {
      return false;
    }
    fprintf(gOutFile->fp, "%s\n", utf8chars.get());
  }
  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_Utf8BufferIsCompilableUnit(cx, cx->global(), buffer.begin(),
                                            buffer.length()));

    if (hitEOF && buffer.empty()) {
      break;
    }

    {
      // Report exceptions but keep going.
      AutoReportException are(cx);
      mozilla::Unused << EvalUtf8AndPrint(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);
    }

    if (!GetShellContext(cx)->quitting) {
      js::RunJobs(cx);
    }

  } while (!hitEOF && !sc->quitting);

  if (gOutFile->isOpen()) {
    fprintf(gOutFile->fp, "\n");
  }

  return true;
}

enum FileKind {
  FileScript,
  FileScriptUtf8,  // FileScript, but don't inflate to UTF-16 before parsing
  FileModule,
  FileBinAST
};

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) {
  FILE* file;
  if (forceTTY || !filename || strcmp(filename, "-") == 0) {
    file = stdin;
  } else {
    file = fopen(filename, "rb");
    if (!file) {
      ReportCantOpenErrorUnknownEncoding(cx, filename);
      return false;
    }
  }
  AutoCloseFile autoClose(file);

  if (!forceTTY && !isatty(fileno(file))) {
    // It's not interactive - just execute it.
    switch (kind) {
      case FileScript:
        if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16,
                     compileOnly)) {
          return false;
        }
        break;
      case FileScriptUtf8:
        if (!RunFile(cx, filename, file, CompileUtf8::DontInflate,
                     compileOnly)) {
          return false;
        }
        break;
      case FileModule:
        if (!RunModule(cx, filename, file, compileOnly)) {
          return false;
        }
        break;
#if defined(JS_BUILD_BINAST)
      case FileBinAST:
        if (!RunBinAST(cx, filename, file, compileOnly)) {
          return false;
        }
        break;
#endif  // JS_BUILD_BINAST
      default:
        MOZ_CRASH("Impossible FileKind!");
    }
  } 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;
}

#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;
  }
  UniqueChars filename = JS_EncodeStringToLatin1(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.get(), "rb");
  if (!file) {
    ReportCantOpenErrorUnknownEncoding(cx, filename.get());
    return false;
  }
  AutoCloseFile autoClose(file);

  struct stat st;
  if (fstat(fileno(file), &st) < 0) {
    JS_ReportErrorASCII(cx, "Unable to stat file");
    return false;
  }

  if ((st.st_mode & S_IFMT) != S_IFREG) {
    JS_ReportErrorASCII(cx, "Path is not a regular file");
    return false;
  }

  if (!sizeGiven) {
    if (off_t(offset) >= st.st_size) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_OFFSET_LARGER_THAN_FILESIZE);
      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 IgnoreUnhandledRejections(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  ShellContext* sc = GetShellContext(cx);
  sc->trackUnhandledRejections = false;

  args.rval().setUndefined();
  return true;
}

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;
    }

    RootedLinearString opt(cx, str->ensureLinear(cx));
    if (!opt) {
      return false;
    }

    if (StringEqualsAscii(opt, "strict")) {
      JS::ContextOptionsRef(cx).toggleExtraWarnings();
    } else if (StringEqualsAscii(opt, "werror")) {
      // Disallow toggling werror when there are off-thread jobs, to avoid
      // confusing CompileError::throwError.
      ShellContext* sc = GetShellContext(cx);
      if (!sc->offThreadJobs.empty()) {
        JS_ReportErrorASCII(
            cx, "can't toggle werror when there are off-thread jobs");
        return false;
      }
      JS::ContextOptionsRef(cx).toggleWerror();
    } else if (StringEqualsAscii(opt, "throw_on_asmjs_validation_failure")) {
      JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
    } else if (StringEqualsAscii(opt, "strict_mode")) {
      JS::ContextOptionsRef(cx).toggleStrictMode();
    } else {
      UniqueChars optChars = JS_EncodeStringToUTF8(cx, opt);
      if (!optChars) {
        return false;
      }

      JS_ReportErrorUTF8(cx,
                         "unknown option name '%s'."
                         " The valid names are strict,"
                         " werror, and strict_mode.",
                         optChars.get());
      return false;
    }
  }

  UniqueChars names = DuplicateString("");
  bool found = false;
  if (names && oldContextOptions.extraWarnings()) {
    names =
        JS_sprintf_append(std::move(names), "%s%s", found ? "," : "", "strict");
    found = true;
  }
  if (names && oldContextOptions.werror()) {
    names =
        JS_sprintf_append(std::move(names), "%s%s", found ? "," : "", "werror");
    found = true;
  }
  if (names && oldContextOptions.throwOnAsmJSValidationFailure()) {
    names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
                              "throw_on_asmjs_validation_failure");
    found = true;
  }
  if (names && oldContextOptions.strictMode()) {
    names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
                              "strict_mode");
    found = true;
  }
  if (!names) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  JSString* str = JS_NewStringCopyZ(cx, names.get());
  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;
    }

    UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
    if (!filename) {
      return false;
    }

    errno = 0;

    CompileOptions opts(cx);
    opts.setIntroductionType("js shell load")
        .setIsRunOnce(true)
        .setNoScriptRval(true);

    RootedValue unused(cx);
    if (!(compileOnly
              ? JS::CompileUtf8Path(cx, opts, filename.get()) != nullptr
              : JS::EvaluateUtf8Path(cx, opts, filename.get(), &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, UniqueChars& 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;
    }
    fileNameBytes = JS_EncodeStringToLatin1(cx, s);
    if (!fileNameBytes) {
      return false;
    }
    options.setFile(fileNameBytes.get());
  }

  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() {
  JSContext* cx = TlsContext.get();
  if (!cx || cx->helperThread()) {
    return;
  }

  MOZ_ASSERT(!JS::RuntimeHeapIsBusy());

  JS::PrepareForFullGC(cx);
  cx->runtime()->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(JSContext* cx, HandleObject cache) {
  MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
  Value v = JS_GetReservedSlot(cache, CacheEntry_SOURCE);
  if (!v.isString()) {
    JS_ReportErrorASCII(
        cx, "CacheEntry_getSource: Unexpected type of source reserved slot.");
    return nullptr;
  }

  return v.toString();
}

static uint8_t* CacheEntry_getBytecode(JSContext* cx, HandleObject cache,
                                       uint32_t* length) {
  MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
  Value v = JS_GetReservedSlot(cache, CacheEntry_BYTECODE);
  if (!v.isObject() || !v.toObject().is<ArrayBufferObject>()) {
    JS_ReportErrorASCII(
        cx,
        "CacheEntry_getBytecode: Unexpected type of bytecode reserved slot.");
    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));

  using BufferContents = ArrayBufferObject::BufferContents;

  BufferContents contents = BufferContents::createMalloced(buffer);
  Rooted<ArrayBufferObject*> arrayBuffer(
      cx, ArrayBufferObject::createForContents(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_BadDecode:
      MOZ_ASSERT(!cx->isExceptionPending());
      JS_ReportErrorASCII(cx, "XDR data corruption");
      return false;
    case JS::TranscodeResult_Failure_WrongCompileOption:
      MOZ_ASSERT(!cx->isExceptionPending());
      JS_ReportErrorASCII(
          cx, "Compile options differs from Compile options of the encoding");
      return false;
    case JS::TranscodeResult_Failure_NotInterpretedFun:
      MOZ_ASSERT(!cx->isExceptionPending());
      JS_ReportErrorASCII(cx,
                          "Only interepreted functions are supported by XDR");
      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(cx, cacheEntry);
    if (!code) {
      return false;
    }
  }

  if (!code || (args.length() == 2 && args[1].isPrimitive())) {
    JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
                              JSSMSG_INVALID_ARGS, "evaluate");
    return false;
  }

  CompileOptions options(cx);
  UniqueChars fileNameBytes;
  RootedString displayURL(cx);
  RootedString sourceMapURL(cx);
  RootedObject global(cx, nullptr);
  bool catchTermination = false;
  bool loadBytecode = false;
  bool saveBytecode = false;
  bool saveIncrementalBytecode = false;
  bool assertEqBytecode = false;
  JS::RootedObjectVector envChain(cx);
  RootedObject callerGlobal(cx, cx->global());

  options.setIntroductionType("js shell evaluate")
      .setFileAndLine("@evaluate", 1);

  global = JS::CurrentGlobalOrNull(cx);
  MOZ_ASSERT(global);

  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::CheckedUnwrapDynamic(&v.toObject(), cx,
                                          /* stopAtWindowProxy = */ false);
        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, "saveIncrementalBytecode", &v)) {
      return false;
    }
    if (!v.isUndefined()) {
      saveIncrementalBytecode = ToBoolean(v);
    }

    if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) {
      return false;
    }
    if (!v.isUndefined()) {
      assertEqBytecode = ToBoolean(v);
    }

    if (!JS_GetProperty(cx, opts, "envChainObject", &v)) {
      return false;
    }
    if (!v.isUndefined()) {
      if (loadBytecode) {
        JS_ReportErrorASCII(cx,
                            "Can't use both loadBytecode and envChainObject");
        return false;
      }

      if (!v.isObject()) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
            "\"envChainObject\" passed to evaluate()", "not an object");
        return false;
      } else if (v.toObject().is<GlobalObject>()) {
        JS_ReportErrorASCII(
            cx,
            "\"envChainObject\" passed to evaluate() should not be a global");
        return false;
      } else if (!envChain.append(&v.toObject())) {
        JS_ReportOutOfMemory(cx);
        return false;
      }
    }

    // We cannot load or save the bytecode if we have no object where the
    // bytecode cache is stored.
    if (loadBytecode || saveBytecode || saveIncrementalBytecode) {
      if (!cacheEntry) {
        JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
                                  JSSMSG_INVALID_ARGS, "evaluate");
        return false;
      }
      if (saveIncrementalBytecode && saveBytecode) {
        JS_ReportErrorASCII(
            cx,
            "saveIncrementalBytecode and saveBytecode cannot be used"
            " at the same time.");
        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(cx, cacheEntry, &loadLength);
    if (!loadData) {
      return false;
    }
    if (!loadBuffer.append(loadData, loadLength)) {
      JS_ReportOutOfMemory(cx);
      return false;
    }
  }

  {
    JSAutoRealm ar(cx, global);
    RootedScript script(cx);

    {
      if (saveBytecode) {
        if (!JS::RealmCreationOptionsRef(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::RealmBehaviorsRef(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();
        JS::SourceText<char16_t> srcBuf;
        if (!srcBuf.init(cx, chars.begin().get(), chars.length(),
                         JS::SourceOwnership::Borrowed)) {
          return false;
        }

        if (envChain.length() == 0) {
          script = JS::Compile(cx, options, srcBuf);
        } else {
          script = JS::CompileForNonSyntacticScope(cx, options, srcBuf);
        }
      }

      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 we want to save the bytecode incrementally, then we should
    // register ahead the fact that every JSFunction which is being
    // delazified should be encoded at the end of the delazification.
    if (saveIncrementalBytecode) {
      if (!StartIncrementalEncoding(cx, script)) {
        return false;
      }
    }

    if (!JS_ExecuteScript(cx, envChain, script, args.rval())) {
      if (catchTermination && !JS_IsExceptionPending(cx)) {
        JSAutoRealm ar1(cx, callerGlobal);
        JSString* str = JS_NewStringCopyZ(cx, "terminated");
        if (!str) {
          return false;
        }
        args.rval().setString(str);
        return true;
      }
      return false;
    }

    // Encode the bytecode after the execution of the script.
    if (saveBytecode) {
      JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script);
      if (!ConvertTranscodeResultToJSException(cx, rv)) {
        return false;
      }
    }

    // Serialize the encoded bytecode, recorded before the execution, into a
    // buffer which can be deserialized linearly.
    if (saveIncrementalBytecode) {
      if (!FinishIncrementalEncoding(cx, script, saveBuffer)) {
        return false;
      }
    }
  }

  if (saveBytecode || saveIncrementalBytecode) {
    // 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, "%zu", loadBuffer.length());
        char saveLengthStr[16];
        SprintfLiteral(saveLengthStr, "%zu", saveBuffer.length());

        JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
                                  JSSMSG_CACHE_EQ_SIZE_FAILED, loadLengthStr,
                                  saveLengthStr);
        return false;
      }

      if (!ArrayEqual(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) {
  UniqueChars pathname = JS_EncodeStringToLatin1(cx, pathnameStr);
  if (!pathname) {
    return nullptr;
  }

  FILE* file;

  file = fopen(pathname.get(), "rb");
  if (!file) {
    ReportCantOpenErrorUnknownEncoding(cx, pathname.get());
    return nullptr;
  }

  AutoCloseFile autoClose(file);

  struct stat st;
  if (fstat(fileno(file), &st) != 0) {
    JS_ReportErrorUTF8(cx, "can't stat %s", pathname.get());
    return nullptr;
  }

  if ((st.st_mode & S_IFMT) != S_IFREG) {
    JS_ReportErrorUTF8(cx, "can't read non-regular file %s", pathname.get());
    return nullptr;
  }

  if (fseek(file, 0, SEEK_END) != 0) {
    pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
    if (!pathname) {
      return nullptr;
    }
    JS_ReportErrorUTF8(cx, "can't seek end of %s", pathname.get());
    return nullptr;
  }

  long endPos = ftell(file);
  if (endPos < 0) {
    JS_ReportErrorUTF8(cx, "can't read length of %s", pathname.get());
    return nullptr;
  }

  size_t len = endPos;
  if (fseek(file, 0, SEEK_SET) != 0) {
    pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
    if (!pathname) {
      return nullptr;
    }
    JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.get());
    return nullptr;
  }

  UniqueChars buf(js_pod_malloc<char>(len + 1));
  if (!buf) {
    JS_ReportErrorUTF8(cx, "out of memory reading %s", pathname.get());
    return nullptr;
  }

  size_t cc = fread(buf.get(), 1, len, file);
  if (cc != len) {
    if (ptrdiff_t(cc) < 0) {
      ReportCantOpenErrorUnknownEncoding(cx, pathname.get());
    } else {
      pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
      if (!pathname) {
        return nullptr;
      }
      JS_ReportErrorUTF8(cx, "can't read %s: short read", pathname.get());
    }
    return nullptr;
  }

  UniqueTwoByteChars ucbuf(
      JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf.get(), len),
                                           &len, js::MallocArena)
          .get());
  if (!ucbuf) {
    pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
    if (!pathname) {
      return nullptr;
    }
    JS_ReportErrorUTF8(cx, "Invalid UTF-8 in file '%s'", pathname.get());
    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;
  }

  JS::SourceText<char16_t> srcBuf;
  if (!srcBuf.init(cx, chars.twoByteRange().begin().get(), str->length(),
                   JS::SourceOwnership::Borrowed)) {
    return false;
  }

  RootedScript script(cx);
  int64_t startClock = PRMJ_Now();
  {
    /* FIXME: This should use UTF-8 (bug 987069). */
    UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
    if (!filename) {
      return false;
    }

    JS::CompileOptions options(cx);
    options.setIntroductionType("js shell run")
        .setFileAndLine(filename.get(), 1)
        .setIsRunOnce(true)
        .setNoScriptRval(true);

    script = JS::Compile(cx, options, srcBuf);
    if (!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_NewStringCopyUTF8N(cx, JS::UTF8Chars(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) {
    sc->readLineBuf = nullptr;
    sc->readLineBufPos = 0;

    RootedString str(cx, JS::ToString(cx, args[0]));
    if (!str) {
      return false;
    }
    sc->readLineBuf = JS_EncodeStringToUTF8(cx, str);
    if (!sc->readLineBuf) {
      return false;
    }

    args.rval().setUndefined();
    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;
    }
    UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
    if (!bytes) {
      return false;
    }
    fputs(bytes.get(), gOutFile->fp);
    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;
    }
    UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
    if (!bytes) {
      return false;
    }
    fprintf(file->fp, "%s%s", i ? " " : "", bytes.get());
  }

  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;
  }

  js::StopDrainingJobQueue(cx);
  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, HandleValue vp, UniqueChars* bytes) {
  RootedString str(cx, JS_ValueToSource(cx, vp));
  if (str) {
    *bytes = JS_EncodeStringToUTF8(cx, str);
    if (*bytes) {
      return bytes->get();
    }
  }
  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) {
    UniqueChars bytes0, bytes1;
    const char* actual = ToSource(cx, args[0], &bytes0);
    const char* expected = ToSource(cx, args[1], &bytes1);
    if (args.length() == 2) {
      JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
                               JSSMSG_ASSERT_EQ_FAILED, actual, expected);
    } else {
      RootedString message(cx, args[2].toString());
      UniqueChars bytes2 = JS_EncodeStringToUTF8(cx, message);
      if (!bytes2) {
        return false;
      }
      JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
                               JSSMSG_ASSERT_EQ_FAILED_MSG, actual, expected,
                               bytes2.get());
    }
    return false;
  }
  args.rval().setUndefined();
  return true;
}

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 = TestingFunctionArgumentToScript(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 = TestingFunctionArgumentToScript(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;
}

#if defined(DEBUG) || defined(JS_JITSPEW)

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") ||
      !sp->jsprintf("%4s %4s %5s %6s %-8s %s\n", "ofs", "line", "pc", "delta",
                    "desc", "args") ||
      !sp->put("---- ---- ----- ------ -------- ------\n")) {
    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 = 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_IF_ELSE:
      case SRC_COND:
      case SRC_CONTINUE:
      case SRC_BREAK:
      case SRC_BREAK2LABEL:
      case SRC_SWITCHBREAK:
      case SRC_ASSIGNOP:
      case SRC_BREAKPOINT:
      case SRC_STEP_SEP:
      case SRC_XDELTA:
        break;

      case SRC_COLSPAN:
        colspan =
            SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, SrcNote::ColSpan::Span));
        if (!sp->jsprintf("%d", colspan)) {
          return false;
        }
        break;

      case SRC_SETLINE:
        lineno = GetSrcNoteOffset(sn, SrcNote::SetLine::Line);
        if (!sp->jsprintf(" lineno %u", lineno)) {
          return false;
        }
        break;

      case SRC_NEWLINE:
        ++lineno;
        break;

      case SRC_FOR:
        if (!sp->jsprintf(
                " cond %u update %u backjump %u",
                unsigned(GetSrcNoteOffset(sn, SrcNote::For::CondOffset)),
                unsigned(GetSrcNoteOffset(sn, SrcNote::For::UpdateOffset)),
                unsigned(GetSrcNoteOffset(sn, SrcNote::For::BackJumpOffset)))) {
          return false;
        }
        break;

      case SRC_WHILE:
      case SRC_FOR_IN:
      case SRC_FOR_OF:
        static_assert(
            unsigned(SrcNote::While::BackJumpOffset) ==
                unsigned(SrcNote::ForIn::BackJumpOffset),
            "SrcNote::{While,ForIn,ForOf}::BackJumpOffset should be same");
        static_assert(
            unsigned(SrcNote::While::BackJumpOffset) ==
                unsigned(SrcNote::ForOf::BackJumpOffset),
            "SrcNote::{While,ForIn,ForOf}::BackJumpOffset should be same");
        if (!sp->jsprintf(" backjump %u",
                          unsigned(GetSrcNoteOffset(
                              sn, SrcNote::While::BackJumpOffset)))) {
          return false;
        }
        break;

      case SRC_DO_WHILE:
        if (!sp->jsprintf(
                " cond %u backjump %u",
                unsigned(GetSrcNoteOffset(sn, SrcNote::DoWhile::CondOffset)),
                unsigned(
                    GetSrcNoteOffset(sn, SrcNote::DoWhile::BackJumpOffset)))) {
          return false;
        }
        break;

      case SRC_NEXTCASE:
        if (!sp->jsprintf(" next case offset %u",
                          unsigned(GetSrcNoteOffset(
                              sn, SrcNote::NextCase::NextCaseOffset)))) {
          return false;
        }
        break;

      case SRC_TABLESWITCH: {
        mozilla::DebugOnly<JSOp> op = JSOp(script->code()[offset]);
        MOZ_ASSERT(op == JSOP_TABLESWITCH);
        if (!sp->jsprintf(" end offset %u",
                          unsigned(GetSrcNoteOffset(
                              sn, SrcNote::TableSwitch::EndOffset)))) {
          return false;
        }
        UpdateSwitchTableBounds(cx, script, offset, &switchTableStart,
                                &switchTableEnd);
        break;
      }
      case SRC_CONDSWITCH: {
        mozilla::DebugOnly<JSOp> op = JSOp(script->code()[offset]);
        MOZ_ASSERT(op == JSOP_CONDSWITCH);
        if (!sp->jsprintf(" end offset %u",
                          unsigned(GetSrcNoteOffset(
                              sn, SrcNote::CondSwitch::EndOffset)))) {
          return false;
        }
        if (unsigned caseOff = unsigned(
                GetSrcNoteOffset(sn, SrcNote::CondSwitch::FirstCaseOffset))) {
          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, SrcNote::Try::EndOfTryJumpOffset)))) {
          return false;
        }
        break;

      case SRC_CLASS_SPAN: {
        unsigned startOffset = GetSrcNoteOffset(sn, 0);
        unsigned endOffset = GetSrcNoteOffset(sn, 1);
        if (!sp->jsprintf(" %u %u", startOffset, endOffset)) {
          return false;
        }
        break;
      }

      default:
        MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
    }
    if (!sp->put("\n")) {
      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, TestingFunctionArgumentToScript(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;
}

static const char* TryNoteName(JSTryNoteKind kind) {
  switch (kind) {
    case JSTRY_CATCH:
      return "catch";
    case JSTRY_FINALLY:
      return "finally";
    case JSTRY_FOR_IN:
      return "for-in";
    case JSTRY_FOR_OF:
      return "for-of";
    case JSTRY_LOOP:
      return "loop";
    case JSTRY_FOR_OF_ITERCLOSE:
      return "for-of-iterclose";
    case JSTRY_DESTRUCTURING:
      return "destructuring";
  }

  MOZ_CRASH("Bad JSTryNoteKind");
}

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")) {
    return false;
  }

  for (const JSTryNote& tn : script->trynotes()) {
    if (!sp->jsprintf(" %-16s %6u %8u %8u\n",
                      TryNoteName(static_cast<JSTryNoteKind>(tn.kind)),
                      tn.stackDepth, tn.start, tn.start + tn.length)) {
      return false;
    }
  }
  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")) {
    return false;
  }

  for (const ScopeNote& note : script->scopeNotes()) {
    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:")) {
      return false;
    }
    if (fun->isLambda()) {
      if (!sp->put(" LAMBDA")) {
        return false;
      }
    }
    if (fun->needsCallObject()) {
      if (!sp->put(" NEEDS_CALLOBJECT")) {
        return false;
      }
    }
    if (fun->needsExtraBodyVarEnvironment()) {
      if (!sp->put(" NEEDS_EXTRABODYVARENV")) {
        return false;
      }
    }
    if (fun->needsNamedLambdaEnvironment()) {
      if (!sp->put(" NEEDS_NAMEDLAMBDAENV")) {
        return false;
      }
    }
    if (fun->isConstructor()) {
      if (!sp->put(" CONSTRUCTOR")) {
        return false;
      }
    }
    if (fun->isSelfHostedBuiltin()) {
      if (!sp->put(" SELF_HOSTED")) {
        return false;
      }
    }
    if (fun->isArrow()) {
      if (!sp->put(" ARROW")) {
        return false;
      }
    }
    if (!sp->put("\n")) {
      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()) {
    for (JSObject* obj : script->objects()) {
      if (obj->is<JSFunction>()) {
        if (!sp->put("\n")) {
          return false;
        }

        RootedFunction fun(cx, &obj->as<JSFunction>());
        if (fun->isInterpreted()) {
          RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
          if (script) {
            if (!DisassembleScript(cx, script, fun, lines, recursive,
                                   sourceNotes, sp)) {
              return false;
            }
          }
        } else {
          if (!sp->put("[native code]\n")) {
            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) {
      JSAutoRealm ar(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>().maybeScript();
      } else {
        script = TestingFunctionArgumentToScript(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;
  }

  JS::ConstUTF8CharsZ utf8chars(sprinter.string(), strlen(sprinter.string()));
  JSString* str = JS_NewStringCopyUTF8Z(cx, utf8chars);
  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;
  }
  UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
  if (!filename) {
    return false;
  }
  RootedScript script(cx);

  {
    CompileOptions options(cx);
    options.setIntroductionType("js shell disFile")
        .setFileAndLine(filename.get(), 1)
        .setIsRunOnce(true)
        .setNoScriptRval(true);

    script = JS::CompileUtf8Path(cx, options, filename.get());
    if (!script) {
      return false;
    }
  }

  Sprinter sprinter(cx);
  if (!sprinter.init()) {
    return false;
  }
  if (!DisassembleScript(cx, script, nullptr, p.lines, p.recursive,
                         p.sourceNotes, &sprinter)) {
    return false;
  }

  fprintf(gOutFile->fp, "%s\n", sprinter.string());

  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 = TestingFunctionArgumentToScript(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(), "rb");
    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 /* defined(DEBUG) || defined(JS_JITSPEW) */

/* Pretend we can always preserve wrappers for dummy DOM objects. */
static bool DummyPreserveWrapperCallback(JSContext* cx, HandleObject 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);

  if (args.length() == 0) {
    JS_ReportErrorASCII(cx, "Invalid arguments to clone");
    return false;
  }

  RootedObject funobj(cx);
  {
    Maybe<JSAutoRealm> ar;
    RootedObject obj(cx, args[0].isPrimitive() ? nullptr : &args[0].toObject());

    if (obj && obj->is<CrossCompartmentWrapperObject>()) {
      obj = UncheckedUnwrap(obj);
      ar.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);
    }
  }

  RootedObject env(cx);
  if (args.length() > 1) {
    if (!JS_ValueToObject(cx, args[1], &env)) {
      return false;
    }
  } else {
    env = JS::CurrentGlobalOrNull(cx);
    MOZ_ASSERT(env);
  }

  // Should it worry us that we might be getting with wrappers
  // around with wrappers here?
  JS::RootedObjectVector envChain(cx);
  if (env && !env->is<GlobalObject>() && !envChain.append(env)) {
    return false;
  }
  JSObject* clone = JS::CloneFunctionObject(cx, funobj, envChain);
  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;
  }
  UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, message);
  if (!utf8chars) {
    return false;
  }
  if (args.get(1).isObject()) {
    RootedValue v(cx);
    RootedObject opts(cx, &args[1].toObject());
    if (!JS_GetProperty(cx, opts, "suppress_minidump", &v)) {
      return false;
    }
    if (v.isBoolean() && v.toBoolean()) {
      js::NoteIntentionalCrash();
    }
  }
#ifndef DEBUG
  MOZ_ReportCrash(utf8chars.get(), __FILE__, __LINE__);
#endif
  MOZ_CRASH_UNSAFE(utf8chars.get());
}

static bool GetSLX(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  RootedScript script(cx);

  script = TestingFunctionArgumentToScript(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, JS::HandleObject obj,
                              JS::MutableHandleIdVector properties,
                              bool enumerableOnly) {
  RootedValue v(cx);

  if (!JS_GetProperty(cx, obj, "lazy", &v)) {
    return false;
  }

  if (!ToBoolean(v)) {
    return true;
  }

  return JS_NewEnumerateStandardClasses(cx, obj, properties, enumerableOnly);
}

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,
                                            sandbox_enumerate,
                                            sandbox_resolve,
                                            nullptr,
                                            nullptr,
                                            nullptr,
                                            nullptr,
                                            nullptr,
                                            JS_GlobalObjectTraceHook};

static const JSClass sandbox_class = {"sandbox", JSCLASS_GLOBAL_FLAGS,
                                      &sandbox_classOps};

static void SetStandardRealmOptions(JS::RealmOptions& options) {
  options.creationOptions()
      .setSharedMemoryAndAtomicsEnabled(enableSharedMemory)
      .setBigIntEnabled(enableBigInt)
      .setStreamsEnabled(enableStreams)
      .setFieldsEnabled(enableFields)
      .setAwaitFixEnabled(enableAwaitFix);
}

static MOZ_MUST_USE bool CheckRealmOptions(JSContext* cx,
                                           JS::RealmOptions& options,
                                           JSPrincipals* principals) {
  JS::RealmCreationOptions& creationOptions = options.creationOptions();
  if (creationOptions.compartmentSpecifier() !=
      JS::CompartmentSpecifier::ExistingCompartment) {
    return true;
  }

  JS::Compartment* comp = creationOptions.compartment();

  // All realms in a compartment must be either system or non-system.
  bool isSystem =
      principals && principals == cx->runtime()->trustedPrincipals();
  if (isSystem != IsSystemCompartment(comp)) {
    JS_ReportErrorASCII(cx,
                        "Cannot create system and non-system realms in the "
                        "same compartment");
    return false;
  }

  // Debugger visibility is per-compartment, not per-realm, so make sure the
  // requested visibility matches the existing compartment's.
  if (creationOptions.invisibleToDebugger() != comp->invisibleToDebugger()) {
    JS_ReportErrorASCII(cx,
                        "All the realms in a compartment must have "
                        "the same debugger visibility");
    return false;
  }

  return true;
}

static JSObject* NewSandbox(JSContext* cx, bool lazy) {
  JS::RealmOptions options;
  SetStandardRealmOptions(options);

  if (defaultToSameCompartment) {
    options.creationOptions().setExistingCompartment(cx->global());
  } else {
    options.creationOptions().setNewCompartmentAndZone();
  }

  JSPrincipals* principals = nullptr;
  if (!CheckRealmOptions(cx, options, principals)) {
    return nullptr;
  }

  RootedObject obj(cx,
                   JS_NewGlobalObject(cx, &sandbox_class, principals,
                                      JS::DontFireOnNewGlobalHook, options));
  if (!obj) {
    return nullptr;
  }

  {
    JSAutoRealm ar(cx, obj);
    if (!lazy && !JS::InitRealmStandardClasses(cx)) {
      return nullptr;
    }

    RootedValue value(cx, BooleanValue(lazy));
    if (!JS_DefineProperty(cx, obj, "lazy", value,
                           JSPROP_PERMANENT | JSPROP_READONLY)) {
      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);
  {
    sobj = UncheckedUnwrap(sobj, true);

    JSAutoRealm ar(cx, sobj);

    sobj = ToWindowIfWindowProxy(sobj);

    if (!JS_IsGlobalObject(sobj)) {
      JS_ReportErrorASCII(cx, "Invalid scope argument to evalcx");
      return false;
    }

    JS::CompileOptions opts(cx);
    opts.setFileAndLine(filename.get(), lineno);

    JS::SourceText<char16_t> srcBuf;
    if (!srcBuf.init(cx, src, srclen, JS::SourceOwnership::Borrowed) ||
        !JS::Evaluate(cx, opts, srcBuf, args.rval())) {
      return false;
    }
  }

  if (!cx->compartment()->wrap(cx, args.rval())) {
    return false;
  }

  return true;
}

static bool EnsureGeckoProfilingStackInstalled(JSContext* cx,
                                               ShellContext* sc) {
  if (cx->geckoProfiler().infraInstalled()) {
    MOZ_ASSERT(sc->geckoProfilingStack);
    return true;
  }

  MOZ_ASSERT(!sc->geckoProfilingStack);
  sc->geckoProfilingStack = MakeUnique<ProfilingStack>();
  if (!sc->geckoProfilingStack) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  SetContextProfilingStack(cx, sc->geckoProfilingStack.get());
  return true;
}

struct WorkerInput {
  JSRuntime* parentRuntime;
  UniqueTwoByteChars chars;
  size_t length;

  WorkerInput(JSRuntime* parentRuntime, UniqueTwoByteChars chars, size_t length)
      : parentRuntime(parentRuntime), chars(std::move(chars)), length(length) {}

  ~WorkerInput() = default;
};

static void DestroyShellCompartmentPrivate(JSFreeOp* fop,
                                           JS::Compartment* compartment) {
  auto priv = static_cast<ShellCompartmentPrivate*>(
      JS_GetCompartmentPrivate(compartment));
  js_delete(priv);
}

static void SetWorkerContextOptions(JSContext* cx);
static bool ShellBuildId(JS::BuildIdCharVector* buildId);

static void WorkerMain(WorkerInput* input) {
  MOZ_ASSERT(input->parentRuntime);

  JSContext* cx = JS_NewContext(8L * 1024L * 1024L, 2L * 1024L * 1024L,
                                input->parentRuntime);
  if (!cx) {
    return;
  }

  ShellContext* sc = js_new<ShellContext>(cx);
  if (!sc) {
    return;
  }

  auto guard = mozilla::MakeScopeExit([&] {
    CancelOffThreadJobsForContext(cx);
    sc->markObservers.reset();
    JS_SetContextPrivate(cx, nullptr);
    js_delete(sc);
    JS_DestroyContext(cx);
    js_delete(input);
  });

  sc->isWorker = true;
  JS_SetContextPrivate(cx, sc);
  JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, nullptr);
  SetWorkerContextOptions(cx);

  JS_SetFutexCanWait(cx);
  JS::SetWarningReporter(cx, WarningReporter);
  js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
  JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
  JS_SetDestroyCompartmentCallback(cx, DestroyShellCompartmentPrivate);

  js::SetWindowProxyClass(cx, &ShellWindowProxyClass);

  js::UseInternalJobQueues(cx);

  if (!JS::InitSelfHostedCode(cx)) {
    return;
  }

  EnvironmentPreparer environmentPreparer(cx);

  do {
    JS::RealmOptions realmOptions;
    SetStandardRealmOptions(realmOptions);

    RootedObject global(cx, NewGlobalObject(cx, realmOptions, nullptr,
                                            ShellGlobalKind::WindowProxy));
    if (!global) {
      break;
    }

    JSAutoRealm ar(cx, global);

    JS::CompileOptions options(cx);
    options.setFileAndLine("<string>", 1).setIsRunOnce(true);

    AutoReportException are(cx);
    JS::SourceText<char16_t> srcBuf;
    if (!srcBuf.init(cx, input->chars.get(), input->length,
                     JS::SourceOwnership::Borrowed)) {
      break;
    }

    RootedScript script(cx, JS::Compile(cx, options, srcBuf));
    if (!script) {
      break;
    }
    RootedValue result(cx);
    JS_ExecuteScript(cx, script, &result);
  } while (0);

  KillWatchdog(cx);
  JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
}

// 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 threads with --no-threads");
    return false;
  }

  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.get(0).isString()) {
    JS_ReportErrorASCII(cx, "Invalid arguments");
    return false;
  }

#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
  if (cx->runningOOMTest) {
    JS_ReportErrorASCII(
        cx, "Can't create threads 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();

  UniqueTwoByteChars chars(js_pod_malloc<char16_t>(str->length()));
  if (!chars) {
    ReportOutOfMemory(cx);
    return false;
  }

  CopyChars(chars.get(), *str);

  WorkerInput* input = js_new<WorkerInput>(JS_GetParentRuntime(cx),
                                           std::move(chars), str->length());
  if (!input) {
    ReportOutOfMemory(cx);
    return false;
  }

  Thread* thread;
  {
    AutoEnterOOMUnsafeRegion oomUnsafe;
    thread = js_new<Thread>(
        Thread::Options().setStackSize(gMaxStackSize + 256 * 1024));
    if (!thread || !thread->init(WorkerMain, input)) {
      oomUnsafe.crash("EvalInWorker");
    }
  }

  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->shape()) >> 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;
  }
  RootedObject obj(cx, &args[0].toObject());
  ObjectGroup* group = JSObject::getGroup(cx, obj);
  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()));

  args.rval().setBoolean(obj1->shape() == obj2->shape());
  return true;
}

static bool Sleep_fn(JSContext* cx, unsigned argc, Value* vp) {
  ShellContext* sc = GetShellContext(cx);
  CallArgs args = CallArgsFromVp(argc, vp);

  TimeDuration duration = TimeDuration::FromSeconds(0.0);
  if (args.length() > 0) {
    double t_secs;
    if (!ToNumber(cx, args[0], &t_secs)) {
      return false;
    }
    if (mozilla::IsNaN(t_secs)) {
      JS_ReportErrorASCII(cx, "sleep interval is not a number");
      return false;
    }

    duration = TimeDuration::FromSeconds(Max(0.0, t_secs));
    const TimeDuration MAX_TIMEOUT_INTERVAL =
        TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
    if (duration > MAX_TIMEOUT_INTERVAL) {
      JS_ReportErrorASCII(cx, "Excessive sleep interval");
      return false;
    }
  }
  {
    LockGuard<Mutex> guard(sc->watchdogLock);
    TimeStamp toWakeup = TimeStamp::Now() + duration;
    for (;;) {
      sc->sleepWakeup.wait_for(guard, duration);
      if (sc->serviceInterrupt) {
        break;
      }
      auto now = TimeStamp::Now();
      if (now >= toWakeup) {
        break;
      }
      duration = toWakeup - now;
    }
  }
  args.rval().setUndefined();
  return !sc->serviceInterrupt;
}

static void KillWatchdog(JSContext* cx) {
  ShellContext* sc = GetShellContext(cx);
  Maybe<Thread> thread;

  {
    LockGuard<Mutex> guard(sc->watchdogLock);
    Swap(sc->watchdogThread, thread);
    if (thread) {
      // The watchdog thread becoming Nothing is its signal to exit.
      sc->watchdogWakeup.notify_one();
    }
  }
  if (thread) {
    thread->join();
  }

  MOZ_ASSERT(!sc->watchdogThread);
}

static void WatchdogMain(JSContext* cx) {
  ThisThread::SetName("JS Watchdog");

  ShellContext* sc = GetShellContext(cx);

  LockGuard<Mutex> guard(sc->watchdogLock);
  while (sc->watchdogThread) {
    auto now = TimeStamp::Now();
    if (sc->watchdogTimeout && now >= sc->watchdogTimeout.value()) {
      /*
       * The timeout has just expired. Request an interrupt callback
       * outside the lock.
       */
      sc->watchdogTimeout = Nothing();
      {
        UnlockGuard<Mutex> unlock(guard);
        CancelExecution(cx);
      }

      /* Wake up any threads doing sleep. */
      sc->sleepWakeup.notify_all();
    } else {
      if (sc->watchdogTimeout) {
        /*
         * Time hasn't expired yet. Simulate an interrupt callback
         * which doesn't abort execution.
         */
        JS_RequestInterruptCallback(cx);
      }

      TimeDuration sleepDuration = sc->watchdogTimeout
                                       ? TimeDuration::FromSeconds(0.1)
                                       : TimeDuration::Forever();
      sc->watchdogWakeup.wait_for(guard, sleepDuration);
    }
  }
}

static bool ScheduleWatchdog(JSContext* cx, double t) {
  ShellContext* sc = GetShellContext(cx);

  if (t <= 0) {
    LockGuard<Mutex> guard(sc->watchdogLock);
    sc->watchdogTimeout = Nothing();
    return true;
  }

  auto interval = TimeDuration::FromSeconds(t);
  auto timeout = TimeStamp::Now() + interval;
  LockGuard<Mutex> guard(sc->watchdogLock);
  if (!sc->watchdogThread) {
    MOZ_ASSERT(!sc->watchdogTimeout);
    sc->watchdogThread.emplace();
    AutoEnterOOMUnsafeRegion oomUnsafe;
    if (!sc->watchdogThread->init(WatchdogMain, cx)) {
      oomUnsafe.crash("watchdogThread.init");
    }
  } else if (!sc->watchdogTimeout || timeout < sc->watchdogTimeout.value()) {
    sc->watchdogWakeup.notify_one();
  }
  sc->watchdogTimeout = Some(timeout);
  return true;
}

static void KillWorkerThreads(JSContext* cx) {
  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();
  }

  workerThreads.clearAndFree();

  js_delete(workerThreadsLock);
  workerThreadsLock = nullptr;
}

static void CancelExecution(JSContext* cx) {
  ShellContext* sc = GetShellContext(cx);
  sc->serviceInterrupt = true;
  JS_RequestInterruptCallback(cx);
}

static bool SetTimeoutValue(JSContext* cx, double t) {
  if (mozilla::IsNaN(t)) {
    JS_ReportErrorASCII(cx, "timeout is not a number");
    return false;
  }
  const TimeDuration MAX_TIMEOUT_INTERVAL =
      TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
  if (TimeDuration::FromSeconds(t) > MAX_TIMEOUT_INTERVAL) {
    JS_ReportErrorASCII(cx, "Excessive timeout value");
    return false;
  }
  GetShellContext(cx)->timeoutInterval = t;
  if (!ScheduleWatchdog(cx, t)) {
    JS_ReportErrorASCII(cx, "Failed to create the watchdog");
    return false;
  }
  return true;
}

static bool Timeout(JSContext* cx, unsigned argc, Value* vp) {
  ShellContext* sc = GetShellContext(cx);
  CallArgs args = CallArgsFromVp(argc, vp);

  if (args.length() == 0) {
    args.rval().setNumber(sc->timeoutInterval);
    return true;
  }

  if (args.length() > 2) {
    JS_ReportErrorASCII(cx, "Wrong number of arguments");
    return false;
  }

  double t;
  if (!ToNumber(cx, args[0], &t)) {
    return false;
  }

  if (args.length() > 1) {
    RootedValue value(cx, args[1]);
    if (!value.isObject() || !value.toObject().is<JSFunction>()) {
      JS_ReportErrorASCII(cx, "Second argument must be a timeout function");
      return false;
    }
    sc->interruptFunc = value;
    sc->haveInterruptFunc = true;
  }

  args.rval().setUndefined();
  return SetTimeoutValue(cx, t);
}

static bool InterruptIf(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (args.length() != 1) {
    JS_ReportErrorASCII(cx, "Wrong number of arguments");
    return false;
  }

  if (ToBoolean(args[0])) {
    GetShellContext(cx)->serviceInterrupt = true;
    JS_RequestInterruptCallback(cx);
  }

  args.rval().setUndefined();
  return true;
}

static bool InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc,
                                           Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (args.length() != 1) {
    JS_ReportErrorASCII(cx, "Wrong number of arguments");
    return false;
  }

  GetShellContext(cx)->serviceInterrupt = true;
  JS_RequestInterruptCallback(cx);
  bool interruptRv = CheckForInterrupt(cx);

  // The interrupt handler could have set a pending exception. Since we call
  // back into JS, don't have it see the pending exception. If we have an
  // uncatchable exception that's not propagating a debug mode forced
  // return, return.
  if (!interruptRv && !cx->isExceptionPending() &&
      !cx->isPropagatingForcedReturn()) {
    return false;
  }

  JS::AutoSaveExceptionState savedExc(cx);

  FixedInvokeArgs<1> iargs(cx);

  iargs[0].setBoolean(interruptRv);

  RootedValue rv(cx);
  if (!js::Call(cx, args[0], UndefinedHandleValue, iargs, &rv)) {
    return false;
  }

  args.rval().setUndefined();
  return interruptRv;
}

static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (args.length() != 1) {
    JS_ReportErrorASCII(cx, "Wrong number of arguments");
    return false;
  }

  RootedValue value(cx, args[0]);
  if (!value.isObject() || !value.toObject().is<JSFunction>()) {
    JS_ReportErrorASCII(cx, "Argument must be a function");
    return false;
  }
  GetShellContext(cx)->interruptFunc = value;
  GetShellContext(cx)->haveInterruptFunc = true;

  args.rval().setUndefined();
  return true;
}

static bool SetJitCompilerOption(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].isString()) {
    ReportUsageErrorASCII(cx, callee, "First argument must be a String.");
    return false;
  }

  if (!args[1].isInt32()) {
    ReportUsageErrorASCII(cx, callee, "Second argument must be an Int32.");
    return false;
  }

  // Disallow setting JIT options when there are worker threads, to avoid
  // races.
  if (workerThreadsLock) {
    ReportUsageErrorASCII(
        cx, callee, "Can't set JIT options when there are worker threads.");
    return false;
  }

  JSFlatString* strArg = JS_FlattenString(cx, args[0].toString());
  if (!strArg) {
    return false;
  }

#define JIT_COMPILER_MATCH(key, string) \
  else if (JS_FlatStringEqualsAscii(strArg, string)) opt = JSJITCOMPILER_##key;

  JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
  if (false) {
  }
  JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
#undef JIT_COMPILER_MATCH

  if (opt == JSJITCOMPILER_NOT_AN_OPTION) {
    ReportUsageErrorASCII(
        cx, callee,
        "First argument does not name a valid option (see jsapi.h).");
    return false;
  }

  int32_t number = args[1].toInt32();
  if (number < 0) {
    number = -1;
  }

  // Throw if disabling the JITs and there's JIT code on the stack, to avoid
  // assertion failures.
  if ((opt == JSJITCOMPILER_BASELINE_ENABLE ||
       opt == JSJITCOMPILER_ION_ENABLE) &&
      number == 0) {
    js::jit::JitActivationIterator iter(cx);
    if (!iter.done()) {
      JS_ReportErrorASCII(cx,
                          "Can't turn off JITs with JIT code on the stack.");
      return false;
    }
  }

  // JIT compiler options are process-wide, so we have to stop off-thread
  // compilations for all runtimes to avoid races.
  HelperThreadState().waitForAllThreads();

  // Only release JIT code for the current runtime because there's no good
  // way to discard code for other runtimes.
  ReleaseAllJITCode(cx->runtime()->defaultFreeOp());

  JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number));

  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;
}

#if defined(DEBUG) || defined(JS_JITSPEW)
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));

  JS::UniqueChars buf =
      JS::FormatStackDump(cx, showArgs, showLocals, showThisProps);
  if (!buf) {
    fputs("Failed to format JavaScript stack for dump\n", gOutFile->fp);
    JS_ClearPendingException(cx);
  } else {
    fputs(buf.get(), gOutFile->fp);
  }

  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.requireAtLeast(cx, "compile", 1)) {
    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);

  JS::SourceText<char16_t> srcBuf;
  if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(),
                   scriptContents->length(), JS::SourceOwnership::Borrowed)) {
    return false;
  }

  RootedScript script(cx, JS::Compile(cx, options, srcBuf));
  if (!script) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

static ShellCompartmentPrivate* EnsureShellCompartmentPrivate(JSContext* cx) {
  Compartment* comp = cx->compartment();
  auto priv =
      static_cast<ShellCompartmentPrivate*>(JS_GetCompartmentPrivate(comp));
  if (!priv) {
    priv = cx->new_<ShellCompartmentPrivate>();
    JS_SetCompartmentPrivate(cx->compartment(), priv);
  }
  return priv;
}

static bool ParseModule(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "parseModule", 1)) {
    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 = JS_EncodeStringToLatin1(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();
  JS::SourceText<char16_t> srcBuf;
  if (!srcBuf.init(cx, chars, scriptContents->length(),
                   JS::SourceOwnership::Borrowed)) {
    return false;
  }

  RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
  if (!module) {
    return false;
  }

  args.rval().setObject(*module);
  return true;
}

static bool SetModuleLoadHook(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "setModuleLoadHook", 1)) {
    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;
  }

  Handle<GlobalObject*> global = cx->global();
  global->setReservedSlot(GlobalAppSlotModuleLoadHook, args[0]);

  args.rval().setUndefined();
  return true;
}

static bool SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "setModuleResolveHook", 1)) {
    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;
  }

  Handle<GlobalObject*> global = cx->global();
  global->setReservedSlot(GlobalAppSlotModuleResolveHook, args[0]);

  args.rval().setUndefined();
  return true;
}

static JSObject* ShellModuleResolveHook(JSContext* cx,
                                        HandleValue referencingPrivate,
                                        HandleString specifier) {
  Handle<GlobalObject*> global = cx->global();
  RootedValue hookValue(
      cx, global->getReservedSlot(GlobalAppSlotModuleResolveHook));
  if (hookValue.isUndefined()) {
    JS_ReportErrorASCII(cx, "Module resolve hook not set");
    return nullptr;
  }
  MOZ_ASSERT(hookValue.toObject().is<JSFunction>());

  JS::AutoValueArray<2> args(cx);
  args[0].set(referencingPrivate);
  args[1].setString(specifier);

  RootedValue result(cx);
  if (!JS_CallFunctionValue(cx, nullptr, hookValue, args, &result)) {
    return nullptr;
  }

  if (!result.isObject() || !result.toObject().is<ModuleObject>()) {
    JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object");
    return nullptr;
  }

  return &result.toObject();
}

static bool SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "setModuleMetadataHook", 1)) {
    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;
  }

  Handle<GlobalObject*> global = cx->global();
  global->setReservedSlot(GlobalAppSlotModuleMetadataHook, args[0]);

  args.rval().setUndefined();
  return true;
}

static bool CallModuleMetadataHook(JSContext* cx, HandleValue modulePrivate,
                                   HandleObject metaObject) {
  Handle<GlobalObject*> global = cx->global();
  RootedValue hookValue(
      cx, global->getReservedSlot(GlobalAppSlotModuleMetadataHook));
  if (hookValue.isUndefined()) {
    JS_ReportErrorASCII(cx, "Module metadata hook not set");
    return false;
  }
  MOZ_ASSERT(hookValue.toObject().is<JSFunction>());

  JS::AutoValueArray<2> args(cx);
  args[0].set(modulePrivate);
  args[1].setObject(*metaObject);

  RootedValue dummy(cx);
  return JS_CallFunctionValue(cx, nullptr, hookValue, args, &dummy);
}

static bool ReportArgumentTypeError(JSContext* cx, HandleValue value,
                                    const char* expected) {
  const char* typeName = InformalValueTypeName(value);
  JS_ReportErrorASCII(cx, "Expected %s, got %s", expected, typeName);
  return false;
}

static bool ShellSetModulePrivate(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "setModulePrivate", 2)) {
    return false;
  }

  if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
    return ReportArgumentTypeError(cx, args[0], "module object");
  }

  JS::SetModulePrivate(&args[0].toObject(), args[1]);
  args.rval().setUndefined();
  return true;
}

static bool ShellGetModulePrivate(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "getModulePrivate", 1)) {
    return false;
  }

  if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
    return ReportArgumentTypeError(cx, args[0], "module object");
  }

  args.rval().set(JS::GetModulePrivate(&args[0].toObject()));
  return true;
}

static bool SetModuleDynamicImportHook(JSContext* cx, unsigned argc,
                                       Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "setModuleDynamicImportHook", 1)) {
    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;
  }

  Handle<GlobalObject*> global = cx->global();
  global->setReservedSlot(GlobalAppSlotModuleDynamicImportHook, args[0]);

  args.rval().setUndefined();
  return true;
}

static bool FinishDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "finishDynamicModuleImport", 3)) {
    return false;
  }

  if (!args[1].isString()) {
    return ReportArgumentTypeError(cx, args[1], "String");
  }

  if (!args[2].isObject() || !args[2].toObject().is<PromiseObject>()) {
    return ReportArgumentTypeError(cx, args[2], "PromiseObject");
  }

  RootedString specifier(cx, args[1].toString());
  Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());

  return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
}

static bool AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!args.requireAtLeast(cx, "abortDynamicModuleImport", 4)) {
    return false;
  }

  if (!args[1].isString()) {
    return ReportArgumentTypeError(cx, args[1], "String");
  }

  if (!args[2].isObject() || !args[2].toObject().is<PromiseObject>()) {
    return ReportArgumentTypeError(cx, args[2], "PromiseObject");
  }

  RootedString specifier(cx, args[1].toString());
  Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());

  cx->setPendingExceptionAndCaptureStack(args[3]);
  return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
}

static bool ShellModuleDynamicImportHook(JSContext* cx,
                                         HandleValue referencingPrivate,
                                         HandleString specifier,
                                         HandleObject promise) {
  Handle<GlobalObject*> global = cx->global();
  RootedValue hookValue(
      cx, global->getReservedSlot(GlobalAppSlotModuleDynamicImportHook));
  if (hookValue.isUndefined()) {
    JS_ReportErrorASCII(cx, "Module resolve hook not set");
    return false;
  }
  MOZ_ASSERT(hookValue.toObject().is<JSFunction>());

  JS::AutoValueArray<3> args(cx);
  args[0].set(referencingPrivate);
  args[1].setString(specifier);
  args[2].setObject(*promise);

  RootedValue result(cx);
  return JS_CallFunctionValue(cx, nullptr, hookValue, args, &result);
}

static bool GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  ShellContext* sc = GetShellContext(cx);
  if (sc->moduleLoadPath) {
    JSString* str = JS_NewStringCopyZ(cx, sc->moduleLoadPath.get());
    if (!str) {
      return false;
    }

    args.rval().setString(str);
  } else {
    args.rval().setNull();
  }
  return true;
}

#if defined(JS_BUILD_BINAST)

using js::frontend::BinASTParser;
using js::frontend::Directives;
using js::frontend::GlobalSharedContext;
using js::frontend::ParseNode;
using js::frontend::UsedNameTracker;

template <typename Tok>
static bool ParseBinASTData(JSContext* cx, uint8_t* buf_data,
                            uint32_t buf_length, GlobalSharedContext* globalsc,
                            UsedNameTracker& usedNames,
                            const JS::ReadOnlyCompileOptions& options,
                            HandleScriptSourceObject sourceObj) {
  MOZ_ASSERT(globalsc);

  // Note: We need to keep `reader` alive as long as we can use `parsed`.
  BinASTParser<Tok> reader(cx, cx->tempLifoAlloc(), usedNames, options,
                           sourceObj);

  JS::Result<ParseNode*> parsed = reader.parse(globalsc, buf_data, buf_length);

  if (parsed.isErr()) {
    return false;
  }

#  ifdef DEBUG
  Fprinter out(stderr);
  DumpParseTree(parsed.unwrap(), out);
#  endif

  return true;
}

static bool BinParse(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "parseBin", 1)) {
    return false;
  }

  // Extract argument 1: ArrayBuffer.

  if (!args[0].isObject()) {
    const char* typeName = InformalValueTypeName(args[0]);
    JS_ReportErrorASCII(cx, "expected object (ArrayBuffer) to parse, got %s",
                        typeName);
    return false;
  }

  RootedObject objBuf(cx, &args[0].toObject());
  if (!JS::IsArrayBufferObject(objBuf)) {
    const char* typeName = InformalValueTypeName(args[0]);
    JS_ReportErrorASCII(cx, "expected ArrayBuffer to parse, got %s", typeName);
    return false;
  }

  uint32_t buf_length = 0;
  bool buf_isSharedMemory = false;
  uint8_t* buf_data = nullptr;
  JS::GetArrayBufferLengthAndData(objBuf, &buf_length, &buf_isSharedMemory,
                                  &buf_data);
  MOZ_ASSERT(buf_data);

  // Extract argument 2: Options.

  if (args.length() >= 2) {
    if (!args[1].isObject()) {
      const char* typeName = InformalValueTypeName(args[1]);
      JS_ReportErrorASCII(cx, "expected object (options) to parse, got %s",
                          typeName);
      return false;
    }
    RootedObject objOptions(cx, &args[1].toObject());

    RootedValue optionFormat(cx);
    if (!JS_GetProperty(cx, objOptions, "format", &optionFormat)) {
      return false;
    }

    if (!optionFormat.isUndefined()) {
      RootedLinearString linearFormat(
          cx, optionFormat.toString()->ensureLinear(cx));
      if (!linearFormat) {
        return false;
      }
      // Currently not used, reserved for future.
      if (!StringEqualsAscii(linearFormat, "multipart")) {
        UniqueChars printable = JS_EncodeStringToUTF8(cx, linearFormat);
        if (!printable) {
          return false;
        }

        JS_ReportErrorUTF8(
            cx,
            "Unknown value for option `format`, expected 'multipart', got %s",
            printable.get());
        return false;
      }
    } else {
      const char* typeName = InformalValueTypeName(optionFormat);
      JS_ReportErrorASCII(cx, "option `format` should be a string, got %s",
                          typeName);
      return false;
    }
  }

  CompileOptions options(cx);
  options.setIntroductionType("js shell bin parse")
      .setFileAndLine("<ArrayBuffer>", 1);

  UsedNameTracker usedNames(cx);

  RootedScriptSourceObject sourceObj(
      cx, frontend::CreateScriptSourceObject(cx, options, Nothing()));
  if (!sourceObj) {
    return false;
  }

  Directives directives(false);
  GlobalSharedContext globalsc(cx, ScopeKind::Global, directives, false);

  auto parseFunc = ParseBinASTData<frontend::BinASTTokenReaderMultipart>;
  if (!parseFunc(cx, buf_data, buf_length, &globalsc, usedNames, options,
                 sourceObj)) {
    return false;
  }

  args.rval().setUndefined();
  return true;
}

#endif  // defined(JS_BUILD_BINAST)

static bool Parse(JSContext* cx, unsigned argc, Value* vp) {
  using namespace js::frontend;

  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "parse", 1)) {
    return false;
  }
  if (!args[0].isString()) {
    const char* typeName = InformalValueTypeName(args[0]);
    JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
    return false;
  }

  bool allowSyntaxParser = true;
  frontend::ParseGoal goal = frontend::ParseGoal::Script;

  if (args.length() >= 2) {
    if (!args[1].isObject()) {
      const char* typeName = InformalValueTypeName(args[1]);
      JS_ReportErrorASCII(cx, "expected object (options) to parse, got %s",
                          typeName);
      return false;
    }
    RootedObject objOptions(cx, &args[1].toObject());

    RootedValue optionAllowSyntaxParser(cx);
    if (!JS_GetProperty(cx, objOptions, "allowSyntaxParser",
                        &optionAllowSyntaxParser)) {
      return false;
    }

    if (optionAllowSyntaxParser.isBoolean()) {
      allowSyntaxParser = optionAllowSyntaxParser.toBoolean();
    } else if (!optionAllowSyntaxParser.isUndefined()) {
      const char* typeName = InformalValueTypeName(optionAllowSyntaxParser);
      JS_ReportErrorASCII(
          cx, "option `allowSyntaxParser` should be a boolean, got %s",
          typeName);
      return false;
    }

    RootedValue optionModule(cx);
    if (!JS_GetProperty(cx, objOptions, "module", &optionModule)) {
      return false;
    }

    if (optionModule.isBoolean()) {
      if (optionModule.toBoolean()) {
        goal = frontend::ParseGoal::Module;
      }
    } else if (!optionModule.isUndefined()) {
      const char* typeName = InformalValueTypeName(optionModule);
      JS_ReportErrorASCII(cx, "option `module` should be a boolean, 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)
      .setAllowSyntaxParser(allowSyntaxParser);
  if (goal == frontend::ParseGoal::Module) {
    // See frontend::CompileModule.
    options.maybeMakeStrictMode(true);
    options.allowHTMLComments = false;
  }

  UsedNameTracker usedNames(cx);

  RootedScriptSourceObject sourceObject(
      cx, frontend::CreateScriptSourceObject(cx, options, Nothing()));
  if (!sourceObject) {
    return false;
  }

  Parser<FullParseHandler, char16_t> parser(
      cx, cx->tempLifoAlloc(), options, chars, length,
      /* foldConstants = */ false, usedNames, nullptr, nullptr, sourceObject,
      goal);
  if (!parser.checkOptions()) {
    return false;
  }

  ParseNode* pn;  // Deallocated once `parser` goes out of scope.
  if (goal == frontend::ParseGoal::Script) {
    pn = parser.parse();
  } else {
    if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) {
      return false;
    }

    Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
    if (!module) {
      return false;
    }

    ModuleBuilder builder(cx, module, &parser);

    ModuleSharedContext modulesc(cx, module, nullptr, builder);
    pn = parser.moduleBody(&modulesc);
  }
  if (!pn) {
    return false;
  }
#ifdef DEBUG
  js::Fprinter out(stderr);
  DumpParseTree(pn, out);
#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.requireAtLeast(cx, "syntaxParse", 1)) {
    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);

  RootedScriptSourceObject sourceObject(
      cx, frontend::CreateScriptSourceObject(cx, options, Nothing()));
  if (!sourceObject) {
    return false;
  }

  Parser<frontend::SyntaxParseHandler, char16_t> parser(
      cx, cx->tempLifoAlloc(), options, chars, length, false, usedNames,
      nullptr, nullptr, sourceObject, frontend::ParseGoal::Script);
  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(JS::OffThreadToken* token,
                                           void* callbackData) {
  auto job = static_cast<OffThreadJob*>(callbackData);
  job->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.requireAtLeast(cx, "offThreadCompileScript", 1)) {
    return false;
  }
  if (!args[0].isString()) {
    const char* typeName = InformalValueTypeName(args[0]);
    JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
    return false;
  }

  UniqueChars 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.twoByteChars();

  // Make sure we own the string's chars, so that they are not freed before
  // the compilation is finished.
  UniqueTwoByteChars ownedChars;
  if (stableChars.maybeGiveOwnershipToCaller()) {
    ownedChars.reset(const_cast<char16_t*>(chars));
  } else {
    ownedChars.reset(cx->pod_malloc<char16_t>(length));
    if (!ownedChars) {
      return false;
    }

    mozilla::PodCopy(ownedChars.get(), chars, length);
  }

  if (!JS::CanCompileOffThread(cx, options, length)) {
    JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
    return false;
  }

  OffThreadJob* job = NewOffThreadJob(
      cx, ScriptKind::Script, OffThreadJob::Source(std::move(ownedChars)));
  if (!job) {
    return false;
  }

  JS::SourceText<char16_t> srcBuf;
  if (!srcBuf.init(cx, job->sourceChars(), length,
                   JS::SourceOwnership::Borrowed) ||
      !JS::CompileOffThread(cx, options, srcBuf, OffThreadCompileScriptCallback,
                            job)) {
    job->cancel();
    DeleteOffThreadJob(cx, job);
    return false;
  }

  args.rval().setInt32(job->id);
  return true;
}

static bool runOffThreadScript(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (OffThreadParsingMustWaitForGC(cx->runtime())) {
    gc::FinishGC(cx);
  }

  OffThreadJob* job =
      LookupOffThreadJobForArgs(cx, ScriptKind::Script, args, 0);
  if (!job) {
    return false;
  }

  JS::OffThreadToken* token = job->waitUntilDone(cx);
  MOZ_ASSERT(token);

  DeleteOffThreadJob(cx, job);

  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;
  }

  UniqueChars 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.twoByteChars();

  // Make sure we own the string's chars, so that they are not freed before
  // the compilation is finished.
  UniqueTwoByteChars ownedChars;
  if (stableChars.maybeGiveOwnershipToCaller()) {
    ownedChars.reset(const_cast<char16_t*>(chars));
  } else {
    ownedChars.reset(cx->pod_malloc<char16_t>(length));
    if (!ownedChars) {
      return false;
    }

    mozilla::PodCopy(ownedChars.get(), chars, length);
  }

  if (!JS::CanCompileOffThread(cx, options, length)) {
    JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
    return false;
  }

  OffThreadJob* job = NewOffThreadJob(
      cx, ScriptKind::Module, OffThreadJob::Source(std::move(ownedChars)));
  if (!job) {
    return false;
  }

  JS::SourceText<char16_t> srcBuf;
  if (!srcBuf.init(cx, job->sourceChars(), length,
                   JS::SourceOwnership::Borrowed) ||
      !JS::CompileOffThreadModule(cx, options, srcBuf,
                                  OffThreadCompileScriptCallback, job)) {
    job->cancel();
    DeleteOffThreadJob(cx, job);
    return false;
  }

  args.rval().setInt32(job->id);
  return true;
}

static bool FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (OffThreadParsingMustWaitForGC(cx->runtime())) {
    gc::FinishGC(cx);
  }

  OffThreadJob* job =
      LookupOffThreadJobForArgs(cx, ScriptKind::Module, args, 0);
  if (!job) {
    return false;
  }

  JS::OffThreadToken* token = job->waitUntilDone(cx);
  MOZ_ASSERT(token);

  DeleteOffThreadJob(cx, job);

  RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
  if (!module) {
    return false;
  }

  args.rval().setObject(*module);
  return true;
}

static bool OffThreadDecodeScript(JSContext* cx, unsigned argc, Value* vp) {
  if (!CanUseExtraThreads()) {
    JS_ReportErrorASCII(cx,
                        "Can't use offThreadDecodeScript with --no-threads");
    return false;
  }

  CallArgs args = CallArgsFromVp(argc, vp);

  if (!args.requireAtLeast(cx, "offThreadDecodeScript", 1)) {
    return false;
  }
  if (!args[0].isObject() || !CacheEntry_isCacheEntry(&args[0].toObject())) {
    const char* typeName = InformalValueTypeName(args[0]);
    JS_ReportErrorASCII(cx, "expected cache entry, got %s", typeName);
    return false;
  }
  RootedObject cacheEntry(cx, &args[0].toObject());

  UniqueChars fileNameBytes;
  CompileOptions options(cx);
  options.setIntroductionType("js shell offThreadDecodeScript")
      .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;

  JS::TranscodeBuffer loadBuffer;
  uint32_t loadLength = 0;
  uint8_t* loadData = nullptr;
  loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
  if (!loadData) {
    return false;
  }
  if (!loadBuffer.append(loadData, loadLength)) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  if (!JS::CanDecodeOffThread(cx, options, loadLength)) {
    JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
    return false;
  }

  OffThreadJob* job =
      NewOffThreadJob(cx, ScriptKind::DecodeScript,
                      OffThreadJob::Source(std::move(loadBuffer)));
  if (!job) {
    return false;
  }

  if (!JS::DecodeOffThreadScript(cx, options, job->xdrBuffer(), 0,
                                 OffThreadCompileScriptCallback, job)) {
    job->cancel();
    DeleteOffThreadJob(cx, job);
    return false;
  }

  args.rval().setInt32(job->id);
  return true;
}

static bool runOffThreadDecodedScript(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  if (OffThreadParsingMustWaitForGC(cx->runtime())) {
    gc::FinishGC(cx);
  }

  OffThreadJob* job =
      LookupOffThreadJobForArgs(cx, ScriptKind::DecodeScript, args, 0);
  if (!job) {
    return false;
  }

  JS::OffThreadToken* token = job->waitUntilDone(cx);
  MOZ_ASSERT(token);

  DeleteOffThreadJob(cx, job);

  RootedScript script(cx, JS::FinishOffThreadScriptDecoder(cx, token));
  if (!script) {
    return false;
  }

  return JS_ExecuteScript(cx, script, args.rval());
}

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(UniqueChars&& arg) {
    if (!argv_.append(arg.get())) {
      return false;
    }

    // Now owned by this vector.
    mozilla::Unused << arg.release();
    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, UniqueChars arg) {
    js_free(argv_[i]);
    argv_[i] = arg.release();
  }
};

#if defined(XP_WIN)
static bool EscapeForShell(JSContext* cx, 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++;
      }
    }

    auto escaped = cx->make_pod_array<char>(newLen);
    if (!escaped) {
      return false;
    }

    char* src = argv[i];
    char* dst = escaped.get();
    *dst++ = '\"';