Source code

Revision control

Other Tools

/* -*- 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/IntegerPrintfMacros.h"
#include "mozilla/mozalloc.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtrExtensions.h" // UniqueFreePtr
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/Variant.h"
#include <algorithm>
#include <chrono>
#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 <ctime>
#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"
#ifndef JS_WITHOUT_NSPR
# include "prerror.h"
# include "prlink.h"
#endif
#include "builtin/Array.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/RegExp.h"
#include "builtin/TestingFunctions.h"
#include "debugger/DebugAPI.h"
#include "frontend/CompilationInfo.h"
#ifdef JS_ENABLE_SMOOSH
# include "frontend/Frontend2.h"
#endif
#include "frontend/ModuleSharedContext.h"
#include "frontend/Parser.h"
#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
#include "gc/PublicIterators.h"
#ifdef JS_SIMULATOR_ARM
# include "jit/arm/Simulator-arm.h"
#endif
#ifdef JS_SIMULATOR_MIPS32
# include "jit/mips32/Simulator-mips32.h"
#endif
#ifdef JS_SIMULATOR_MIPS64
# include "jit/mips64/Simulator-mips64.h"
#endif
#include "jit/CacheIRHealth.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/Array.h" // JS::NewArrayObject
#include "js/ArrayBuffer.h" // JS::{CreateMappedArrayBufferContents,NewMappedArrayBufferWithContents,IsArrayBufferObject,GetArrayBufferLengthAndData}
#include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
#include "js/CharacterEncoding.h" // JS::StringIsASCII
#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/ErrorReport.h" // JS::PrintError
#include "js/Exception.h" // JS::StealPendingExceptionStack
#include "js/experimental/SourceHook.h" // js::{Set,Forget,}SourceHook
#include "js/experimental/TypedData.h" // JS_NewUint8Array
#include "js/friend/DumpFunctions.h" // JS::FormatStackDump
#include "js/friend/StackLimits.h" // js::CheckRecursionLimitConservative
#include "js/friend/WindowProxy.h" // js::IsWindowProxy, js::SetWindowProxyClass, js::ToWindowProxyIfWindow, js::ToWindowIfWindowProxy
#include "js/GCAPI.h" // JS::AutoCheckCannotGC
#include "js/GCVector.h"
#include "js/Initialization.h"
#include "js/JSON.h"
#include "js/MemoryFunctions.h"
#include "js/Modules.h" // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate
#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/WasmModule.h" // JS::WasmModule
#include "js/Wrapper.h"
#include "shell/jsoptparse.h"
#include "shell/jsshell.h"
#include "shell/OSObject.h"
#include "shell/WasmTesting.h"
#include "threading/ConditionVariable.h"
#include "threading/ExclusiveData.h"
#include "threading/LockGuard.h"
#include "threading/Thread.h"
#include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "util/Windows.h"
#include "vm/ArgumentsObject.h"
#include "vm/Compression.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" // QuoteString
#include "vm/PromiseObject.h" // js::PromiseObject
#include "vm/Shape.h"
#include "vm/SharedArrayObject.h"
#include "vm/Time.h"
#include "vm/ToSource.h" // js::ValueToSource
#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;
enum JSShellExitCode {
EXITCODE_RUNTIME_ERROR = 3,
EXITCODE_FILE_NOT_FOUND = 4,
EXITCODE_OUT_OF_MEMORY = 5,
EXITCODE_TIMEOUT = 6
};
/*
* 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
#ifdef ENABLE_SHARED_MEMORY
# define SHARED_MEMORY_DEFAULT 1
#else
# define SHARED_MEMORY_DEFAULT 0
#endif
// Fuzzing support for JS runtime fuzzing
#ifdef FUZZING_INTERFACES
# include "shell/jsrtfuzzing/jsrtfuzzing.h"
static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
static bool fuzzHaveModule = !!getenv("FUZZER");
#endif // FUZZING_INTERFACES
// 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;
};
bool shell::enableCodeCoverage = false;
bool shell::enableDisassemblyDumps = false;
bool shell::offthreadCompilation = false;
bool shell::enableAsmJS = false;
bool shell::enableWasm = false;
bool shell::enableSharedMemory = SHARED_MEMORY_DEFAULT;
bool shell::enableWasmBaseline = false;
bool shell::enableWasmIon = false;
bool shell::enableWasmCranelift = false;
bool shell::enableWasmReftypes = true;
#ifdef ENABLE_WASM_GC
bool shell::enableWasmGc = false;
#endif
#ifdef ENABLE_WASM_MULTI_VALUE
bool shell::enableWasmMultiValue = true;
#endif
#ifdef ENABLE_WASM_SIMD
bool shell::enableWasmSimd = true;
#endif
bool shell::enableWasmVerbose = false;
bool shell::enableTestWasmAwaitTier2 = false;
bool shell::enableSourcePragmas = true;
bool shell::enableAsyncStacks = false;
bool shell::enableAsyncStackCaptureDebuggeeOnly = false;
bool shell::enableStreams = false;
bool shell::enableReadableByteStreams = false;
bool shell::enableBYOBStreamReaders = false;
bool shell::enableWritableStreams = false;
bool shell::enableReadableStreamPipeTo = false;
bool shell::enableWeakRefs = false;
bool shell::enableToSource = false;
bool shell::enablePropertyErrorMessageFix = false;
bool shell::enableIteratorHelpers = false;
bool shell::enablePrivateClassFields = false;
#ifdef JS_GC_ZEAL
uint32_t shell::gZealBits = 0;
uint32_t shell::gZealFrequency = 0;
#endif
bool shell::printTiming = false;
RCFile* shell::gErrFile = nullptr;
RCFile* shell::gOutFile = nullptr;
bool shell::reportWarnings = true;
bool shell::compileOnly = false;
bool shell::fuzzingSafe = false;
bool shell::disableOOMFunctions = false;
bool shell::defaultToSameCompartment = true;
#ifdef DEBUG
bool shell::dumpEntrainedVariables = false;
bool shell::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 JSClass 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);
JSAutoRealm ar(cx, global);
JSObject* obj =
js::Wrapper::NewSingleton(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);
}
bool isSystemOrAddonPrincipal() override { return true; }
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),
finalizationRegistryCleanupCallbacks(cx) {}
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 mozilla::UniqueFreePtr<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) {
mozilla::UniqueFreePtr<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.get());
}
return linep;
}
#endif
size_t len = 0;
if (*prompt != '\0' && gOutFile->isOpen()) {
fprintf(gOutFile->fp, "%s", prompt);
fflush(gOutFile->fp);
}
size_t size = 80;
mozilla::UniqueFreePtr<char[]> buffer(static_cast<char*>(malloc(size)));
if (!buffer) {
return nullptr;
}
char* current = buffer.get();
do {
while (true) {
if (fgets(current, size - len, file)) {
break;
}
if (errno != EINTR) {
return nullptr;
}
}
len += strlen(current);
char* t = buffer.get() + len - 1;
if (*t == '\n') {
/* Line was read. We remove '\n' and exit. */
*t = '\0';
break;
}
if (len + 1 == size) {
size = size * 2;
char* raw = buffer.release();
char* tmp = static_cast<char*>(realloc(raw, size));
if (!tmp) {
free(raw);
return nullptr;
}
buffer.reset(tmp);
}
current = buffer.get() + 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;
}
}
JSObject* js::shell::CreateScriptPrivate(JSContext* cx, HandleString path) {
RootedObject info(cx, JS_NewPlainObject(cx));
if (!info) {
return nullptr;
}
if (path) {
RootedValue pathValue(cx, StringValue(path));
if (!JS_DefineProperty(cx, info, "path", pathValue, JSPROP_ENUMERATE)) {
return nullptr;
}
}
return info;
}
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;
}
MOZ_ASSERT(JS::GetScriptPrivate(script).isUndefined());
RootedObject infoObject(cx, CreateScriptPrivate(cx, path));
if (!infoObject) {
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) {
script = JS::CompileUtf8File(cx, options, file);
} else {
fprintf(stderr, "(compiling '%s' after inflating to UTF-16)\n", filename);
FileContents buffer(cx);
if (!ReadCompleteFile(cx, file, buffer)) {
return false;
}
size_t length = buffer.length();
auto chars = UniqueTwoByteChars(
UTF8CharsToNewTwoByteCharsZ(
cx,
UTF8Chars(reinterpret_cast<const char*>(buffer.begin()),
buffer.length()),
&length, js::MallocArena)
.get());
if (!chars) {
return false;
}
JS::SourceText<char16_t> source;
if (!source.init(cx, std::move(chars), length)) {
return false;
}
script = JS::Compile(cx, options, source);
}
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;
}
static MOZ_MUST_USE bool RunModule(JSContext* cx, const char* filename,
bool compileOnly) {
ShellContext* sc = GetShellContext(cx);
RootedString path(cx, JS_NewStringCopyZ(cx, filename));
if (!path) {
return false;
}
path = ResolvePath(cx, path, RootRelative);
if (!path) {
return false;
}
return sc->moduleLoader->loadRootModule(cx, path);
}
static void ShellCleanupFinalizationRegistryCallback(JSFunction* doCleanup,
JSObject* incumbentGlobal,
void* data) {
// In the browser this queues a task. Shell jobs correspond to microtasks so
// we arrange for cleanup to happen after all jobs/microtasks have run. The
// incumbent global is ignored in the shell.
auto sc = static_cast<ShellContext*>(data);
AutoEnterOOMUnsafeRegion oomUnsafe;
if (!sc->finalizationRegistryCleanupCallbacks.append(doCleanup)) {
oomUnsafe.crash("ShellCleanupFinalizationRegistryCallback");
}
}
// Run any FinalizationRegistry cleanup tasks and return whether any ran.
static bool MaybeRunFinalizationRegistryCleanupTasks(JSContext* cx) {
ShellContext* sc = GetShellContext(cx);
MOZ_ASSERT(!sc->quitting);
Rooted<ShellContext::FunctionVector> callbacks(cx);
std::swap(callbacks.get(), sc->finalizationRegistryCleanupCallbacks.get());
bool ranTasks = false;
RootedFunction callback(cx);
for (JSFunction* f : callbacks) {
callback = f;
AutoRealm ar(cx, f);
{
AutoReportException are(cx);
RootedValue unused(cx);
mozilla::Unused << JS_CallFunction(cx, nullptr, callback,
HandleValueArray::empty(), &unused);
}
ranTasks = true;
if (sc->quitting) {
break;
}
}
return ranTasks;
}
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 void RunShellJobs(JSContext* cx) {
ShellContext* sc = GetShellContext(cx);
if (sc->quitting) {
return;
}
while (true) {
// Run microtasks.
js::RunJobs(cx);
if (sc->quitting) {
return;
}
// Run tasks (only finalization registry clean tasks are possible).
bool ranTasks = MaybeRunFinalizationRegistryCleanupTasks(cx);
if (!ranTasks) {
break;
}
}
}
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;
}
RunShellJobs(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, bool mutedErrors, 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 JS_HAS_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;
}
if (!js::AddMozDisplayNamesConstructor(cx, intl)) {
return false;
}
args.rval().setUndefined();
return true;
}
#endif // JS_HAS_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::Compile(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;
mozilla::UniqueFreePtr<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.get(), strlen(line.get())) ||
!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);
}
RunShellJobs(cx);
} while (!hitEOF && !sc->quitting);
if (gOutFile->isOpen()) {
fprintf(gOutFile->fp, "\n");
}
return true;
}
enum FileKind {
FileScript, // UTF-8, directly parsed as such
FileScriptUtf16, // FileScript, but inflate to UTF-16 before parsing
FileModule,
};
static void ReportCantOpenErrorUnknownEncoding(JSContext* cx,
const char* filename) {
/*
* Filenames are in some random system encoding. *Probably* it's UTF-8,
* but no guarantees.
*
* strerror(errno)'s encoding, in contrast, depends on the user's C locale.
*
* Latin-1 is possibly wrong for both of these -- but at least if it's
* wrong it'll produce mojibake *safely*. Run with Latin-1 til someone
* complains.
*/
JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN,
filename, strerror(errno));
}
static MOZ_MUST_USE bool Process(JSContext* cx, const char* filename,
bool forceTTY, FileKind kind) {
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::DontInflate,
compileOnly)) {
return false;
}
break;
case FileScriptUtf16:
if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16,
compileOnly)) {
return false;
}
break;
case FileModule:
if (!RunModule(cx, filename, compileOnly)) {
return false;
}
break;
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 (StringEqualsLiteral(opt, "throw_on_asmjs_validation_failure")) {
JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
} else if (StringEqualsLiteral(opt, "strict_mode")) {
JS::ContextOptionsRef(cx).toggleStrictMode();
} else {
UniqueChars optChars = QuoteString(cx, opt, '"');
if (!optChars) {
return false;
}
JS_ReportErrorASCII(cx,
"unknown option name %s."
" The valid names are "
"throw_on_asmjs_validation_failure and strict_mode.",
optChars.get());
return false;
}
}
UniqueChars names = DuplicateString("");
bool found = false;
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, "skipFileNameValidation", &v)) {
return false;
}
if (!v.isUndefined()) {
options.setSkipFilenameValidation(ToBoolean(v));
}
if (!JS_GetProperty(cx, opts, "element", &v)) {
return false;
}
if (v.isObject()) {
RootedObject infoObject(cx, CreateScriptPrivate(cx));
RootedValue elementValue(cx, v);
if (!JS_WrapValue(cx, &elementValue)) {
return false;
}
if (!JS_DefineProperty(cx, infoObject, "element", elementValue, 0)) {
return false;
}
options.setPrivateValue(ObjectValue(*infoObject));
}
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->isHelperThreadContext()) {
return;
}
MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
JS::PrepareForFullGC(cx);
cx->runtime()->gc.gc(GC_SHRINK, JS::GCReason::SHARED_MEMORY_LIMIT);
}
static const uint32_t CacheEntry_SOURCE = 0;
static const uint32_t CacheEntry_BYTECODE = 1;