7988 lines
244 KiB
C++
7988 lines
244 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* JS shell. */
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/GuardObjects.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/mozalloc.h"
|
|
#include "mozilla/PodOperations.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/SizePrintfMacros.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
|
|
#ifdef XP_WIN
|
|
# include <direct.h>
|
|
# include <process.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#if defined(XP_WIN)
|
|
# include <io.h> /* for isatty() */
|
|
#endif
|
|
#include <locale.h>
|
|
#if defined(MALLOC_H)
|
|
# include MALLOC_H /* for malloc_usable_size, malloc_size, _msize */
|
|
#endif
|
|
#include <math.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#ifdef XP_UNIX
|
|
# include <sys/mman.h>
|
|
# include <sys/stat.h>
|
|
# include <sys/wait.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include "jsapi.h"
|
|
#include "jsarray.h"
|
|
#include "jsatom.h"
|
|
#include "jscntxt.h"
|
|
#include "jsfun.h"
|
|
#include "jsobj.h"
|
|
#include "jsprf.h"
|
|
#include "jsscript.h"
|
|
#include "jstypes.h"
|
|
#include "jsutil.h"
|
|
#ifdef XP_WIN
|
|
# include "jswin.h"
|
|
#endif
|
|
#include "jswrapper.h"
|
|
#include "shellmoduleloader.out.h"
|
|
|
|
#include "builtin/ModuleObject.h"
|
|
#include "builtin/TestingFunctions.h"
|
|
#include "frontend/Parser.h"
|
|
#include "gc/GCInternals.h"
|
|
#include "jit/arm/Simulator-arm.h"
|
|
#include "jit/InlinableNatives.h"
|
|
#include "jit/Ion.h"
|
|
#include "jit/JitcodeMap.h"
|
|
#include "jit/OptimizationTracking.h"
|
|
#include "js/Debug.h"
|
|
#include "js/GCAPI.h"
|
|
#include "js/Initialization.h"
|
|
#include "js/StructuredClone.h"
|
|
#include "js/TrackedOptimizationInfo.h"
|
|
#include "perf/jsperf.h"
|
|
#include "shell/jsoptparse.h"
|
|
#include "shell/jsshell.h"
|
|
#include "shell/OSObject.h"
|
|
#include "threading/ConditionVariable.h"
|
|
#include "threading/LockGuard.h"
|
|
#include "threading/Thread.h"
|
|
#include "vm/ArgumentsObject.h"
|
|
#include "vm/AsyncFunction.h"
|
|
#include "vm/Compression.h"
|
|
#include "vm/Debugger.h"
|
|
#include "vm/HelperThreads.h"
|
|
#include "vm/Monitor.h"
|
|
#include "vm/MutexIDs.h"
|
|
#include "vm/Shape.h"
|
|
#include "vm/SharedArrayObject.h"
|
|
#include "vm/StringBuffer.h"
|
|
#include "vm/Time.h"
|
|
#include "vm/TypedArrayObject.h"
|
|
#include "vm/WrapperObject.h"
|
|
#include "wasm/WasmJS.h"
|
|
|
|
#include "jscompartmentinlines.h"
|
|
#include "jsobjinlines.h"
|
|
|
|
#include "vm/ErrorObject-inl.h"
|
|
#include "vm/Interpreter-inl.h"
|
|
#include "vm/Stack-inl.h"
|
|
|
|
using namespace js;
|
|
using namespace js::cli;
|
|
using namespace js::shell;
|
|
|
|
using mozilla::ArrayLength;
|
|
using mozilla::Atomic;
|
|
using mozilla::MakeScopeExit;
|
|
using mozilla::Maybe;
|
|
using mozilla::Nothing;
|
|
using mozilla::NumberEqualsInt32;
|
|
using mozilla::PodCopy;
|
|
using mozilla::PodEqual;
|
|
using mozilla::TimeDuration;
|
|
using mozilla::TimeStamp;
|
|
|
|
enum JSShellExitCode {
|
|
EXITCODE_RUNTIME_ERROR = 3,
|
|
EXITCODE_FILE_NOT_FOUND = 4,
|
|
EXITCODE_OUT_OF_MEMORY = 5,
|
|
EXITCODE_TIMEOUT = 6
|
|
};
|
|
|
|
static const size_t gStackChunkSize = 8192;
|
|
|
|
/*
|
|
* Note: This limit should match the stack limit set by the browser in
|
|
* js/xpconnect/src/XPCJSContext.cpp
|
|
*/
|
|
#if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
|
|
static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
|
|
#else
|
|
static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024;
|
|
#endif
|
|
|
|
/*
|
|
* Limit the timeout to 30 minutes to prevent an overflow on platfoms
|
|
* that represent the time internally in microseconds using 32-bit int.
|
|
*/
|
|
static const double MAX_TIMEOUT_SECONDS = 1800.0;
|
|
|
|
// SharedArrayBuffer and Atomics settings track Firefox. Choose a custom setting
|
|
// with --shared-memory={on,off}.
|
|
#ifndef RELEASE_OR_BETA
|
|
# define SHARED_MEMORY_DEFAULT 1
|
|
#else
|
|
# define SHARED_MEMORY_DEFAULT 0
|
|
#endif
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
using JobQueue = GCVector<JSObject*, 0, SystemAllocPolicy>;
|
|
|
|
struct ShellAsyncTasks
|
|
{
|
|
explicit ShellAsyncTasks(JSContext* cx)
|
|
: outstanding(0),
|
|
finished(cx)
|
|
{}
|
|
|
|
size_t outstanding;
|
|
Vector<JS::AsyncTask*> finished;
|
|
};
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
enum class ScriptKind
|
|
{
|
|
Script,
|
|
Module
|
|
};
|
|
|
|
class OffThreadState {
|
|
enum State {
|
|
IDLE, /* ready to work; no token, no source */
|
|
COMPILING, /* working; no token, have source */
|
|
DONE /* compilation done: have token and source */
|
|
};
|
|
|
|
public:
|
|
OffThreadState()
|
|
: monitor(mutexid::ShellOffThreadState),
|
|
state(IDLE),
|
|
token(),
|
|
source(nullptr)
|
|
{ }
|
|
|
|
bool startIfIdle(JSContext* cx, ScriptKind kind,
|
|
ScopedJSFreePtr<char16_t>& newSource)
|
|
{
|
|
AutoLockMonitor alm(monitor);
|
|
if (state != IDLE)
|
|
return false;
|
|
|
|
MOZ_ASSERT(!token);
|
|
|
|
source = newSource.forget();
|
|
|
|
scriptKind = kind;
|
|
state = COMPILING;
|
|
return true;
|
|
}
|
|
|
|
void abandon(JSContext* cx) {
|
|
AutoLockMonitor alm(monitor);
|
|
MOZ_ASSERT(state == COMPILING);
|
|
MOZ_ASSERT(!token);
|
|
MOZ_ASSERT(source);
|
|
|
|
js_free(source);
|
|
source = nullptr;
|
|
|
|
state = IDLE;
|
|
}
|
|
|
|
void markDone(void* newToken) {
|
|
AutoLockMonitor alm(monitor);
|
|
MOZ_ASSERT(state == COMPILING);
|
|
MOZ_ASSERT(!token);
|
|
MOZ_ASSERT(source);
|
|
MOZ_ASSERT(newToken);
|
|
|
|
token = newToken;
|
|
state = DONE;
|
|
alm.notify();
|
|
}
|
|
|
|
void* waitUntilDone(JSContext* cx, ScriptKind kind) {
|
|
AutoLockMonitor alm(monitor);
|
|
if (state == IDLE || scriptKind != kind)
|
|
return nullptr;
|
|
|
|
if (state == COMPILING) {
|
|
while (state != DONE)
|
|
alm.wait();
|
|
}
|
|
|
|
MOZ_ASSERT(source);
|
|
js_free(source);
|
|
source = nullptr;
|
|
|
|
MOZ_ASSERT(token);
|
|
void* holdToken = token;
|
|
token = nullptr;
|
|
state = IDLE;
|
|
return holdToken;
|
|
}
|
|
|
|
private:
|
|
Monitor monitor;
|
|
ScriptKind scriptKind;
|
|
State state;
|
|
void* token;
|
|
char16_t* source;
|
|
};
|
|
|
|
// Per-context shell state.
|
|
struct ShellContext
|
|
{
|
|
explicit ShellContext(JSContext* cx);
|
|
|
|
bool isWorker;
|
|
double timeoutInterval;
|
|
double startTime;
|
|
Atomic<bool> serviceInterrupt;
|
|
Atomic<bool> haveInterruptFunc;
|
|
JS::PersistentRootedValue interruptFunc;
|
|
bool lastWarningEnabled;
|
|
JS::PersistentRootedValue lastWarning;
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
JS::PersistentRootedValue promiseRejectionTrackerCallback;
|
|
JS::PersistentRooted<JobQueue> jobQueue;
|
|
ExclusiveData<ShellAsyncTasks> asyncTasks;
|
|
bool drainingJobQueue;
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
/*
|
|
* Watchdog thread state.
|
|
*/
|
|
Mutex watchdogLock;
|
|
ConditionVariable watchdogWakeup;
|
|
Maybe<Thread> watchdogThread;
|
|
Maybe<TimeStamp> watchdogTimeout;
|
|
|
|
ConditionVariable sleepWakeup;
|
|
|
|
int exitCode;
|
|
bool quitting;
|
|
|
|
UniqueChars readLineBuf;
|
|
size_t readLineBufPos;
|
|
|
|
static const uint32_t SpsProfilingMaxStackSize = 1000;
|
|
ProfileEntry spsProfilingStack[SpsProfilingMaxStackSize];
|
|
uint32_t spsProfilingStackSize;
|
|
|
|
OffThreadState offThreadState;
|
|
};
|
|
|
|
struct MOZ_STACK_CLASS EnvironmentPreparer : public js::ScriptEnvironmentPreparer {
|
|
JSContext* cx;
|
|
explicit EnvironmentPreparer(JSContext* cx)
|
|
: cx(cx)
|
|
{
|
|
js::SetScriptEnvironmentPreparer(cx, this);
|
|
}
|
|
void invoke(JS::HandleObject scope, Closure& closure) override;
|
|
};
|
|
|
|
// Shell state set once at startup.
|
|
static bool enableCodeCoverage = false;
|
|
static bool enableDisassemblyDumps = false;
|
|
static bool offthreadCompilation = false;
|
|
static bool enableBaseline = false;
|
|
static bool enableIon = false;
|
|
static bool enableAsmJS = false;
|
|
static bool enableWasm = false;
|
|
static bool enableNativeRegExp = false;
|
|
static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
|
|
static bool enableWasmAlwaysBaseline = false;
|
|
static bool enableArrayProtoValues = true;
|
|
static bool printTiming = false;
|
|
static const char* jsCacheDir = nullptr;
|
|
static const char* jsCacheAsmJSPath = nullptr;
|
|
static RCFile* gErrFile = nullptr;
|
|
static RCFile* gOutFile = nullptr;
|
|
static bool reportWarnings = true;
|
|
static bool compileOnly = false;
|
|
static bool fuzzingSafe = false;
|
|
static bool disableOOMFunctions = false;
|
|
static const char* moduleLoadPath = ".";
|
|
|
|
#ifdef DEBUG
|
|
static bool dumpEntrainedVariables = false;
|
|
static bool OOM_printAllocationCount = false;
|
|
#endif
|
|
|
|
// Shell state this is only accessed on the main thread.
|
|
bool jsCachingEnabled = false;
|
|
mozilla::Atomic<bool> jsCacheOpened(false);
|
|
|
|
static bool
|
|
SetTimeoutValue(JSContext* cx, double t);
|
|
|
|
static void
|
|
KillWatchdog(JSContext* cx);
|
|
|
|
static bool
|
|
ScheduleWatchdog(JSContext* cx, double t);
|
|
|
|
static void
|
|
CancelExecution(JSContext* cx);
|
|
|
|
static JSObject*
|
|
NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
|
|
JSPrincipals* principals);
|
|
|
|
/*
|
|
* A toy principals type for the shell.
|
|
*
|
|
* In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
|
|
* set bits in P are a superset of those in Q. Thus, the principal 0 is
|
|
* subsumed by everything, and the principal ~0 subsumes everything.
|
|
*
|
|
* As a special case, a null pointer as a principal is treated like 0xffff.
|
|
*
|
|
* The 'newGlobal' function takes an option indicating which principal the
|
|
* new global should have; 'evaluate' does for the new code.
|
|
*/
|
|
class ShellPrincipals final : public JSPrincipals {
|
|
uint32_t bits;
|
|
|
|
static uint32_t getBits(JSPrincipals* p) {
|
|
if (!p)
|
|
return 0xffff;
|
|
return static_cast<ShellPrincipals*>(p)->bits;
|
|
}
|
|
|
|
public:
|
|
explicit ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) {
|
|
this->refcount = refcount;
|
|
}
|
|
|
|
bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
|
|
// The shell doesn't have a read principals hook, so it doesn't really
|
|
// matter what we write here, but we have to write something so the
|
|
// fuzzer is happy.
|
|
return JS_WriteUint32Pair(writer, bits, 0);
|
|
}
|
|
|
|
static void destroy(JSPrincipals* principals) {
|
|
MOZ_ASSERT(principals != &fullyTrusted);
|
|
MOZ_ASSERT(principals->refcount == 0);
|
|
js_delete(static_cast<const ShellPrincipals*>(principals));
|
|
}
|
|
|
|
static bool subsumes(JSPrincipals* first, JSPrincipals* second) {
|
|
uint32_t firstBits = getBits(first);
|
|
uint32_t secondBits = getBits(second);
|
|
return (firstBits | secondBits) == firstBits;
|
|
}
|
|
|
|
static JSSecurityCallbacks securityCallbacks;
|
|
|
|
// Fully-trusted principals singleton.
|
|
static ShellPrincipals fullyTrusted;
|
|
};
|
|
|
|
JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
|
|
nullptr, // contentSecurityPolicyAllows
|
|
subsumes
|
|
};
|
|
|
|
// The fully-trusted principal subsumes all other principals.
|
|
ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1);
|
|
|
|
#ifdef EDITLINE
|
|
extern "C" {
|
|
extern JS_EXPORT_API(char*) readline(const char* prompt);
|
|
extern JS_EXPORT_API(void) add_history(char* line);
|
|
} // extern "C"
|
|
#endif
|
|
|
|
ShellContext::ShellContext(JSContext* cx)
|
|
: isWorker(false),
|
|
timeoutInterval(-1.0),
|
|
startTime(PRMJ_Now()),
|
|
serviceInterrupt(false),
|
|
haveInterruptFunc(false),
|
|
interruptFunc(cx, NullValue()),
|
|
lastWarningEnabled(false),
|
|
lastWarning(cx, NullValue()),
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
promiseRejectionTrackerCallback(cx, NullValue()),
|
|
asyncTasks(mutexid::ShellAsyncTasks, cx),
|
|
drainingJobQueue(false),
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
watchdogLock(mutexid::ShellContextWatchdog),
|
|
exitCode(0),
|
|
quitting(false),
|
|
readLineBufPos(0),
|
|
spsProfilingStackSize(0)
|
|
{}
|
|
|
|
static ShellContext*
|
|
GetShellContext(JSContext* cx)
|
|
{
|
|
ShellContext* sc = static_cast<ShellContext*>(JS_GetContextPrivate(cx));
|
|
MOZ_ASSERT(sc);
|
|
return sc;
|
|
}
|
|
|
|
static char*
|
|
GetLine(FILE* file, const char * prompt)
|
|
{
|
|
#ifdef EDITLINE
|
|
/*
|
|
* Use readline only if file is stdin, because there's no way to specify
|
|
* another handle. Are other filehandles interactive?
|
|
*/
|
|
if (file == stdin) {
|
|
char* linep = readline(prompt);
|
|
/*
|
|
* We set it to zero to avoid complaining about inappropriate ioctl
|
|
* for device in the case of EOF. Looks like errno == 251 if line is
|
|
* finished with EOF and errno == 25 (EINVAL on Mac) if there is
|
|
* nothing left to read.
|
|
*/
|
|
if (errno == 251 || errno == 25 || errno == EINVAL)
|
|
errno = 0;
|
|
if (!linep)
|
|
return nullptr;
|
|
if (linep[0] != '\0')
|
|
add_history(linep);
|
|
return linep;
|
|
}
|
|
#endif
|
|
|
|
size_t len = 0;
|
|
if (*prompt != '\0' && gOutFile->isOpen()) {
|
|
fprintf(gOutFile->fp, "%s", prompt);
|
|
fflush(gOutFile->fp);
|
|
}
|
|
|
|
size_t size = 80;
|
|
char* buffer = static_cast<char*>(malloc(size));
|
|
if (!buffer)
|
|
return nullptr;
|
|
|
|
char* current = buffer;
|
|
do {
|
|
while (true) {
|
|
if (fgets(current, size - len, file))
|
|
break;
|
|
if (errno != EINTR) {
|
|
free(buffer);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
len += strlen(current);
|
|
char* t = buffer + len - 1;
|
|
if (*t == '\n') {
|
|
/* Line was read. We remove '\n' and exit. */
|
|
*t = '\0';
|
|
return buffer;
|
|
}
|
|
|
|
if (len + 1 == size) {
|
|
size = size * 2;
|
|
char* tmp = static_cast<char*>(realloc(buffer, size));
|
|
if (!tmp) {
|
|
free(buffer);
|
|
return nullptr;
|
|
}
|
|
buffer = tmp;
|
|
}
|
|
current = buffer + len;
|
|
} while (true);
|
|
return nullptr;
|
|
}
|
|
|
|
static bool
|
|
ShellInterruptCallback(JSContext* cx)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
if (!sc->serviceInterrupt)
|
|
return true;
|
|
|
|
// Reset serviceInterrupt. CancelExecution or InterruptIf will set it to
|
|
// true to distinguish watchdog or user triggered interrupts.
|
|
// Do this first to prevent other interrupts that may occur while the
|
|
// user-supplied callback is executing from re-entering the handler.
|
|
sc->serviceInterrupt = false;
|
|
|
|
bool result;
|
|
if (sc->haveInterruptFunc) {
|
|
bool wasAlreadyThrowing = cx->isExceptionPending();
|
|
JS::AutoSaveExceptionState savedExc(cx);
|
|
JSAutoCompartment ac(cx, &sc->interruptFunc.toObject());
|
|
RootedValue rval(cx);
|
|
|
|
// Report any exceptions thrown by the JS interrupt callback, but do
|
|
// *not* keep it on the cx. The interrupt handler is invoked at points
|
|
// that are not expected to throw catchable exceptions, like at
|
|
// JSOP_RETRVAL.
|
|
//
|
|
// If the interrupted JS code was already throwing, any exceptions
|
|
// thrown by the interrupt handler are silently swallowed.
|
|
{
|
|
Maybe<AutoReportException> are;
|
|
if (!wasAlreadyThrowing)
|
|
are.emplace(cx);
|
|
result = JS_CallFunctionValue(cx, nullptr, sc->interruptFunc,
|
|
JS::HandleValueArray::empty(), &rval);
|
|
}
|
|
savedExc.restore();
|
|
|
|
if (rval.isBoolean())
|
|
result = rval.toBoolean();
|
|
else
|
|
result = false;
|
|
} else {
|
|
result = false;
|
|
}
|
|
|
|
if (!result && sc->exitCode == 0)
|
|
sc->exitCode = EXITCODE_TIMEOUT;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Some UTF-8 files, notably those written using Notepad, have a Unicode
|
|
* Byte-Order-Mark (BOM) as their first character. This is useless (byte-order
|
|
* is meaningless for UTF-8) but causes a syntax error unless we skip it.
|
|
*/
|
|
static void
|
|
SkipUTF8BOM(FILE* file)
|
|
{
|
|
int ch1 = fgetc(file);
|
|
int ch2 = fgetc(file);
|
|
int ch3 = fgetc(file);
|
|
|
|
// Skip the BOM
|
|
if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF)
|
|
return;
|
|
|
|
// No BOM - revert
|
|
if (ch3 != EOF)
|
|
ungetc(ch3, file);
|
|
if (ch2 != EOF)
|
|
ungetc(ch2, file);
|
|
if (ch1 != EOF)
|
|
ungetc(ch1, file);
|
|
}
|
|
|
|
void
|
|
EnvironmentPreparer::invoke(HandleObject scope, Closure& closure)
|
|
{
|
|
MOZ_ASSERT(!JS_IsExceptionPending(cx));
|
|
|
|
AutoCompartment ac(cx, scope);
|
|
AutoReportException are(cx);
|
|
if (!closure(cx))
|
|
return;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
|
|
{
|
|
SkipUTF8BOM(file);
|
|
|
|
// To support the UNIX #! shell hack, gobble the first line if it starts
|
|
// with '#'.
|
|
int ch = fgetc(file);
|
|
if (ch == '#') {
|
|
while ((ch = fgetc(file)) != EOF) {
|
|
if (ch == '\n' || ch == '\r')
|
|
break;
|
|
}
|
|
}
|
|
ungetc(ch, file);
|
|
|
|
int64_t t1 = PRMJ_Now();
|
|
RootedScript script(cx);
|
|
|
|
{
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("js shell file")
|
|
.setUTF8(true)
|
|
.setFileAndLine(filename, 1)
|
|
.setIsRunOnce(true)
|
|
.setNoScriptRval(true);
|
|
|
|
if (!JS::Compile(cx, options, file, &script))
|
|
return false;
|
|
MOZ_ASSERT(script);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (dumpEntrainedVariables)
|
|
AnalyzeEntrainedVariables(cx, script);
|
|
#endif
|
|
if (!compileOnly) {
|
|
if (!JS_ExecuteScript(cx, script))
|
|
return false;
|
|
int64_t t2 = PRMJ_Now() - t1;
|
|
if (printTiming)
|
|
printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
InitModuleLoader(JSContext* cx)
|
|
{
|
|
// Decompress and evaluate the embedded module loader source to initialize
|
|
// the module loader for the current compartment.
|
|
|
|
uint32_t srcLen = moduleloader::GetRawScriptsSize();
|
|
ScopedJSFreePtr<char> src(cx->pod_malloc<char>(srcLen));
|
|
if (!src || !DecompressString(moduleloader::compressedSources, moduleloader::GetCompressedSize(),
|
|
reinterpret_cast<unsigned char*>(src.get()), srcLen))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("shell module loader");
|
|
options.setFileAndLine("shell/ModuleLoader.js", 1);
|
|
options.setSelfHostingMode(false);
|
|
options.setCanLazilyParse(false);
|
|
options.setVersion(JSVERSION_LATEST);
|
|
options.werrorOption = true;
|
|
options.strictOption = true;
|
|
|
|
RootedValue rv(cx);
|
|
return Evaluate(cx, options, src, srcLen, &rv);
|
|
}
|
|
|
|
static bool
|
|
GetLoaderObject(JSContext* cx, MutableHandleObject resultOut)
|
|
{
|
|
// Look up the |Reflect.Loader| object that has been defined by the module
|
|
// loader.
|
|
|
|
RootedObject object(cx, cx->global());
|
|
RootedValue value(cx);
|
|
if (!JS_GetProperty(cx, object, "Reflect", &value) || !value.isObject())
|
|
return false;
|
|
|
|
object = &value.toObject();
|
|
if (!JS_GetProperty(cx, object, "Loader", &value) || !value.isObject())
|
|
return false;
|
|
|
|
resultOut.set(&value.toObject());
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GetImportMethod(JSContext* cx, HandleObject loader, MutableHandleFunction resultOut)
|
|
{
|
|
// Look up the module loader's |import| method.
|
|
|
|
RootedValue value(cx);
|
|
if (!JS_GetProperty(cx, loader, "import", &value) || !value.isObject())
|
|
return false;
|
|
|
|
RootedObject object(cx, &value.toObject());
|
|
if (!object->is<JSFunction>())
|
|
return false;
|
|
|
|
resultOut.set(&object->as<JSFunction>());
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
|
|
{
|
|
// Execute a module by calling |Reflect.Loader.import(filename)|.
|
|
|
|
RootedObject loaderObj(cx);
|
|
MOZ_ALWAYS_TRUE(GetLoaderObject(cx, &loaderObj));
|
|
|
|
RootedFunction importFun(cx);
|
|
MOZ_ALWAYS_TRUE(GetImportMethod(cx, loaderObj, &importFun));
|
|
|
|
JS::AutoValueArray<2> args(cx);
|
|
args[0].setString(JS_NewStringCopyZ(cx, filename));
|
|
args[1].setUndefined();
|
|
|
|
RootedValue value(cx);
|
|
return JS_CallFunction(cx, loaderObj, importFun, args, &value);
|
|
}
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
static JSObject*
|
|
ShellGetIncumbentGlobalCallback(JSContext* cx)
|
|
{
|
|
return JS::CurrentGlobalOrNull(cx);
|
|
}
|
|
|
|
static bool
|
|
ShellEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job, JS::HandleObject allocationSite,
|
|
JS::HandleObject incumbentGlobal, void* data)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
MOZ_ASSERT(job);
|
|
return sc->jobQueue.append(job);
|
|
}
|
|
|
|
static bool
|
|
ShellStartAsyncTaskCallback(JSContext* cx, JS::AsyncTask* task)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
task->user = sc;
|
|
|
|
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
|
asyncTasks->outstanding++;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ShellFinishAsyncTaskCallback(JS::AsyncTask* task)
|
|
{
|
|
ShellContext* sc = (ShellContext*)task->user;
|
|
|
|
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
|
MOZ_ASSERT(asyncTasks->outstanding > 0);
|
|
asyncTasks->outstanding--;
|
|
return asyncTasks->finished.append(task);
|
|
}
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
static bool
|
|
DrainJobQueue(JSContext* cx)
|
|
{
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
ShellContext* sc = GetShellContext(cx);
|
|
if (sc->quitting || sc->drainingJobQueue)
|
|
return true;
|
|
|
|
// Wait for any outstanding async tasks to finish so that the
|
|
// finishedAsyncTasks list is fixed.
|
|
while (true) {
|
|
AutoLockHelperThreadState lock;
|
|
if (!sc->asyncTasks.lock()->outstanding)
|
|
break;
|
|
HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
|
|
}
|
|
|
|
// Lock the whole time while copying back the asyncTasks finished queue so
|
|
// that any new tasks created during finish() cannot racily join the job
|
|
// queue. Call finish() only thereafter, to avoid a circular mutex
|
|
// dependency (see also bug 1297901).
|
|
Vector<JS::AsyncTask*> finished(cx);
|
|
{
|
|
ExclusiveData<ShellAsyncTasks>::Guard asyncTasks = sc->asyncTasks.lock();
|
|
finished = Move(asyncTasks->finished);
|
|
asyncTasks->finished.clear();
|
|
}
|
|
|
|
for (JS::AsyncTask* task : finished)
|
|
task->finish(cx);
|
|
|
|
// It doesn't make sense for job queue draining to be reentrant. At the
|
|
// same time we don't want to assert against it, because that'd make
|
|
// drainJobQueue unsafe for fuzzers. We do want fuzzers to test this, so
|
|
// we simply ignore nested calls of drainJobQueue.
|
|
sc->drainingJobQueue = true;
|
|
|
|
RootedObject job(cx);
|
|
JS::HandleValueArray args(JS::HandleValueArray::empty());
|
|
RootedValue rval(cx);
|
|
// Execute jobs in a loop until we've reached the end of the queue.
|
|
// Since executing a job can trigger enqueuing of additional jobs,
|
|
// it's crucial to re-check the queue length during each iteration.
|
|
for (size_t i = 0; i < sc->jobQueue.length(); i++) {
|
|
job = sc->jobQueue[i];
|
|
AutoCompartment ac(cx, job);
|
|
{
|
|
AutoReportException are(cx);
|
|
JS::Call(cx, UndefinedHandleValue, job, args, &rval);
|
|
}
|
|
sc->jobQueue[i].set(nullptr);
|
|
}
|
|
sc->jobQueue.clear();
|
|
sc->drainingJobQueue = false;
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DrainJobQueue(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!DrainJobQueue(cx))
|
|
return false;
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
static void
|
|
ForwardingPromiseRejectionTrackerCallback(JSContext* cx, JS::HandleObject promise,
|
|
PromiseRejectionHandlingState state, void* data)
|
|
{
|
|
RootedValue callback(cx, GetShellContext(cx)->promiseRejectionTrackerCallback);
|
|
if (callback.isNull()) {
|
|
return;
|
|
}
|
|
|
|
AutoCompartment ac(cx, &callback.toObject());
|
|
|
|
FixedInvokeArgs<2> args(cx);
|
|
args[0].setObject(*promise);
|
|
args[1].setInt32(static_cast<int32_t>(state));
|
|
|
|
if (!JS_WrapValue(cx, args[0]))
|
|
return;
|
|
|
|
RootedValue rval(cx);
|
|
if (!Call(cx, callback, UndefinedHandleValue, args, &rval))
|
|
JS_ClearPendingException(cx);
|
|
}
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
static bool
|
|
SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
if (!IsCallable(args.get(0))) {
|
|
JS_ReportErrorASCII(cx,
|
|
"setPromiseRejectionTrackerCallback expects a function as its sole "
|
|
"argument");
|
|
return false;
|
|
}
|
|
|
|
GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];
|
|
JS::SetPromiseRejectionTrackerCallback(cx, ForwardingPromiseRejectionTrackerCallback);
|
|
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
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("getDisplayNames", "Intl_getDisplayNames", 2, 0),
|
|
JS_FS_END
|
|
};
|
|
|
|
if (!JS_DefineFunctions(cx, intl, funcs))
|
|
return false;
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
|
|
int lineno, bool compileOnly)
|
|
{
|
|
// Eval.
|
|
JS::CompileOptions options(cx);
|
|
options.setIntroductionType("js shell interactive")
|
|
.setUTF8(true)
|
|
.setIsRunOnce(true)
|
|
.setFileAndLine("typein", lineno);
|
|
RootedScript script(cx);
|
|
if (!JS::Compile(cx, options, bytes, length, &script))
|
|
return false;
|
|
if (compileOnly)
|
|
return true;
|
|
RootedValue result(cx);
|
|
if (!JS_ExecuteScript(cx, script, &result))
|
|
return false;
|
|
|
|
if (!result.isUndefined() && gOutFile->isOpen()) {
|
|
// Print.
|
|
RootedString str(cx);
|
|
str = JS_ValueToSource(cx, result);
|
|
if (!str)
|
|
return false;
|
|
|
|
char* utf8chars = JS_EncodeStringToUTF8(cx, str);
|
|
if (!utf8chars)
|
|
return false;
|
|
fprintf(gOutFile->fp, "%s\n", utf8chars);
|
|
JS_free(cx, utf8chars);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
int lineno = 1;
|
|
bool hitEOF = false;
|
|
|
|
do {
|
|
/*
|
|
* Accumulate lines until we get a 'compilable unit' - one that either
|
|
* generates an error (before running out of source) or that compiles
|
|
* cleanly. This should be whenever we get a complete statement that
|
|
* coincides with the end of a line.
|
|
*/
|
|
int startline = lineno;
|
|
typedef Vector<char, 32> CharBuffer;
|
|
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
|
|
CharBuffer buffer(cx);
|
|
do {
|
|
ScheduleWatchdog(cx, -1);
|
|
sc->serviceInterrupt = false;
|
|
errno = 0;
|
|
|
|
char* line = GetLine(in, startline == lineno ? "js> " : "");
|
|
if (!line) {
|
|
if (errno) {
|
|
/*
|
|
* Use Latin1 variant here because strerror(errno)'s
|
|
* encoding depends on the user's C locale.
|
|
*/
|
|
JS_ReportErrorLatin1(cx, "%s", strerror(errno));
|
|
return false;
|
|
}
|
|
hitEOF = true;
|
|
break;
|
|
}
|
|
|
|
if (!buffer.append(line, strlen(line)) || !buffer.append('\n'))
|
|
return false;
|
|
|
|
lineno++;
|
|
if (!ScheduleWatchdog(cx, sc->timeoutInterval)) {
|
|
hitEOF = true;
|
|
break;
|
|
}
|
|
} while (!JS_BufferIsCompilableUnit(cx, cx->global(), buffer.begin(), buffer.length()));
|
|
|
|
if (hitEOF && buffer.empty())
|
|
break;
|
|
|
|
{
|
|
// Report exceptions but keep going.
|
|
AutoReportException are(cx);
|
|
(void) EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly);
|
|
}
|
|
|
|
// If a let or const fail to initialize they will remain in an unusable
|
|
// without further intervention. This call cleans up the global scope,
|
|
// setting uninitialized lexicals to undefined so that they may still
|
|
// be used. This behavior is _only_ acceptable in the context of the repl.
|
|
if (JS::ForceLexicalInitialization(cx, globalLexical) && gErrFile->isOpen()) {
|
|
fputs("Warning: According to the standard, after the above exception,\n"
|
|
"Warning: the global bindings should be permanently uninitialized.\n"
|
|
"Warning: We have non-standard-ly initialized them to `undefined`"
|
|
"for you.\nWarning: This nicety only happens in the JS shell.\n",
|
|
stderr);
|
|
}
|
|
|
|
DrainJobQueue(cx);
|
|
|
|
} while (!hitEOF && !sc->quitting);
|
|
|
|
if (gOutFile->isOpen())
|
|
fprintf(gOutFile->fp, "\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
enum FileKind
|
|
{
|
|
FileScript,
|
|
FileModule
|
|
};
|
|
|
|
static void
|
|
ReportCantOpenErrorUnknownEncoding(JSContext* cx, const char* filename)
|
|
{
|
|
/*
|
|
* Filenames are in some random system encoding. *Probably* it's UTF-8,
|
|
* but no guarantees.
|
|
*
|
|
* strerror(errno)'s encoding, in contrast, depends on the user's C locale.
|
|
*
|
|
* Latin-1 is possibly wrong for both of these -- but at least if it's
|
|
* wrong it'll produce mojibake *safely*. Run with Latin-1 til someone
|
|
* complains.
|
|
*/
|
|
JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN,
|
|
filename, strerror(errno));
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = FileScript)
|
|
{
|
|
FILE* file;
|
|
if (forceTTY || !filename || strcmp(filename, "-") == 0) {
|
|
file = stdin;
|
|
} else {
|
|
file = fopen(filename, "r");
|
|
if (!file) {
|
|
ReportCantOpenErrorUnknownEncoding(cx, filename);
|
|
return false;
|
|
}
|
|
}
|
|
AutoCloseFile autoClose(file);
|
|
|
|
if (!forceTTY && !isatty(fileno(file))) {
|
|
// It's not interactive - just execute it.
|
|
if (kind == FileScript) {
|
|
if (!RunFile(cx, filename, file, compileOnly))
|
|
return false;
|
|
} else {
|
|
if (!RunModule(cx, filename, file, compileOnly))
|
|
return false;
|
|
}
|
|
} else {
|
|
// It's an interactive filehandle; drop into read-eval-print loop.
|
|
MOZ_ASSERT(kind == FileScript);
|
|
if (!ReadEvalPrintLoop(cx, file, compileOnly))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Version(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
JSVersion origVersion = JS_GetVersion(cx);
|
|
if (args.length() == 0 || args[0].isUndefined()) {
|
|
/* Get version. */
|
|
args.rval().setInt32(origVersion);
|
|
} else {
|
|
/* Set version. */
|
|
int32_t v = -1;
|
|
if (args[0].isInt32()) {
|
|
v = args[0].toInt32();
|
|
} else if (args[0].isDouble()) {
|
|
double fv = args[0].toDouble();
|
|
int32_t fvi;
|
|
if (NumberEqualsInt32(fv, &fvi))
|
|
v = fvi;
|
|
}
|
|
if (v < 0 || v > JSVERSION_LATEST) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"version");
|
|
return false;
|
|
}
|
|
JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
|
|
args.rval().setInt32(origVersion);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
# define GET_FD_FROM_FILE(a) int(_get_osfhandle(fileno(a)))
|
|
#else
|
|
# define GET_FD_FROM_FILE(a) fileno(a)
|
|
#endif
|
|
|
|
static bool
|
|
CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() < 1 || args.length() > 3) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
|
|
args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
|
|
"createMappedArrayBuffer");
|
|
return false;
|
|
}
|
|
|
|
RootedString rawFilenameStr(cx, JS::ToString(cx, args[0]));
|
|
if (!rawFilenameStr)
|
|
return false;
|
|
// It's a little bizarre to resolve relative to the script, but for testing
|
|
// I need a file at a known location, and the only good way I know of to do
|
|
// that right now is to include it in the repo alongside the test script.
|
|
// Bug 944164 would introduce an alternative.
|
|
JSString* filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative);
|
|
if (!filenameStr)
|
|
return false;
|
|
JSAutoByteString filename(cx, filenameStr);
|
|
if (!filename)
|
|
return false;
|
|
|
|
uint32_t offset = 0;
|
|
if (args.length() >= 2) {
|
|
if (!JS::ToUint32(cx, args[1], &offset))
|
|
return false;
|
|
}
|
|
|
|
bool sizeGiven = false;
|
|
uint32_t size;
|
|
if (args.length() >= 3) {
|
|
if (!JS::ToUint32(cx, args[2], &size))
|
|
return false;
|
|
sizeGiven = true;
|
|
if (size == 0) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FILE* file = fopen(filename.ptr(), "r");
|
|
if (!file) {
|
|
ReportCantOpenErrorUnknownEncoding(cx, filename.ptr());
|
|
return false;
|
|
}
|
|
AutoCloseFile autoClose(file);
|
|
|
|
if (!sizeGiven) {
|
|
struct stat st;
|
|
if (fstat(fileno(file), &st) < 0) {
|
|
JS_ReportErrorASCII(cx, "Unable to stat file");
|
|
return false;
|
|
}
|
|
if (off_t(offset) >= st.st_size) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
|
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
|
|
return false;
|
|
}
|
|
size = st.st_size - offset;
|
|
}
|
|
|
|
void* contents = JS_CreateMappedArrayBufferContents(GET_FD_FROM_FILE(file), offset, size);
|
|
if (!contents) {
|
|
JS_ReportErrorASCII(cx, "failed to allocate mapped array buffer contents (possibly due to bad alignment)");
|
|
return false;
|
|
}
|
|
|
|
RootedObject obj(cx, JS_NewMappedArrayBufferWithContents(cx, size, contents));
|
|
if (!obj)
|
|
return false;
|
|
|
|
args.rval().setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
#undef GET_FD_FROM_FILE
|
|
|
|
static bool
|
|
AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 3) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
|
|
args.length() < 3 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
|
|
"addPromiseReactions");
|
|
return false;
|
|
}
|
|
|
|
RootedObject promise(cx);
|
|
if (args[0].isObject())
|
|
promise = &args[0].toObject();
|
|
|
|
if (!promise || !JS::IsPromiseObject(promise)) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"addPromiseReactions");
|
|
return false;
|
|
}
|
|
|
|
RootedObject onResolve(cx);
|
|
if (args[1].isObject())
|
|
onResolve = &args[1].toObject();
|
|
|
|
RootedObject onReject(cx);
|
|
if (args[2].isObject())
|
|
onReject = &args[2].toObject();
|
|
|
|
if (!onResolve || !onResolve->is<JSFunction>() || !onReject || !onReject->is<JSFunction>()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"addPromiseReactions");
|
|
return false;
|
|
}
|
|
|
|
return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
|
|
}
|
|
|
|
static bool
|
|
Options(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx);
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
RootedString str(cx, JS::ToString(cx, args[i]));
|
|
if (!str)
|
|
return false;
|
|
args[i].setString(str);
|
|
|
|
JSAutoByteString opt;
|
|
if (!opt.encodeUtf8(cx, str))
|
|
return false;
|
|
|
|
if (strcmp(opt.ptr(), "strict") == 0)
|
|
JS::ContextOptionsRef(cx).toggleExtraWarnings();
|
|
else if (strcmp(opt.ptr(), "werror") == 0)
|
|
JS::ContextOptionsRef(cx).toggleWerror();
|
|
else if (strcmp(opt.ptr(), "throw_on_asmjs_validation_failure") == 0)
|
|
JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
|
|
else if (strcmp(opt.ptr(), "strict_mode") == 0)
|
|
JS::ContextOptionsRef(cx).toggleStrictMode();
|
|
else {
|
|
JS_ReportErrorUTF8(cx,
|
|
"unknown option name '%s'."
|
|
" The valid names are strict,"
|
|
" werror, and strict_mode.",
|
|
opt.ptr());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
char* names = strdup("");
|
|
bool found = false;
|
|
if (names && oldContextOptions.extraWarnings()) {
|
|
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict");
|
|
found = true;
|
|
}
|
|
if (names && oldContextOptions.werror()) {
|
|
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror");
|
|
found = true;
|
|
}
|
|
if (names && oldContextOptions.throwOnAsmJSValidationFailure()) {
|
|
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "throw_on_asmjs_validation_failure");
|
|
found = true;
|
|
}
|
|
if (names && oldContextOptions.strictMode()) {
|
|
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict_mode");
|
|
found = true;
|
|
}
|
|
if (!names) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
JSString* str = JS_NewStringCopyZ(cx, names);
|
|
free(names);
|
|
if (!str)
|
|
return false;
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
LoadScript(JSContext* cx, unsigned argc, Value* vp, bool scriptRelative)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedString str(cx);
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
str = JS::ToString(cx, args[i]);
|
|
if (!str) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"load");
|
|
return false;
|
|
}
|
|
str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative);
|
|
if (!str) {
|
|
JS_ReportErrorASCII(cx, "unable to resolve path");
|
|
return false;
|
|
}
|
|
JSAutoByteString filename(cx, str);
|
|
if (!filename)
|
|
return false;
|
|
errno = 0;
|
|
CompileOptions opts(cx);
|
|
opts.setIntroductionType("js shell load")
|
|
.setUTF8(true)
|
|
.setIsRunOnce(true)
|
|
.setNoScriptRval(true);
|
|
RootedScript script(cx);
|
|
RootedValue unused(cx);
|
|
if ((compileOnly && !Compile(cx, opts, filename.ptr(), &script)) ||
|
|
!Evaluate(cx, opts, filename.ptr(), &unused))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Load(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
return LoadScript(cx, argc, vp, false);
|
|
}
|
|
|
|
static bool
|
|
LoadScriptRelativeToScript(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
return LoadScript(cx, argc, vp, true);
|
|
}
|
|
|
|
// Populate |options| with the options given by |opts|'s properties. If we
|
|
// need to convert a filename to a C string, let fileNameBytes own the
|
|
// bytes.
|
|
static bool
|
|
ParseCompileOptions(JSContext* cx, CompileOptions& options, HandleObject opts,
|
|
JSAutoByteString& fileNameBytes)
|
|
{
|
|
RootedValue v(cx);
|
|
RootedString s(cx);
|
|
|
|
if (!JS_GetProperty(cx, opts, "isRunOnce", &v))
|
|
return false;
|
|
if (!v.isUndefined())
|
|
options.setIsRunOnce(ToBoolean(v));
|
|
|
|
if (!JS_GetProperty(cx, opts, "noScriptRval", &v))
|
|
return false;
|
|
if (!v.isUndefined())
|
|
options.setNoScriptRval(ToBoolean(v));
|
|
|
|
if (!JS_GetProperty(cx, opts, "fileName", &v))
|
|
return false;
|
|
if (v.isNull()) {
|
|
options.setFile(nullptr);
|
|
} else if (!v.isUndefined()) {
|
|
s = ToString(cx, v);
|
|
if (!s)
|
|
return false;
|
|
char* fileName = fileNameBytes.encodeLatin1(cx, s);
|
|
if (!fileName)
|
|
return false;
|
|
options.setFile(fileName);
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "element", &v))
|
|
return false;
|
|
if (v.isObject())
|
|
options.setElement(&v.toObject());
|
|
|
|
if (!JS_GetProperty(cx, opts, "elementAttributeName", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
s = ToString(cx, v);
|
|
if (!s)
|
|
return false;
|
|
options.setElementAttributeName(s);
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "lineNumber", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
uint32_t u;
|
|
if (!ToUint32(cx, v, &u))
|
|
return false;
|
|
options.setLine(u);
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "columnNumber", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
int32_t c;
|
|
if (!ToInt32(cx, v, &c))
|
|
return false;
|
|
options.setColumn(c);
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "sourceIsLazy", &v))
|
|
return false;
|
|
if (v.isBoolean())
|
|
options.setSourceIsLazy(v.toBoolean());
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
my_LargeAllocFailCallback(void* data)
|
|
{
|
|
JSContext* cx = (JSContext*)data;
|
|
JSRuntime* rt = cx->runtime();
|
|
|
|
if (!cx->isJSContext())
|
|
return;
|
|
|
|
MOZ_ASSERT(!rt->isHeapBusy());
|
|
|
|
JS::PrepareForFullGC(cx);
|
|
AutoKeepAtoms keepAtoms(cx->perThreadData);
|
|
rt->gc.gc(GC_NORMAL, JS::gcreason::SHARED_MEMORY_LIMIT);
|
|
}
|
|
|
|
static const uint32_t CacheEntry_SOURCE = 0;
|
|
static const uint32_t CacheEntry_BYTECODE = 1;
|
|
|
|
static const JSClass CacheEntry_class = {
|
|
"CacheEntryObject", JSCLASS_HAS_RESERVED_SLOTS(2)
|
|
};
|
|
|
|
static bool
|
|
CacheEntry(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1 || !args[0].isString()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"CacheEntry");
|
|
return false;
|
|
}
|
|
|
|
RootedObject obj(cx, JS_NewObject(cx, &CacheEntry_class));
|
|
if (!obj)
|
|
return false;
|
|
|
|
SetReservedSlot(obj, CacheEntry_SOURCE, args[0]);
|
|
SetReservedSlot(obj, CacheEntry_BYTECODE, UndefinedValue());
|
|
args.rval().setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
CacheEntry_isCacheEntry(JSObject* cache)
|
|
{
|
|
return JS_GetClass(cache) == &CacheEntry_class;
|
|
}
|
|
|
|
static JSString*
|
|
CacheEntry_getSource(HandleObject cache)
|
|
{
|
|
MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
|
|
Value v = JS_GetReservedSlot(cache, CacheEntry_SOURCE);
|
|
if (!v.isString())
|
|
return nullptr;
|
|
|
|
return v.toString();
|
|
}
|
|
|
|
static uint8_t*
|
|
CacheEntry_getBytecode(HandleObject cache, uint32_t* length)
|
|
{
|
|
MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
|
|
Value v = JS_GetReservedSlot(cache, CacheEntry_BYTECODE);
|
|
if (!v.isObject() || !v.toObject().is<ArrayBufferObject>())
|
|
return nullptr;
|
|
|
|
ArrayBufferObject* arrayBuffer = &v.toObject().as<ArrayBufferObject>();
|
|
*length = arrayBuffer->byteLength();
|
|
return arrayBuffer->dataPointer();
|
|
}
|
|
|
|
static bool
|
|
CacheEntry_setBytecode(JSContext* cx, HandleObject cache, uint8_t* buffer, uint32_t length)
|
|
{
|
|
MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
|
|
|
|
ArrayBufferObject::BufferContents contents =
|
|
ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(buffer);
|
|
Rooted<ArrayBufferObject*> arrayBuffer(cx, ArrayBufferObject::create(cx, length, contents));
|
|
if (!arrayBuffer)
|
|
return false;
|
|
|
|
SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ConvertTranscodeResultToJSException(JSContext* cx, JS::TranscodeResult rv)
|
|
{
|
|
switch (rv) {
|
|
case JS::TranscodeResult_Ok:
|
|
return true;
|
|
|
|
default:
|
|
MOZ_FALLTHROUGH;
|
|
case JS::TranscodeResult_Failure:
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
JS_ReportErrorASCII(cx, "generic warning");
|
|
return false;
|
|
case JS::TranscodeResult_Failure_BadBuildId:
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
JS_ReportErrorASCII(cx, "the build-id does not match");
|
|
return false;
|
|
case JS::TranscodeResult_Failure_RunOnceNotSupported:
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
JS_ReportErrorASCII(cx, "run-once script are not supported by XDR");
|
|
return false;
|
|
case JS::TranscodeResult_Failure_AsmJSNotSupported:
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
JS_ReportErrorASCII(cx, "Asm.js is not supported by XDR");
|
|
return false;
|
|
case JS::TranscodeResult_Failure_BadDecode:
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
JS_ReportErrorASCII(cx, "XDR data corruption");
|
|
return false;
|
|
|
|
case JS::TranscodeResult_Throw:
|
|
MOZ_ASSERT(cx->isExceptionPending());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
Evaluate(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() < 1 || args.length() > 2) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
|
|
args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
|
|
"evaluate");
|
|
return false;
|
|
}
|
|
|
|
RootedString code(cx, nullptr);
|
|
RootedObject cacheEntry(cx, nullptr);
|
|
if (args[0].isString()) {
|
|
code = args[0].toString();
|
|
} else if (args[0].isObject() && CacheEntry_isCacheEntry(&args[0].toObject())) {
|
|
cacheEntry = &args[0].toObject();
|
|
code = CacheEntry_getSource(cacheEntry);
|
|
}
|
|
|
|
if (!code || (args.length() == 2 && args[1].isPrimitive())) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
|
|
return false;
|
|
}
|
|
|
|
CompileOptions options(cx);
|
|
JSAutoByteString fileNameBytes;
|
|
RootedString displayURL(cx);
|
|
RootedString sourceMapURL(cx);
|
|
RootedObject global(cx, nullptr);
|
|
bool catchTermination = false;
|
|
bool loadBytecode = false;
|
|
bool saveBytecode = false;
|
|
bool assertEqBytecode = false;
|
|
RootedObject callerGlobal(cx, cx->global());
|
|
|
|
options.setIntroductionType("js shell evaluate")
|
|
.setFileAndLine("@evaluate", 1);
|
|
|
|
global = JS_GetGlobalForObject(cx, &args.callee());
|
|
if (!global)
|
|
return false;
|
|
|
|
if (args.length() == 2) {
|
|
RootedObject opts(cx, &args[1].toObject());
|
|
RootedValue v(cx);
|
|
|
|
if (!ParseCompileOptions(cx, options, opts, fileNameBytes))
|
|
return false;
|
|
|
|
if (!JS_GetProperty(cx, opts, "displayURL", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
displayURL = ToString(cx, v);
|
|
if (!displayURL)
|
|
return false;
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "sourceMapURL", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
sourceMapURL = ToString(cx, v);
|
|
if (!sourceMapURL)
|
|
return false;
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "global", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
if (v.isObject()) {
|
|
global = js::UncheckedUnwrap(&v.toObject());
|
|
if (!global)
|
|
return false;
|
|
}
|
|
if (!global || !(JS_GetClass(global)->flags & JSCLASS_IS_GLOBAL)) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
|
|
"\"global\" passed to evaluate()", "not a global object");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!JS_GetProperty(cx, opts, "catchTermination", &v))
|
|
return false;
|
|
if (!v.isUndefined())
|
|
catchTermination = ToBoolean(v);
|
|
|
|
if (!JS_GetProperty(cx, opts, "loadBytecode", &v))
|
|
return false;
|
|
if (!v.isUndefined())
|
|
loadBytecode = ToBoolean(v);
|
|
|
|
if (!JS_GetProperty(cx, opts, "saveBytecode", &v))
|
|
return false;
|
|
if (!v.isUndefined())
|
|
saveBytecode = ToBoolean(v);
|
|
|
|
if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v))
|
|
return false;
|
|
if (!v.isUndefined())
|
|
assertEqBytecode = ToBoolean(v);
|
|
|
|
// We cannot load or save the bytecode if we have no object where the
|
|
// bytecode cache is stored.
|
|
if (loadBytecode || saveBytecode) {
|
|
if (!cacheEntry) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"evaluate");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
AutoStableStringChars codeChars(cx);
|
|
if (!codeChars.initTwoByte(cx, code))
|
|
return false;
|
|
|
|
JS::TranscodeBuffer loadBuffer;
|
|
JS::TranscodeBuffer saveBuffer;
|
|
|
|
if (loadBytecode) {
|
|
uint32_t loadLength = 0;
|
|
uint8_t* loadData = nullptr;
|
|
loadData = CacheEntry_getBytecode(cacheEntry, &loadLength);
|
|
if (!loadData)
|
|
return false;
|
|
if (!loadBuffer.append(loadData, loadLength)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
JSAutoCompartment ac(cx, global);
|
|
RootedScript script(cx);
|
|
|
|
{
|
|
if (saveBytecode) {
|
|
if (!JS::CompartmentCreationOptionsRef(cx).cloneSingletons()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
|
|
JSSMSG_CACHE_SINGLETON_FAILED);
|
|
return false;
|
|
}
|
|
|
|
// cloneSingletons implies that singletons are used as template objects.
|
|
MOZ_ASSERT(JS::CompartmentBehaviorsRef(cx).getSingletonsAsTemplates());
|
|
}
|
|
|
|
if (loadBytecode) {
|
|
JS::TranscodeResult rv = JS::DecodeScript(cx, loadBuffer, &script);
|
|
if (!ConvertTranscodeResultToJSException(cx, rv))
|
|
return false;
|
|
} else {
|
|
mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
|
|
(void) JS::Compile(cx, options, chars.begin().get(), chars.length(), &script);
|
|
}
|
|
|
|
if (!script)
|
|
return false;
|
|
}
|
|
|
|
if (displayURL && !script->scriptSource()->hasDisplayURL()) {
|
|
JSFlatString* flat = displayURL->ensureFlat(cx);
|
|
if (!flat)
|
|
return false;
|
|
|
|
AutoStableStringChars chars(cx);
|
|
if (!chars.initTwoByte(cx, flat))
|
|
return false;
|
|
|
|
const char16_t* durl = chars.twoByteRange().begin().get();
|
|
if (!script->scriptSource()->setDisplayURL(cx, durl))
|
|
return false;
|
|
}
|
|
if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) {
|
|
JSFlatString* flat = sourceMapURL->ensureFlat(cx);
|
|
if (!flat)
|
|
return false;
|
|
|
|
AutoStableStringChars chars(cx);
|
|
if (!chars.initTwoByte(cx, flat))
|
|
return false;
|
|
|
|
const char16_t* smurl = chars.twoByteRange().begin().get();
|
|
if (!script->scriptSource()->setSourceMapURL(cx, smurl))
|
|
return false;
|
|
}
|
|
if (!JS_ExecuteScript(cx, script, args.rval())) {
|
|
if (catchTermination && !JS_IsExceptionPending(cx)) {
|
|
JSAutoCompartment ac1(cx, callerGlobal);
|
|
JSString* str = JS_NewStringCopyZ(cx, "terminated");
|
|
if (!str)
|
|
return false;
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (saveBytecode) {
|
|
JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script);
|
|
if (!ConvertTranscodeResultToJSException(cx, rv))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (saveBytecode) {
|
|
// If we are both loading and saving, we assert that we are going to
|
|
// replace the current bytecode by the same stream of bytes.
|
|
if (loadBytecode && assertEqBytecode) {
|
|
if (saveBuffer.length() != loadBuffer.length()) {
|
|
char loadLengthStr[16];
|
|
SprintfLiteral(loadLengthStr, "%" PRIuSIZE, loadBuffer.length());
|
|
char saveLengthStr[16];
|
|
SprintfLiteral(saveLengthStr,"%" PRIuSIZE, saveBuffer.length());
|
|
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED,
|
|
loadLengthStr, saveLengthStr);
|
|
return false;
|
|
}
|
|
|
|
if (!PodEqual(loadBuffer.begin(), saveBuffer.begin(), loadBuffer.length())) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
|
|
JSSMSG_CACHE_EQ_CONTENT_FAILED);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
size_t saveLength = saveBuffer.length();
|
|
if (saveLength >= INT32_MAX) {
|
|
JS_ReportErrorASCII(cx, "Cannot save large cache entry content");
|
|
return false;
|
|
}
|
|
uint8_t* saveData = saveBuffer.extractOrCopyRawBuffer();
|
|
if (!CacheEntry_setBytecode(cx, cacheEntry, saveData, saveLength)) {
|
|
js_free(saveData);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return JS_WrapValue(cx, args.rval());
|
|
}
|
|
|
|
JSString*
|
|
js::shell::FileAsString(JSContext* cx, JS::HandleString pathnameStr)
|
|
{
|
|
JSAutoByteString pathname(cx, pathnameStr);
|
|
if (!pathname)
|
|
return nullptr;
|
|
|
|
FILE* file;
|
|
|
|
file = fopen(pathname.ptr(), "rb");
|
|
if (!file) {
|
|
ReportCantOpenErrorUnknownEncoding(cx, pathname.ptr());
|
|
return nullptr;
|
|
}
|
|
|
|
AutoCloseFile autoClose(file);
|
|
|
|
if (fseek(file, 0, SEEK_END) != 0) {
|
|
pathname.clear();
|
|
if (!pathname.encodeUtf8(cx, pathnameStr))
|
|
return nullptr;
|
|
JS_ReportErrorUTF8(cx, "can't seek end of %s", pathname.ptr());
|
|
return nullptr;
|
|
}
|
|
|
|
size_t len = ftell(file);
|
|
if (fseek(file, 0, SEEK_SET) != 0) {
|
|
pathname.clear();
|
|
if (!pathname.encodeUtf8(cx, pathnameStr))
|
|
return nullptr;
|
|
JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.ptr());
|
|
return nullptr;
|
|
}
|
|
|
|
UniqueChars buf(static_cast<char*>(js_malloc(len + 1)));
|
|
if (!buf)
|
|
return nullptr;
|
|
|
|
size_t cc = fread(buf.get(), 1, len, file);
|
|
if (cc != len) {
|
|
if (ptrdiff_t(cc) < 0) {
|
|
ReportCantOpenErrorUnknownEncoding(cx, pathname.ptr());
|
|
} else {
|
|
pathname.clear();
|
|
if (!pathname.encodeUtf8(cx, pathnameStr))
|
|
return nullptr;
|
|
JS_ReportErrorUTF8(cx, "can't read %s: short read", pathname.ptr());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UniqueTwoByteChars ucbuf(
|
|
JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf.get(), len), &len).get()
|
|
);
|
|
if (!ucbuf) {
|
|
pathname.clear();
|
|
if (!pathname.encodeUtf8(cx, pathnameStr))
|
|
return nullptr;
|
|
JS_ReportErrorUTF8(cx, "Invalid UTF-8 in file '%s'", pathname.ptr());
|
|
return nullptr;
|
|
}
|
|
|
|
return JS_NewUCStringCopyN(cx, ucbuf.get(), len);
|
|
}
|
|
|
|
/*
|
|
* Function to run scripts and return compilation + execution time. Semantics
|
|
* are closely modelled after the equivalent function in WebKit, as this is used
|
|
* to produce benchmark timings by SunSpider.
|
|
*/
|
|
static bool
|
|
Run(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "run");
|
|
return false;
|
|
}
|
|
|
|
RootedString str(cx, JS::ToString(cx, args[0]));
|
|
if (!str)
|
|
return false;
|
|
args[0].setString(str);
|
|
|
|
str = FileAsString(cx, str);
|
|
if (!str)
|
|
return false;
|
|
|
|
AutoStableStringChars chars(cx);
|
|
if (!chars.initTwoByte(cx, str))
|
|
return false;
|
|
|
|
const char16_t* ucbuf = chars.twoByteRange().begin().get();
|
|
size_t buflen = str->length();
|
|
|
|
RootedScript script(cx);
|
|
int64_t startClock = PRMJ_Now();
|
|
{
|
|
/* FIXME: This should use UTF-8 (bug 987069). */
|
|
JSAutoByteString filename(cx, str);
|
|
if (!filename)
|
|
return false;
|
|
|
|
JS::CompileOptions options(cx);
|
|
options.setIntroductionType("js shell run")
|
|
.setFileAndLine(filename.ptr(), 1)
|
|
.setIsRunOnce(true)
|
|
.setNoScriptRval(true);
|
|
if (!JS_CompileUCScript(cx, ucbuf, buflen, options, &script))
|
|
return false;
|
|
}
|
|
|
|
if (!JS_ExecuteScript(cx, script))
|
|
return false;
|
|
|
|
int64_t endClock = PRMJ_Now();
|
|
|
|
args.rval().setDouble((endClock - startClock) / double(PRMJ_USEC_PER_MSEC));
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* function readline()
|
|
* Provides a hook for scripts to read a line from stdin.
|
|
*/
|
|
static bool
|
|
ReadLine(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
#define BUFSIZE 256
|
|
FILE* from = stdin;
|
|
size_t buflength = 0;
|
|
size_t bufsize = BUFSIZE;
|
|
char* buf = (char*) JS_malloc(cx, bufsize);
|
|
if (!buf)
|
|
return false;
|
|
|
|
bool sawNewline = false;
|
|
size_t gotlength;
|
|
while ((gotlength = js_fgets(buf + buflength, bufsize - buflength, from)) > 0) {
|
|
buflength += gotlength;
|
|
|
|
/* Are we done? */
|
|
if (buf[buflength - 1] == '\n') {
|
|
buf[buflength - 1] = '\0';
|
|
sawNewline = true;
|
|
break;
|
|
} else if (buflength < bufsize - 1) {
|
|
break;
|
|
}
|
|
|
|
/* Else, grow our buffer for another pass. */
|
|
char* tmp;
|
|
bufsize *= 2;
|
|
if (bufsize > buflength) {
|
|
tmp = static_cast<char*>(JS_realloc(cx, buf, bufsize / 2, bufsize));
|
|
} else {
|
|
JS_ReportOutOfMemory(cx);
|
|
tmp = nullptr;
|
|
}
|
|
|
|
if (!tmp) {
|
|
JS_free(cx, buf);
|
|
return false;
|
|
}
|
|
|
|
buf = tmp;
|
|
}
|
|
|
|
/* Treat the empty string specially. */
|
|
if (buflength == 0) {
|
|
args.rval().set(feof(from) ? NullValue() : JS_GetEmptyStringValue(cx));
|
|
JS_free(cx, buf);
|
|
return true;
|
|
}
|
|
|
|
/* Shrink the buffer to the real size. */
|
|
char* tmp = static_cast<char*>(JS_realloc(cx, buf, bufsize, buflength));
|
|
if (!tmp) {
|
|
JS_free(cx, buf);
|
|
return false;
|
|
}
|
|
|
|
buf = tmp;
|
|
|
|
/*
|
|
* Turn buf into a JSString. Note that buflength includes the trailing null
|
|
* character.
|
|
*/
|
|
JSString* str = JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength);
|
|
JS_free(cx, buf);
|
|
if (!str)
|
|
return false;
|
|
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* function readlineBuf()
|
|
* Provides a hook for scripts to emulate readline() using a string object.
|
|
*/
|
|
static bool
|
|
ReadLineBuf(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
if (!args.length()) {
|
|
if (!sc->readLineBuf) {
|
|
JS_ReportErrorASCII(cx, "No source buffer set. You must initially "
|
|
"call readlineBuf with an argument.");
|
|
return false;
|
|
}
|
|
|
|
char* currentBuf = sc->readLineBuf.get() + sc->readLineBufPos;
|
|
size_t buflen = strlen(currentBuf);
|
|
|
|
if (!buflen) {
|
|
args.rval().setNull();
|
|
return true;
|
|
}
|
|
|
|
size_t len = 0;
|
|
while(len < buflen) {
|
|
if (currentBuf[len] == '\n')
|
|
break;
|
|
len++;
|
|
}
|
|
|
|
JSString* str = JS_NewStringCopyN(cx, currentBuf, len);
|
|
if (!str)
|
|
return false;
|
|
|
|
if (currentBuf[len] == '\0')
|
|
sc->readLineBufPos += len;
|
|
else
|
|
sc->readLineBufPos += len + 1;
|
|
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
if (args.length() == 1) {
|
|
if (sc->readLineBuf)
|
|
sc->readLineBuf.reset();
|
|
|
|
RootedString str(cx, JS::ToString(cx, args[0]));
|
|
if (!str)
|
|
return false;
|
|
sc->readLineBuf = UniqueChars(JS_EncodeStringToUTF8(cx, str));
|
|
if (!sc->readLineBuf)
|
|
return false;
|
|
|
|
sc->readLineBufPos = 0;
|
|
return true;
|
|
}
|
|
|
|
JS_ReportErrorASCII(cx, "Must specify at most one argument");
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
PutStr(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 0) {
|
|
if (!gOutFile->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
RootedString str(cx, JS::ToString(cx, args[0]));
|
|
if (!str)
|
|
return false;
|
|
char* bytes = JS_EncodeStringToUTF8(cx, str);
|
|
if (!bytes)
|
|
return false;
|
|
fputs(bytes, gOutFile->fp);
|
|
JS_free(cx, bytes);
|
|
fflush(gOutFile->fp);
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Now(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC);
|
|
args.rval().setDouble(now);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
PrintInternal(JSContext* cx, const CallArgs& args, RCFile* file)
|
|
{
|
|
if (!file->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
RootedString str(cx, JS::ToString(cx, args[i]));
|
|
if (!str)
|
|
return false;
|
|
char* bytes = JS_EncodeStringToUTF8(cx, str);
|
|
if (!bytes)
|
|
return false;
|
|
fprintf(file->fp, "%s%s", i ? " " : "", bytes);
|
|
JS_free(cx, bytes);
|
|
}
|
|
|
|
fputc('\n', file->fp);
|
|
fflush(file->fp);
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Print(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
return PrintInternal(cx, args, gOutFile);
|
|
}
|
|
|
|
static bool
|
|
PrintErr(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
return PrintInternal(cx, args, gErrFile);
|
|
}
|
|
|
|
static bool
|
|
Help(JSContext* cx, unsigned argc, Value* vp);
|
|
|
|
static bool
|
|
Quit(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
#ifdef JS_MORE_DETERMINISTIC
|
|
// Print a message to stderr in more-deterministic builds to help jsfunfuzz
|
|
// find uncatchable-exception bugs.
|
|
fprintf(stderr, "quit called\n");
|
|
#endif
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
int32_t code;
|
|
if (!ToInt32(cx, args.get(0), &code))
|
|
return false;
|
|
|
|
// The fuzzers check the shell's exit code and assume a value >= 128 means
|
|
// the process crashed (for instance, SIGSEGV will result in code 139). On
|
|
// POSIX platforms, the exit code is 8-bit and negative values can also
|
|
// result in an exit code >= 128. We restrict the value to range [0, 127] to
|
|
// avoid false positives.
|
|
if (code < 0 || code >= 128) {
|
|
JS_ReportErrorASCII(cx, "quit exit code should be in range 0-127");
|
|
return false;
|
|
}
|
|
|
|
sc->exitCode = code;
|
|
sc->quitting = true;
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
StartTimingMutator(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() > 0) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_TOO_MANY_ARGS,
|
|
"startTimingMutator");
|
|
return false;
|
|
}
|
|
|
|
if (!cx->runtime()->gc.stats.startTimingMutator()) {
|
|
JS_ReportErrorASCII(cx, "StartTimingMutator should only be called from outside of GC");
|
|
return false;
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
StopTimingMutator(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() > 0) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_TOO_MANY_ARGS,
|
|
"stopTimingMutator");
|
|
return false;
|
|
}
|
|
|
|
double mutator_ms, gc_ms;
|
|
if (!cx->runtime()->gc.stats.stopTimingMutator(mutator_ms, gc_ms)) {
|
|
JS_ReportErrorASCII(cx, "stopTimingMutator called when not timing the mutator");
|
|
return false;
|
|
}
|
|
double total_ms = mutator_ms + gc_ms;
|
|
if (total_ms > 0 && gOutFile->isOpen()) {
|
|
fprintf(gOutFile->fp, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n",
|
|
mutator_ms, mutator_ms / total_ms * 100.0, gc_ms, gc_ms / total_ms * 100.0);
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static const char*
|
|
ToSource(JSContext* cx, MutableHandleValue vp, JSAutoByteString* bytes)
|
|
{
|
|
JSString* str = JS_ValueToSource(cx, vp);
|
|
if (str) {
|
|
vp.setString(str);
|
|
if (bytes->encodeLatin1(cx, str))
|
|
return bytes->ptr();
|
|
}
|
|
JS_ClearPendingException(cx);
|
|
return "<<error converting value to string>>";
|
|
}
|
|
|
|
static bool
|
|
AssertEq(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
|
|
(args.length() < 2)
|
|
? JSSMSG_NOT_ENOUGH_ARGS
|
|
: (args.length() == 3)
|
|
? JSSMSG_INVALID_ARGS
|
|
: JSSMSG_TOO_MANY_ARGS,
|
|
"assertEq");
|
|
return false;
|
|
}
|
|
|
|
bool same;
|
|
if (!JS_SameValue(cx, args[0], args[1], &same))
|
|
return false;
|
|
if (!same) {
|
|
JSAutoByteString bytes0, bytes1;
|
|
const char* actual = ToSource(cx, args[0], &bytes0);
|
|
const char* expected = ToSource(cx, args[1], &bytes1);
|
|
if (args.length() == 2) {
|
|
JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED,
|
|
actual, expected);
|
|
} else {
|
|
JSAutoByteString bytes2(cx, args[2].toString());
|
|
if (!bytes2)
|
|
return false;
|
|
JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr,
|
|
JSSMSG_ASSERT_EQ_FAILED_MSG,
|
|
actual, expected, bytes2.ptr());
|
|
}
|
|
return false;
|
|
}
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static JSScript*
|
|
ValueToScript(JSContext* cx, HandleValue v, JSFunction** funp = nullptr)
|
|
{
|
|
if (v.isString()) {
|
|
// To convert a string to a script, compile it. Parse it as an ES6 Program.
|
|
RootedLinearString linearStr(cx, StringToLinearString(cx, v.toString()));
|
|
if (!linearStr)
|
|
return nullptr;
|
|
size_t len = GetLinearStringLength(linearStr);
|
|
AutoStableStringChars linearChars(cx);
|
|
if (!linearChars.initTwoByte(cx, linearStr))
|
|
return nullptr;
|
|
const char16_t* chars = linearChars.twoByteRange().begin().get();
|
|
|
|
RootedScript script(cx);
|
|
CompileOptions options(cx);
|
|
if (!JS::Compile(cx, options, chars, len, &script))
|
|
return nullptr;
|
|
return script;
|
|
}
|
|
|
|
RootedFunction fun(cx, JS_ValueToFunction(cx, v));
|
|
if (!fun)
|
|
return nullptr;
|
|
|
|
// Unwrap bound functions.
|
|
while (fun->isBoundFunction()) {
|
|
JSObject* target = fun->getBoundFunctionTarget();
|
|
if (target && target->is<JSFunction>())
|
|
fun = &target->as<JSFunction>();
|
|
else
|
|
break;
|
|
}
|
|
|
|
// Get unwrapped async function.
|
|
if (IsWrappedAsyncFunction(fun))
|
|
fun = GetUnwrappedAsyncFunction(fun);
|
|
|
|
if (!fun->isInterpreted()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY);
|
|
return nullptr;
|
|
}
|
|
|
|
JSScript* script = JSFunction::getOrCreateScript(cx, fun);
|
|
if (!script)
|
|
return nullptr;
|
|
|
|
if (funp)
|
|
*funp = fun;
|
|
|
|
return script;
|
|
}
|
|
|
|
static JSScript*
|
|
GetTopScript(JSContext* cx)
|
|
{
|
|
NonBuiltinScriptFrameIter iter(cx);
|
|
return iter.done() ? nullptr : iter.script();
|
|
}
|
|
|
|
static bool
|
|
GetScriptAndPCArgs(JSContext* cx, CallArgs& args, MutableHandleScript scriptp,
|
|
int32_t* ip)
|
|
{
|
|
RootedScript script(cx, GetTopScript(cx));
|
|
*ip = 0;
|
|
if (!args.get(0).isUndefined()) {
|
|
HandleValue v = args[0];
|
|
unsigned intarg = 0;
|
|
if (v.isObject() &&
|
|
JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) {
|
|
script = ValueToScript(cx, v);
|
|
if (!script)
|
|
return false;
|
|
intarg++;
|
|
}
|
|
if (!args.get(intarg).isUndefined()) {
|
|
if (!JS::ToInt32(cx, args[intarg], ip))
|
|
return false;
|
|
if ((uint32_t)*ip >= script->length()) {
|
|
JS_ReportErrorASCII(cx, "Invalid PC");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
scriptp.set(script);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
LineToPC(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() == 0) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_LINE2PC_USAGE);
|
|
return false;
|
|
}
|
|
|
|
RootedScript script(cx, GetTopScript(cx));
|
|
int32_t lineArg = 0;
|
|
if (args[0].isObject() && args[0].toObject().is<JSFunction>()) {
|
|
script = ValueToScript(cx, args[0]);
|
|
if (!script)
|
|
return false;
|
|
lineArg++;
|
|
}
|
|
|
|
uint32_t lineno;
|
|
if (!ToUint32(cx, args.get(lineArg), &lineno))
|
|
return false;
|
|
|
|
jsbytecode* pc = LineNumberToPC(script, lineno);
|
|
if (!pc)
|
|
return false;
|
|
args.rval().setInt32(script->pcToOffset(pc));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
PCToLine(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedScript script(cx);
|
|
int32_t i;
|
|
unsigned lineno;
|
|
|
|
if (!GetScriptAndPCArgs(cx, args, &script, &i))
|
|
return false;
|
|
lineno = PCToLineNumber(script, script->offsetToPC(i));
|
|
if (!lineno)
|
|
return false;
|
|
args.rval().setInt32(lineno);
|
|
return true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
static void
|
|
UpdateSwitchTableBounds(JSContext* cx, HandleScript script, unsigned offset,
|
|
unsigned* start, unsigned* end)
|
|
{
|
|
jsbytecode* pc;
|
|
JSOp op;
|
|
ptrdiff_t jmplen;
|
|
int32_t low, high, n;
|
|
|
|
pc = script->offsetToPC(offset);
|
|
op = JSOp(*pc);
|
|
switch (op) {
|
|
case JSOP_TABLESWITCH:
|
|
jmplen = JUMP_OFFSET_LEN;
|
|
pc += jmplen;
|
|
low = GET_JUMP_OFFSET(pc);
|
|
pc += JUMP_OFFSET_LEN;
|
|
high = GET_JUMP_OFFSET(pc);
|
|
pc += JUMP_OFFSET_LEN;
|
|
n = high - low + 1;
|
|
break;
|
|
|
|
default:
|
|
/* [condswitch] switch does not have any jump or lookup tables. */
|
|
MOZ_ASSERT(op == JSOP_CONDSWITCH);
|
|
return;
|
|
}
|
|
|
|
*start = script->pcToOffset(pc);
|
|
*end = *start + (unsigned)(n * jmplen);
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
SrcNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
|
{
|
|
if (sp->put("\nSource notes:\n") < 0 ||
|
|
!sp->jsprintf("%4s %4s %5s %6s %-8s %s\n",
|
|
"ofs", "line", "pc", "delta", "desc", "args") ||
|
|
sp->put("---- ---- ----- ------ -------- ------\n") < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
unsigned offset = 0;
|
|
unsigned colspan = 0;
|
|
unsigned lineno = script->lineno();
|
|
jssrcnote* notes = script->notes();
|
|
unsigned switchTableEnd = 0, switchTableStart = 0;
|
|
for (jssrcnote* sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
|
|
unsigned delta = SN_DELTA(sn);
|
|
offset += delta;
|
|
SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
|
|
const char* name = js_SrcNoteSpec[type].name;
|
|
if (!sp->jsprintf("%3u: %4u %5u [%4u] %-8s",
|
|
unsigned(sn - notes), lineno, offset, delta, name))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (type) {
|
|
case SRC_NULL:
|
|
case SRC_IF:
|
|
case SRC_CONTINUE:
|
|
case SRC_BREAK:
|
|
case SRC_BREAK2LABEL:
|
|
case SRC_SWITCHBREAK:
|
|
case SRC_ASSIGNOP:
|
|
case SRC_XDELTA:
|
|
break;
|
|
|
|
case SRC_COLSPAN:
|
|
colspan = SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, 0));
|
|
if (!sp->jsprintf("%d", colspan))
|
|
return false;
|
|
break;
|
|
|
|
case SRC_SETLINE:
|
|
lineno = GetSrcNoteOffset(sn, 0);
|
|
if (!sp->jsprintf(" lineno %u", lineno))
|
|
return false;
|
|
break;
|
|
|
|
case SRC_NEWLINE:
|
|
++lineno;
|
|
break;
|
|
|
|
case SRC_FOR:
|
|
if (!sp->jsprintf(" cond %u update %u tail %u",
|
|
unsigned(GetSrcNoteOffset(sn, 0)),
|
|
unsigned(GetSrcNoteOffset(sn, 1)),
|
|
unsigned(GetSrcNoteOffset(sn, 2))))
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case SRC_IF_ELSE:
|
|
if (!sp->jsprintf(" else %u", unsigned(GetSrcNoteOffset(sn, 0))))
|
|
return false;
|
|
break;
|
|
|
|
case SRC_FOR_IN:
|
|
case SRC_FOR_OF:
|
|
if (!sp->jsprintf(" closingjump %u", unsigned(GetSrcNoteOffset(sn, 0))))
|
|
return false;
|
|
break;
|
|
|
|
case SRC_COND:
|
|
case SRC_WHILE:
|
|
case SRC_NEXTCASE:
|
|
if (!sp->jsprintf(" offset %u", unsigned(GetSrcNoteOffset(sn, 0))))
|
|
return false;
|
|
break;
|
|
|
|
case SRC_TABLESWITCH: {
|
|
JSOp op = JSOp(script->code()[offset]);
|
|
MOZ_ASSERT(op == JSOP_TABLESWITCH);
|
|
if (!sp->jsprintf(" length %u", unsigned(GetSrcNoteOffset(sn, 0))))
|
|
return false;
|
|
UpdateSwitchTableBounds(cx, script, offset,
|
|
&switchTableStart, &switchTableEnd);
|
|
break;
|
|
}
|
|
case SRC_CONDSWITCH: {
|
|
JSOp op = JSOp(script->code()[offset]);
|
|
MOZ_ASSERT(op == JSOP_CONDSWITCH);
|
|
if (!sp->jsprintf(" length %u", unsigned(GetSrcNoteOffset(sn, 0))))
|
|
return false;
|
|
if (unsigned caseOff = (unsigned) GetSrcNoteOffset(sn, 1)) {
|
|
if (!sp->jsprintf(" first case offset %u", caseOff))
|
|
return false;
|
|
}
|
|
UpdateSwitchTableBounds(cx, script, offset,
|
|
&switchTableStart, &switchTableEnd);
|
|
break;
|
|
}
|
|
|
|
case SRC_TRY:
|
|
MOZ_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY);
|
|
if (!sp->jsprintf(" offset to jump %u", unsigned(GetSrcNoteOffset(sn, 0))))
|
|
return false;
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
|
|
}
|
|
if (sp->put("\n") < 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Notes(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
Sprinter sprinter(cx);
|
|
if (!sprinter.init())
|
|
return false;
|
|
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
RootedScript script (cx, ValueToScript(cx, args[i]));
|
|
if (!script)
|
|
return false;
|
|
|
|
if (!SrcNotes(cx, script, &sprinter))
|
|
return false;
|
|
}
|
|
|
|
JSString* str = JS_NewStringCopyZ(cx, sprinter.string());
|
|
if (!str)
|
|
return false;
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
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_ITERCLOSE:
|
|
return "dstr-iterclose";
|
|
}
|
|
|
|
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") < 0)
|
|
return false;
|
|
|
|
JSTryNote* tn = script->trynotes()->vector;
|
|
JSTryNote* tnlimit = tn + script->trynotes()->length;
|
|
do {
|
|
uint32_t startOff = script->pcToOffset(script->main()) + tn->start;
|
|
if (!sp->jsprintf(" %-16s %6u %8u %8u\n",
|
|
TryNoteName(static_cast<JSTryNoteKind>(tn->kind)),
|
|
tn->stackDepth, startOff, startOff + tn->length))
|
|
{
|
|
return false;
|
|
}
|
|
} while (++tn != tnlimit);
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
ScopeNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
|
{
|
|
if (!script->hasScopeNotes())
|
|
return true;
|
|
|
|
if (sp->put("\nScope notes:\n index parent start end\n") < 0)
|
|
return false;
|
|
|
|
ScopeNoteArray* notes = script->scopeNotes();
|
|
for (uint32_t i = 0; i < notes->length; i++) {
|
|
const ScopeNote* note = ¬es->vector[i];
|
|
if (note->index == ScopeNote::NoScopeIndex) {
|
|
if (!sp->jsprintf("%8s ", "(none)"))
|
|
return false;
|
|
} else {
|
|
if (!sp->jsprintf("%8u ", note->index))
|
|
return false;
|
|
}
|
|
if (note->parent == ScopeNote::NoScopeIndex) {
|
|
if (!sp->jsprintf("%8s ", "(none)"))
|
|
return false;
|
|
} else {
|
|
if (!sp->jsprintf("%8u ", note->parent))
|
|
return false;
|
|
}
|
|
if (!sp->jsprintf("%8u %8u\n", note->start, note->start + note->length))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
DisassembleScript(JSContext* cx, HandleScript script, HandleFunction fun,
|
|
bool lines, bool recursive, bool sourceNotes, Sprinter* sp)
|
|
{
|
|
if (fun) {
|
|
if (sp->put("flags:") < 0)
|
|
return false;
|
|
if (fun->isLambda()) {
|
|
if (sp->put(" LAMBDA") < 0)
|
|
return false;
|
|
}
|
|
if (fun->needsCallObject()) {
|
|
if (sp->put(" NEEDS_CALLOBJECT") < 0)
|
|
return false;
|
|
}
|
|
if (fun->needsExtraBodyVarEnvironment()) {
|
|
if (sp->put(" NEEDS_EXTRABODYVARENV") < 0)
|
|
return false;
|
|
}
|
|
if (fun->needsNamedLambdaEnvironment()) {
|
|
if (sp->put(" NEEDS_NAMEDLAMBDAENV") < 0)
|
|
return false;
|
|
}
|
|
if (fun->isConstructor()) {
|
|
if (sp->put(" CONSTRUCTOR") < 0)
|
|
return false;
|
|
}
|
|
if (script->isExprBody()) {
|
|
if (sp->put(" EXPRESSION_CLOSURE") < 0)
|
|
return false;
|
|
}
|
|
if (fun->isSelfHostedBuiltin()) {
|
|
if (sp->put(" SELF_HOSTED") < 0)
|
|
return false;
|
|
}
|
|
if (fun->isArrow()) {
|
|
if (sp->put(" ARROW") < 0)
|
|
return false;
|
|
}
|
|
if (sp->put("\n") < 0)
|
|
return false;
|
|
}
|
|
|
|
if (!Disassemble(cx, script, lines, sp))
|
|
return false;
|
|
if (sourceNotes) {
|
|
if (!SrcNotes(cx, script, sp))
|
|
return false;
|
|
}
|
|
if (!TryNotes(cx, script, sp))
|
|
return false;
|
|
if (!ScopeNotes(cx, script, sp))
|
|
return false;
|
|
|
|
if (recursive && script->hasObjects()) {
|
|
ObjectArray* objects = script->objects();
|
|
for (unsigned i = 0; i != objects->length; ++i) {
|
|
JSObject* obj = objects->vector[i];
|
|
if (obj->is<JSFunction>()) {
|
|
if (sp->put("\n") < 0)
|
|
return false;
|
|
|
|
RootedFunction fun(cx, &obj->as<JSFunction>());
|
|
if (fun->isInterpreted()) {
|
|
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
|
|
if (script) {
|
|
if (!DisassembleScript(cx, script, fun, lines, recursive, sourceNotes, sp))
|
|
return false;
|
|
}
|
|
} else {
|
|
if (sp->put("[native code]\n") < 0)
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct DisassembleOptionParser {
|
|
unsigned argc;
|
|
Value* argv;
|
|
bool lines;
|
|
bool recursive;
|
|
bool sourceNotes;
|
|
|
|
DisassembleOptionParser(unsigned argc, Value* argv)
|
|
: argc(argc), argv(argv), lines(false), recursive(false), sourceNotes(true) {}
|
|
|
|
bool parse(JSContext* cx) {
|
|
/* Read options off early arguments */
|
|
while (argc > 0 && argv[0].isString()) {
|
|
JSString* str = argv[0].toString();
|
|
JSFlatString* flatStr = JS_FlattenString(cx, str);
|
|
if (!flatStr)
|
|
return false;
|
|
if (JS_FlatStringEqualsAscii(flatStr, "-l"))
|
|
lines = true;
|
|
else if (JS_FlatStringEqualsAscii(flatStr, "-r"))
|
|
recursive = true;
|
|
else if (JS_FlatStringEqualsAscii(flatStr, "-S"))
|
|
sourceNotes = false;
|
|
else
|
|
break;
|
|
argv++, argc--;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} /* anonymous namespace */
|
|
|
|
static bool
|
|
DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp, Sprinter* sprinter)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
DisassembleOptionParser p(args.length(), args.array());
|
|
if (!p.parse(cx))
|
|
return false;
|
|
|
|
if (p.argc == 0) {
|
|
/* Without arguments, disassemble the current script. */
|
|
RootedScript script(cx, GetTopScript(cx));
|
|
if (script) {
|
|
JSAutoCompartment ac(cx, script);
|
|
if (!Disassemble(cx, script, p.lines, sprinter))
|
|
return false;
|
|
if (!SrcNotes(cx, script, sprinter))
|
|
return false;
|
|
if (!TryNotes(cx, script, sprinter))
|
|
return false;
|
|
if (!ScopeNotes(cx, script, sprinter))
|
|
return false;
|
|
}
|
|
} else {
|
|
for (unsigned i = 0; i < p.argc; i++) {
|
|
RootedFunction fun(cx);
|
|
RootedScript script(cx);
|
|
RootedValue value(cx, p.argv[i]);
|
|
if (value.isObject() && value.toObject().is<ModuleObject>())
|
|
script = value.toObject().as<ModuleObject>().script();
|
|
else
|
|
script = ValueToScript(cx, value, fun.address());
|
|
if (!script)
|
|
return false;
|
|
if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, p.sourceNotes, sprinter))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return !sprinter->hadOutOfMemory();
|
|
}
|
|
|
|
static bool
|
|
DisassembleToString(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
Sprinter sprinter(cx);
|
|
if (!sprinter.init())
|
|
return false;
|
|
if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter))
|
|
return false;
|
|
|
|
JSString* str = JS_NewStringCopyZ(cx, sprinter.string());
|
|
if (!str)
|
|
return false;
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Disassemble(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!gOutFile->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
Sprinter sprinter(cx);
|
|
if (!sprinter.init())
|
|
return false;
|
|
if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter))
|
|
return false;
|
|
|
|
fprintf(gOutFile->fp, "%s\n", sprinter.string());
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DisassFile(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!gOutFile->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
/* Support extra options at the start, just like Disassemble. */
|
|
DisassembleOptionParser p(args.length(), args.array());
|
|
if (!p.parse(cx))
|
|
return false;
|
|
|
|
if (!p.argc) {
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// We should change DisassembleOptionParser to store CallArgs.
|
|
JSString* str = JS::ToString(cx, HandleValue::fromMarkedLocation(&p.argv[0]));
|
|
if (!str)
|
|
return false;
|
|
JSAutoByteString filename(cx, str);
|
|
if (!filename)
|
|
return false;
|
|
RootedScript script(cx);
|
|
|
|
{
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("js shell disFile")
|
|
.setUTF8(true)
|
|
.setFileAndLine(filename.ptr(), 1)
|
|
.setIsRunOnce(true)
|
|
.setNoScriptRval(true);
|
|
|
|
if (!JS::Compile(cx, options, filename.ptr(), &script))
|
|
return false;
|
|
}
|
|
|
|
Sprinter sprinter(cx);
|
|
if (!sprinter.init())
|
|
return false;
|
|
bool ok = DisassembleScript(cx, script, nullptr, p.lines, p.recursive, p.sourceNotes, &sprinter);
|
|
if (ok)
|
|
fprintf(gOutFile->fp, "%s\n", sprinter.string());
|
|
if (!ok)
|
|
return false;
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DisassWithSrc(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!gOutFile->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
const size_t lineBufLen = 512;
|
|
unsigned len, line1, line2, bupline;
|
|
char linebuf[lineBufLen];
|
|
static const char sep[] = ";-------------------------";
|
|
|
|
RootedScript script(cx);
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
script = ValueToScript(cx, args[i]);
|
|
if (!script)
|
|
return false;
|
|
|
|
if (!script->filename()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_FILE_SCRIPTS_ONLY);
|
|
return false;
|
|
}
|
|
|
|
FILE* file = fopen(script->filename(), "r");
|
|
if (!file) {
|
|
/* FIXME: script->filename() should become UTF-8 (bug 987069). */
|
|
ReportCantOpenErrorUnknownEncoding(cx, script->filename());
|
|
return false;
|
|
}
|
|
auto closeFile = MakeScopeExit([file]() { fclose(file); });
|
|
|
|
jsbytecode* pc = script->code();
|
|
jsbytecode* end = script->codeEnd();
|
|
|
|
Sprinter sprinter(cx);
|
|
if (!sprinter.init())
|
|
return false;
|
|
|
|
/* burn the leading lines */
|
|
line2 = PCToLineNumber(script, pc);
|
|
for (line1 = 0; line1 < line2 - 1; line1++) {
|
|
char* tmp = fgets(linebuf, lineBufLen, file);
|
|
if (!tmp) {
|
|
/* FIXME: This should use UTF-8 (bug 987069). */
|
|
JS_ReportErrorLatin1(cx, "failed to read %s fully", script->filename());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bupline = 0;
|
|
while (pc < end) {
|
|
line2 = PCToLineNumber(script, pc);
|
|
|
|
if (line2 < line1) {
|
|
if (bupline != line2) {
|
|
bupline = line2;
|
|
if (!sprinter.jsprintf("%s %3u: BACKUP\n", sep, line2))
|
|
return false;
|
|
}
|
|
} else {
|
|
if (bupline && line1 == line2) {
|
|
if (!sprinter.jsprintf("%s %3u: RESTORE\n", sep, line2))
|
|
return false;
|
|
}
|
|
bupline = 0;
|
|
while (line1 < line2) {
|
|
if (!fgets(linebuf, lineBufLen, file)) {
|
|
/*
|
|
* FIXME: script->filename() should become UTF-8
|
|
* (bug 987069).
|
|
*/
|
|
JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr,
|
|
JSSMSG_UNEXPECTED_EOF,
|
|
script->filename());
|
|
return false;
|
|
}
|
|
line1++;
|
|
if (!sprinter.jsprintf("%s %3u: %s", sep, line1, linebuf))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
len = Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter);
|
|
if (!len)
|
|
return false;
|
|
|
|
pc += len;
|
|
}
|
|
|
|
fprintf(gOutFile->fp, "%s\n", sprinter.string());
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
#endif /* DEBUG */
|
|
|
|
/* Pretend we can always preserve wrappers for dummy DOM objects. */
|
|
static bool
|
|
DummyPreserveWrapperCallback(JSContext* cx, JSObject* obj)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Intern(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
JSString* str = JS::ToString(cx, args.get(0));
|
|
if (!str)
|
|
return false;
|
|
|
|
AutoStableStringChars strChars(cx);
|
|
if (!strChars.initTwoByte(cx, str))
|
|
return false;
|
|
|
|
mozilla::Range<const char16_t> chars = strChars.twoByteRange();
|
|
|
|
if (!JS_AtomizeAndPinUCStringN(cx, chars.begin().get(), chars.length()))
|
|
return false;
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Clone(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject parent(cx);
|
|
RootedObject funobj(cx);
|
|
|
|
if (!args.length()) {
|
|
JS_ReportErrorASCII(cx, "Invalid arguments to clone");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
Maybe<JSAutoCompartment> ac;
|
|
RootedObject obj(cx, args[0].isPrimitive() ? nullptr : &args[0].toObject());
|
|
|
|
if (obj && obj->is<CrossCompartmentWrapperObject>()) {
|
|
obj = UncheckedUnwrap(obj);
|
|
ac.emplace(cx, obj);
|
|
args[0].setObject(*obj);
|
|
}
|
|
if (obj && obj->is<JSFunction>()) {
|
|
funobj = obj;
|
|
} else {
|
|
JSFunction* fun = JS_ValueToFunction(cx, args[0]);
|
|
if (!fun)
|
|
return false;
|
|
funobj = JS_GetFunctionObject(fun);
|
|
}
|
|
}
|
|
|
|
if (args.length() > 1) {
|
|
if (!JS_ValueToObject(cx, args[1], &parent))
|
|
return false;
|
|
} else {
|
|
parent = js::GetGlobalForObjectCrossCompartment(&args.callee());
|
|
}
|
|
|
|
// Should it worry us that we might be getting with wrappers
|
|
// around with wrappers here?
|
|
JS::AutoObjectVector scopeChain(cx);
|
|
if (!parent->is<GlobalObject>() && !scopeChain.append(parent))
|
|
return false;
|
|
JSObject* clone = JS::CloneFunctionObject(cx, funobj, scopeChain);
|
|
if (!clone)
|
|
return false;
|
|
args.rval().setObject(*clone);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Crash(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() == 0)
|
|
MOZ_CRASH("forced crash");
|
|
RootedString message(cx, JS::ToString(cx, args[0]));
|
|
if (!message)
|
|
return false;
|
|
char* utf8chars = JS_EncodeStringToUTF8(cx, message);
|
|
if (!utf8chars)
|
|
return false;
|
|
#ifndef DEBUG
|
|
MOZ_ReportCrash(utf8chars, __FILE__, __LINE__);
|
|
#endif
|
|
MOZ_CRASH_UNSAFE_OOL(utf8chars);
|
|
}
|
|
|
|
static bool
|
|
GetSLX(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedScript script(cx);
|
|
|
|
script = ValueToScript(cx, args.get(0));
|
|
if (!script)
|
|
return false;
|
|
args.rval().setInt32(GetScriptLineExtent(script));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ThrowError(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
JS_ReportErrorASCII(cx, "This is an error");
|
|
return false;
|
|
}
|
|
|
|
#define LAZY_STANDARD_CLASSES
|
|
|
|
/* A class for easily testing the inner/outer object callbacks. */
|
|
typedef struct ComplexObject {
|
|
bool isInner;
|
|
bool frozen;
|
|
JSObject* inner;
|
|
JSObject* outer;
|
|
} ComplexObject;
|
|
|
|
static bool
|
|
sandbox_enumerate(JSContext* cx, HandleObject obj)
|
|
{
|
|
RootedValue v(cx);
|
|
|
|
if (!JS_GetProperty(cx, obj, "lazy", &v))
|
|
return false;
|
|
|
|
if (!ToBoolean(v))
|
|
return true;
|
|
|
|
return JS_EnumerateStandardClasses(cx, obj);
|
|
}
|
|
|
|
static bool
|
|
sandbox_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
|
|
{
|
|
RootedValue v(cx);
|
|
if (!JS_GetProperty(cx, obj, "lazy", &v))
|
|
return false;
|
|
|
|
if (ToBoolean(v))
|
|
return JS_ResolveStandardClass(cx, obj, id, resolvedp);
|
|
return true;
|
|
}
|
|
|
|
static const JSClassOps sandbox_classOps = {
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
sandbox_enumerate, sandbox_resolve,
|
|
nullptr, nullptr,
|
|
nullptr, nullptr, nullptr,
|
|
JS_GlobalObjectTraceHook
|
|
};
|
|
|
|
static const JSClass sandbox_class = {
|
|
"sandbox",
|
|
JSCLASS_GLOBAL_FLAGS,
|
|
&sandbox_classOps
|
|
};
|
|
|
|
static void
|
|
SetStandardCompartmentOptions(JS::CompartmentOptions& options)
|
|
{
|
|
options.behaviors().setVersion(JSVERSION_DEFAULT);
|
|
options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
|
|
}
|
|
|
|
static JSObject*
|
|
NewSandbox(JSContext* cx, bool lazy)
|
|
{
|
|
JS::CompartmentOptions options;
|
|
SetStandardCompartmentOptions(options);
|
|
RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, nullptr,
|
|
JS::DontFireOnNewGlobalHook, options));
|
|
if (!obj)
|
|
return nullptr;
|
|
|
|
{
|
|
JSAutoCompartment ac(cx, obj);
|
|
if (!lazy && !JS_InitStandardClasses(cx, obj))
|
|
return nullptr;
|
|
|
|
RootedValue value(cx, BooleanValue(lazy));
|
|
if (!JS_SetProperty(cx, obj, "lazy", value))
|
|
return nullptr;
|
|
}
|
|
|
|
JS_FireOnNewGlobalObject(cx, obj);
|
|
|
|
if (!cx->compartment()->wrap(cx, &obj))
|
|
return nullptr;
|
|
return obj;
|
|
}
|
|
|
|
static bool
|
|
EvalInContext(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (!args.requireAtLeast(cx, "evalcx", 1))
|
|
return false;
|
|
|
|
RootedString str(cx, ToString(cx, args[0]));
|
|
if (!str)
|
|
return false;
|
|
|
|
RootedObject sobj(cx);
|
|
if (args.hasDefined(1)) {
|
|
sobj = ToObject(cx, args[1]);
|
|
if (!sobj)
|
|
return false;
|
|
}
|
|
|
|
AutoStableStringChars strChars(cx);
|
|
if (!strChars.initTwoByte(cx, str))
|
|
return false;
|
|
|
|
mozilla::Range<const char16_t> chars = strChars.twoByteRange();
|
|
size_t srclen = chars.length();
|
|
const char16_t* src = chars.begin().get();
|
|
|
|
bool lazy = false;
|
|
if (srclen == 4) {
|
|
if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') {
|
|
lazy = true;
|
|
srclen = 0;
|
|
}
|
|
}
|
|
|
|
if (!sobj) {
|
|
sobj = NewSandbox(cx, lazy);
|
|
if (!sobj)
|
|
return false;
|
|
}
|
|
|
|
if (srclen == 0) {
|
|
args.rval().setObject(*sobj);
|
|
return true;
|
|
}
|
|
|
|
JS::AutoFilename filename;
|
|
unsigned lineno;
|
|
|
|
DescribeScriptedCaller(cx, &filename, &lineno);
|
|
{
|
|
Maybe<JSAutoCompartment> ac;
|
|
unsigned flags;
|
|
JSObject* unwrapped = UncheckedUnwrap(sobj, true, &flags);
|
|
if (flags & Wrapper::CROSS_COMPARTMENT) {
|
|
sobj = unwrapped;
|
|
ac.emplace(cx, sobj);
|
|
}
|
|
|
|
sobj = ToWindowIfWindowProxy(sobj);
|
|
|
|
if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) {
|
|
JS_ReportErrorASCII(cx, "Invalid scope argument to evalcx");
|
|
return false;
|
|
}
|
|
JS::CompileOptions opts(cx);
|
|
opts.setFileAndLine(filename.get(), lineno);
|
|
if (!JS::Evaluate(cx, opts, src, srclen, args.rval())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!cx->compartment()->wrap(cx, args.rval()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
struct WorkerInput
|
|
{
|
|
JSContext* context;
|
|
char16_t* chars;
|
|
size_t length;
|
|
|
|
WorkerInput(JSContext* context, char16_t* chars, size_t length)
|
|
: context(context), chars(chars), length(length)
|
|
{}
|
|
|
|
~WorkerInput() {
|
|
js_free(chars);
|
|
}
|
|
};
|
|
|
|
static void SetWorkerContextOptions(JSContext* cx);
|
|
|
|
static void
|
|
WorkerMain(void* arg)
|
|
{
|
|
WorkerInput* input = (WorkerInput*) arg;
|
|
|
|
JSContext* cx = JS_NewContext(8L * 1024L * 1024L, 2L * 1024L * 1024L, input->context);
|
|
if (!cx) {
|
|
js_delete(input);
|
|
return;
|
|
}
|
|
|
|
UniquePtr<ShellContext> sc = MakeUnique<ShellContext>(cx);
|
|
if (!sc) {
|
|
JS_DestroyContext(cx);
|
|
js_delete(input);
|
|
return;
|
|
}
|
|
|
|
sc->isWorker = true;
|
|
JS_SetContextPrivate(cx, sc.get());
|
|
JS_SetFutexCanWait(cx);
|
|
JS::SetWarningReporter(cx, WarningReporter);
|
|
js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
|
|
JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
|
|
SetWorkerContextOptions(cx);
|
|
|
|
if (!JS::InitSelfHostedCode(cx)) {
|
|
JS_DestroyContext(cx);
|
|
js_delete(input);
|
|
return;
|
|
}
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
sc->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
|
|
JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
|
|
JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
|
|
JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
EnvironmentPreparer environmentPreparer(cx);
|
|
|
|
JS::SetLargeAllocationFailureCallback(cx, my_LargeAllocFailCallback, (void*)cx);
|
|
|
|
do {
|
|
JSAutoRequest ar(cx);
|
|
|
|
JS::CompartmentOptions compartmentOptions;
|
|
SetStandardCompartmentOptions(compartmentOptions);
|
|
RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr));
|
|
if (!global)
|
|
break;
|
|
|
|
JSAutoCompartment ac(cx, global);
|
|
|
|
JS::CompileOptions options(cx);
|
|
options.setFileAndLine("<string>", 1)
|
|
.setIsRunOnce(true);
|
|
|
|
AutoReportException are(cx);
|
|
RootedScript script(cx);
|
|
if (!JS::Compile(cx, options, input->chars, input->length, &script))
|
|
break;
|
|
RootedValue result(cx);
|
|
JS_ExecuteScript(cx, script, &result);
|
|
} while (0);
|
|
|
|
JS::SetLargeAllocationFailureCallback(cx, nullptr, nullptr);
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
JS::SetGetIncumbentGlobalCallback(cx, nullptr);
|
|
JS::SetEnqueuePromiseJobCallback(cx, nullptr);
|
|
sc->jobQueue.reset();
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
KillWatchdog(cx);
|
|
|
|
JS_DestroyContext(cx);
|
|
|
|
js_delete(input);
|
|
}
|
|
|
|
// Workers can spawn other workers, so we need a lock to access workerThreads.
|
|
static Mutex* workerThreadsLock = nullptr;
|
|
static Vector<js::Thread*, 0, SystemAllocPolicy> workerThreads;
|
|
|
|
class MOZ_RAII AutoLockWorkerThreads : public LockGuard<Mutex>
|
|
{
|
|
using Base = LockGuard<Mutex>;
|
|
public:
|
|
AutoLockWorkerThreads()
|
|
: Base(*workerThreadsLock)
|
|
{
|
|
MOZ_ASSERT(workerThreadsLock);
|
|
}
|
|
};
|
|
|
|
static bool
|
|
EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
if (!CanUseExtraThreads()) {
|
|
JS_ReportErrorASCII(cx, "Can't create worker threads with --no-threads");
|
|
return false;
|
|
}
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (!args.get(0).isString()) {
|
|
JS_ReportErrorASCII(cx, "Invalid arguments to evalInWorker");
|
|
return false;
|
|
}
|
|
|
|
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
|
|
if (cx->runtime()->runningOOMTest) {
|
|
JS_ReportErrorASCII(cx, "Can't create workers while running simulated OOM test");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (!args[0].toString()->ensureLinear(cx))
|
|
return false;
|
|
|
|
if (!workerThreadsLock) {
|
|
workerThreadsLock = js_new<Mutex>(mutexid::ShellWorkerThreads);
|
|
if (!workerThreadsLock) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JSLinearString* str = &args[0].toString()->asLinear();
|
|
|
|
char16_t* chars = (char16_t*) js_malloc(str->length() * sizeof(char16_t));
|
|
if (!chars) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
CopyChars(chars, *str);
|
|
|
|
WorkerInput* input = js_new<WorkerInput>(JS_GetParentContext(cx), chars, str->length());
|
|
if (!input) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
auto thread = js_new<Thread>(Thread::Options().setStackSize(gMaxStackSize + 128 * 1024));
|
|
if (!thread || !thread->init(WorkerMain, input)) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
AutoLockWorkerThreads alwt;
|
|
if (!workerThreads.append(thread)) {
|
|
ReportOutOfMemory(cx);
|
|
thread->join();
|
|
return false;
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (!args.get(0).isObject()) {
|
|
JS_ReportErrorASCII(cx, "shapeOf: object expected");
|
|
return false;
|
|
}
|
|
JSObject* obj = &args[0].toObject();
|
|
args.rval().set(JS_NumberValue(double(uintptr_t(obj->maybeShape()) >> 3)));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GroupOf(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (!args.get(0).isObject()) {
|
|
JS_ReportErrorASCII(cx, "groupOf: object expected");
|
|
return false;
|
|
}
|
|
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()));
|
|
|
|
if (!obj1->is<ShapedObject>() || !obj2->is<ShapedObject>()) {
|
|
JS_ReportErrorASCII(cx, "object does not have a Shape");
|
|
return false;
|
|
}
|
|
|
|
args.rval().setBoolean(obj1->as<ShapedObject>().shape() == obj2->as<ShapedObject>().shape());
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Sleep_fn(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
TimeDuration duration = TimeDuration::FromSeconds(0.0);
|
|
if (args.length() > 0) {
|
|
double t_secs;
|
|
if (!ToNumber(cx, args[0], &t_secs))
|
|
return false;
|
|
if (mozilla::IsNaN(t_secs)) {
|
|
JS_ReportErrorASCII(cx, "sleep interval is not a number");
|
|
return false;
|
|
}
|
|
|
|
duration = TimeDuration::FromSeconds(Max(0.0, t_secs));
|
|
const TimeDuration MAX_TIMEOUT_INTERVAL = TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
|
|
if (duration > MAX_TIMEOUT_INTERVAL) {
|
|
JS_ReportErrorASCII(cx, "Excessive sleep interval");
|
|
return false;
|
|
}
|
|
}
|
|
{
|
|
LockGuard<Mutex> guard(sc->watchdogLock);
|
|
TimeStamp toWakeup = TimeStamp::Now() + duration;
|
|
for (;;) {
|
|
sc->sleepWakeup.wait_for(guard, duration);
|
|
if (sc->serviceInterrupt)
|
|
break;
|
|
auto now = TimeStamp::Now();
|
|
if (now >= toWakeup)
|
|
break;
|
|
duration = toWakeup - now;
|
|
}
|
|
}
|
|
args.rval().setUndefined();
|
|
return !sc->serviceInterrupt;
|
|
}
|
|
|
|
static void
|
|
KillWatchdog(JSContext* cx)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
Maybe<Thread> thread;
|
|
|
|
{
|
|
LockGuard<Mutex> guard(sc->watchdogLock);
|
|
Swap(sc->watchdogThread, thread);
|
|
if (thread) {
|
|
// The watchdog thread becoming Nothing is its signal to exit.
|
|
sc->watchdogWakeup.notify_one();
|
|
}
|
|
}
|
|
if (thread)
|
|
thread->join();
|
|
|
|
MOZ_ASSERT(!sc->watchdogThread);
|
|
}
|
|
|
|
static void
|
|
WatchdogMain(JSContext* cx)
|
|
{
|
|
ThisThread::SetName("JS Watchdog");
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
LockGuard<Mutex> guard(sc->watchdogLock);
|
|
while (sc->watchdogThread) {
|
|
auto now = TimeStamp::Now();
|
|
if (sc->watchdogTimeout && now >= sc->watchdogTimeout.value()) {
|
|
/*
|
|
* The timeout has just expired. Request an interrupt callback
|
|
* outside the lock.
|
|
*/
|
|
sc->watchdogTimeout = Nothing();
|
|
{
|
|
UnlockGuard<Mutex> unlock(guard);
|
|
CancelExecution(cx);
|
|
}
|
|
|
|
/* Wake up any threads doing sleep. */
|
|
sc->sleepWakeup.notify_all();
|
|
} else {
|
|
if (sc->watchdogTimeout) {
|
|
/*
|
|
* Time hasn't expired yet. Simulate an interrupt callback
|
|
* which doesn't abort execution.
|
|
*/
|
|
JS_RequestInterruptCallback(cx);
|
|
}
|
|
|
|
TimeDuration sleepDuration = sc->watchdogTimeout
|
|
? TimeDuration::FromSeconds(0.1)
|
|
: TimeDuration::Forever();
|
|
sc->watchdogWakeup.wait_for(guard, sleepDuration);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
ScheduleWatchdog(JSContext* cx, double t)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
if (t <= 0) {
|
|
LockGuard<Mutex> guard(sc->watchdogLock);
|
|
sc->watchdogTimeout = Nothing();
|
|
return true;
|
|
}
|
|
|
|
auto interval = TimeDuration::FromSeconds(t);
|
|
auto timeout = TimeStamp::Now() + interval;
|
|
LockGuard<Mutex> guard(sc->watchdogLock);
|
|
if (!sc->watchdogThread) {
|
|
MOZ_ASSERT(!sc->watchdogTimeout);
|
|
sc->watchdogThread.emplace();
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!sc->watchdogThread->init(WatchdogMain, cx))
|
|
oomUnsafe.crash("watchdogThread.init");
|
|
} else if (!sc->watchdogTimeout || timeout < sc->watchdogTimeout.value()) {
|
|
sc->watchdogWakeup.notify_one();
|
|
}
|
|
sc->watchdogTimeout = Some(timeout);
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
KillWorkerThreads()
|
|
{
|
|
MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty());
|
|
|
|
if (!workerThreadsLock) {
|
|
MOZ_ASSERT(workerThreads.empty());
|
|
return;
|
|
}
|
|
|
|
while (true) {
|
|
// We need to leave the AutoLockWorkerThreads scope before we call
|
|
// js::Thread::join, to avoid deadlocks when AutoLockWorkerThreads is
|
|
// used by the worker thread.
|
|
Thread* thread;
|
|
{
|
|
AutoLockWorkerThreads alwt;
|
|
if (workerThreads.empty())
|
|
break;
|
|
thread = workerThreads.popCopy();
|
|
}
|
|
thread->join();
|
|
}
|
|
|
|
js_delete(workerThreadsLock);
|
|
workerThreadsLock = nullptr;
|
|
}
|
|
|
|
static void
|
|
CancelExecution(JSContext* cx)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
sc->serviceInterrupt = true;
|
|
JS_RequestInterruptCallback(cx);
|
|
|
|
if (sc->haveInterruptFunc) {
|
|
static const char msg[] = "Script runs for too long, terminating.\n";
|
|
fputs(msg, stderr);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
SetTimeoutValue(JSContext* cx, double t)
|
|
{
|
|
if (mozilla::IsNaN(t)) {
|
|
JS_ReportErrorASCII(cx, "timeout is not a number");
|
|
return false;
|
|
}
|
|
const TimeDuration MAX_TIMEOUT_INTERVAL = TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
|
|
if (TimeDuration::FromSeconds(t) > MAX_TIMEOUT_INTERVAL) {
|
|
JS_ReportErrorASCII(cx, "Excessive timeout value");
|
|
return false;
|
|
}
|
|
GetShellContext(cx)->timeoutInterval = t;
|
|
if (!ScheduleWatchdog(cx, t)) {
|
|
JS_ReportErrorASCII(cx, "Failed to create the watchdog");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Timeout(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() == 0) {
|
|
args.rval().setNumber(sc->timeoutInterval);
|
|
return true;
|
|
}
|
|
|
|
if (args.length() > 2) {
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
double t;
|
|
if (!ToNumber(cx, args[0], &t))
|
|
return false;
|
|
|
|
if (args.length() > 1) {
|
|
RootedValue value(cx, args[1]);
|
|
if (!value.isObject() || !value.toObject().is<JSFunction>()) {
|
|
JS_ReportErrorASCII(cx, "Second argument must be a timeout function");
|
|
return false;
|
|
}
|
|
sc->interruptFunc = value;
|
|
sc->haveInterruptFunc = true;
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return SetTimeoutValue(cx, t);
|
|
}
|
|
|
|
static bool
|
|
InterruptIf(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
if (ToBoolean(args[0])) {
|
|
GetShellContext(cx)->serviceInterrupt = true;
|
|
JS_RequestInterruptCallback(cx);
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
GetShellContext(cx)->serviceInterrupt = true;
|
|
JS_RequestInterruptCallback(cx);
|
|
bool interruptRv = CheckForInterrupt(cx);
|
|
|
|
// The interrupt handler could have set a pending exception. Since we call
|
|
// back into JS, don't have it see the pending exception. If we have an
|
|
// uncatchable exception that's not propagating a debug mode forced
|
|
// return, return.
|
|
if (!interruptRv && !cx->isExceptionPending() && !cx->isPropagatingForcedReturn())
|
|
return false;
|
|
|
|
JS::AutoSaveExceptionState savedExc(cx);
|
|
|
|
FixedInvokeArgs<1> iargs(cx);
|
|
|
|
iargs[0].setBoolean(interruptRv);
|
|
|
|
RootedValue rv(cx);
|
|
if (!js::Call(cx, args[0], UndefinedHandleValue, iargs, &rv))
|
|
return false;
|
|
|
|
args.rval().setUndefined();
|
|
return interruptRv;
|
|
}
|
|
|
|
static bool
|
|
SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
RootedValue value(cx, args[0]);
|
|
if (!value.isObject() || !value.toObject().is<JSFunction>()) {
|
|
JS_ReportErrorASCII(cx, "Argument must be a function");
|
|
return false;
|
|
}
|
|
GetShellContext(cx)->interruptFunc = value;
|
|
GetShellContext(cx)->haveInterruptFunc = true;
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
EnableLastWarning(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
sc->lastWarningEnabled = true;
|
|
sc->lastWarning.setNull();
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DisableLastWarning(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
sc->lastWarningEnabled = false;
|
|
sc->lastWarning.setNull();
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GetLastWarning(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!sc->lastWarningEnabled) {
|
|
JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
|
|
return false;
|
|
}
|
|
|
|
if (!JS_WrapValue(cx, &sc->lastWarning))
|
|
return false;
|
|
|
|
args.rval().set(sc->lastWarning);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ClearLastWarning(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!sc->lastWarningEnabled) {
|
|
JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
|
|
return false;
|
|
}
|
|
|
|
sc->lastWarning.setNull();
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static bool
|
|
StackDump(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (!gOutFile->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
bool showArgs = ToBoolean(args.get(0));
|
|
bool showLocals = ToBoolean(args.get(1));
|
|
bool showThisProps = ToBoolean(args.get(2));
|
|
|
|
char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps);
|
|
if (!buf) {
|
|
fputs("Failed to format JavaScript stack for dump\n", gOutFile->fp);
|
|
JS_ClearPendingException(cx);
|
|
} else {
|
|
fputs(buf, gOutFile->fp);
|
|
JS_smprintf_free(buf);
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static bool
|
|
StackPointerInfo(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
// Copy the truncated stack pointer to the result. This value is not used
|
|
// as a pointer but as a way to measure frame-size from JS.
|
|
args.rval().setInt32(int32_t(reinterpret_cast<size_t>(&args) & 0xfffffff));
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
Elapsed(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() == 0) {
|
|
double d = PRMJ_Now() - GetShellContext(cx)->startTime;
|
|
args.rval().setDouble(d);
|
|
return true;
|
|
}
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
Compile(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() < 1) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
|
|
"compile", "0", "s");
|
|
return false;
|
|
}
|
|
if (!args[0].isString()) {
|
|
const char* typeName = InformalValueTypeName(args[0]);
|
|
JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
|
|
JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
|
|
if (!scriptContents)
|
|
return false;
|
|
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, scriptContents))
|
|
return false;
|
|
|
|
JS::CompileOptions options(cx);
|
|
options.setIntroductionType("js shell compile")
|
|
.setFileAndLine("<string>", 1)
|
|
.setIsRunOnce(true)
|
|
.setNoScriptRval(true);
|
|
RootedScript script(cx);
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
bool ok = JS_CompileUCScript(cx, chars, scriptContents->length(), options, &script);
|
|
args.rval().setUndefined();
|
|
return ok;
|
|
}
|
|
|
|
static bool
|
|
ParseModule(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() == 0) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
|
|
"parseModule", "0", "s");
|
|
return false;
|
|
}
|
|
|
|
if (!args[0].isString()) {
|
|
const char* typeName = InformalValueTypeName(args[0]);
|
|
JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
|
|
if (!scriptContents)
|
|
return false;
|
|
|
|
UniqueChars filename;
|
|
CompileOptions options(cx);
|
|
if (args.length() > 1) {
|
|
if (!args[1].isString()) {
|
|
const char* typeName = InformalValueTypeName(args[1]);
|
|
JS_ReportErrorASCII(cx, "expected filename string, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
RootedString str(cx, args[1].toString());
|
|
filename.reset(JS_EncodeString(cx, str));
|
|
if (!filename)
|
|
return false;
|
|
|
|
options.setFileAndLine(filename.get(), 1);
|
|
} else {
|
|
options.setFileAndLine("<string>", 1);
|
|
}
|
|
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, scriptContents))
|
|
return false;
|
|
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
SourceBufferHolder srcBuf(chars, scriptContents->length(),
|
|
SourceBufferHolder::NoOwnership);
|
|
|
|
RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
|
|
if (!module)
|
|
return false;
|
|
|
|
args.rval().setObject(*module);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
|
|
"setModuleResolveHook", "0", "s");
|
|
return false;
|
|
}
|
|
|
|
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
|
|
const char* typeName = InformalValueTypeName(args[0]);
|
|
JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
RootedFunction hook(cx, &args[0].toObject().as<JSFunction>());
|
|
Rooted<GlobalObject*> global(cx, cx->global());
|
|
global->setModuleResolveHook(hook);
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
args.rval().setString(JS_NewStringCopyZ(cx, moduleLoadPath));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Parse(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
using namespace js::frontend;
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() < 1) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
|
|
"parse", "0", "s");
|
|
return false;
|
|
}
|
|
if (!args[0].isString()) {
|
|
const char* typeName = InformalValueTypeName(args[0]);
|
|
JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
|
|
if (!scriptContents)
|
|
return false;
|
|
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, scriptContents))
|
|
return false;
|
|
|
|
size_t length = scriptContents->length();
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("js shell parse")
|
|
.setFileAndLine("<string>", 1);
|
|
UsedNameTracker usedNames(cx);
|
|
if (!usedNames.init())
|
|
return false;
|
|
Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars, length,
|
|
/* foldConstants = */ true, usedNames, nullptr, nullptr);
|
|
if (!parser.checkOptions())
|
|
return false;
|
|
|
|
ParseNode* pn = parser.parse();
|
|
if (!pn)
|
|
return false;
|
|
#ifdef DEBUG
|
|
DumpParseTree(pn);
|
|
fputc('\n', stderr);
|
|
#endif
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
SyntaxParse(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
using namespace js::frontend;
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() < 1) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
|
|
"parse", "0", "s");
|
|
return false;
|
|
}
|
|
if (!args[0].isString()) {
|
|
const char* typeName = InformalValueTypeName(args[0]);
|
|
JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx);
|
|
if (!scriptContents)
|
|
return false;
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("js shell syntaxParse")
|
|
.setFileAndLine("<string>", 1);
|
|
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, scriptContents))
|
|
return false;
|
|
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
size_t length = scriptContents->length();
|
|
UsedNameTracker usedNames(cx);
|
|
if (!usedNames.init())
|
|
return false;
|
|
Parser<frontend::SyntaxParseHandler> parser(cx, cx->tempLifoAlloc(),
|
|
options, chars, length, false,
|
|
usedNames, nullptr, nullptr);
|
|
if (!parser.checkOptions())
|
|
return false;
|
|
|
|
bool succeeded = parser.parse();
|
|
if (cx->isExceptionPending())
|
|
return false;
|
|
|
|
if (!succeeded && !parser.hadAbortedSyntaxParse()) {
|
|
// If no exception is posted, either there was an OOM or a language
|
|
// feature unhandled by the syntax parser was encountered.
|
|
MOZ_ASSERT(cx->runtime()->hadOutOfMemory);
|
|
return false;
|
|
}
|
|
|
|
args.rval().setBoolean(succeeded);
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
OffThreadCompileScriptCallback(void* token, void* callbackData)
|
|
{
|
|
ShellContext* sc = static_cast<ShellContext*>(callbackData);
|
|
sc->offThreadState.markDone(token);
|
|
}
|
|
|
|
static bool
|
|
OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
if (!CanUseExtraThreads()) {
|
|
JS_ReportErrorASCII(cx, "Can't use offThreadCompileScript with --no-threads");
|
|
return false;
|
|
}
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() < 1) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
|
|
"offThreadCompileScript", "0", "s");
|
|
return false;
|
|
}
|
|
if (!args[0].isString()) {
|
|
const char* typeName = InformalValueTypeName(args[0]);
|
|
JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
|
|
return false;
|
|
}
|
|
|
|
JSAutoByteString fileNameBytes;
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("js shell offThreadCompileScript")
|
|
.setFileAndLine("<string>", 1);
|
|
|
|
if (args.length() >= 2) {
|
|
if (args[1].isPrimitive()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"evaluate");
|
|
return false;
|
|
}
|
|
|
|
RootedObject opts(cx, &args[1].toObject());
|
|
if (!ParseCompileOptions(cx, options, opts, fileNameBytes))
|
|
return false;
|
|
}
|
|
|
|
// These option settings must override whatever the caller requested.
|
|
options.setIsRunOnce(true)
|
|
.setSourceIsLazy(false);
|
|
|
|
// We assume the caller wants caching if at all possible, ignoring
|
|
// heuristics that make sense for a real browser.
|
|
options.forceAsync = true;
|
|
|
|
JSString* scriptContents = args[0].toString();
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, scriptContents))
|
|
return false;
|
|
|
|
size_t length = scriptContents->length();
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
|
|
// Make sure we own the string's chars, so that they are not freed before
|
|
// the compilation is finished.
|
|
ScopedJSFreePtr<char16_t> ownedChars;
|
|
if (stableChars.maybeGiveOwnershipToCaller()) {
|
|
ownedChars = const_cast<char16_t*>(chars);
|
|
} else {
|
|
char16_t* copy = cx->pod_malloc<char16_t>(length);
|
|
if (!copy)
|
|
return false;
|
|
|
|
mozilla::PodCopy(copy, chars, length);
|
|
ownedChars = copy;
|
|
chars = copy;
|
|
}
|
|
|
|
if (!JS::CanCompileOffThread(cx, options, length)) {
|
|
JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
|
|
return false;
|
|
}
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
if (!sc->offThreadState.startIfIdle(cx, ScriptKind::Script, ownedChars)) {
|
|
JS_ReportErrorASCII(cx, "called offThreadCompileScript without calling runOffThreadScript"
|
|
" to receive prior off-thread compilation");
|
|
return false;
|
|
}
|
|
|
|
if (!JS::CompileOffThread(cx, options, chars, length,
|
|
OffThreadCompileScriptCallback, sc))
|
|
{
|
|
sc->offThreadState.abandon(cx);
|
|
return false;
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
runOffThreadScript(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (OffThreadParsingMustWaitForGC(cx))
|
|
gc::FinishGC(cx);
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
void* token = sc->offThreadState.waitUntilDone(cx, ScriptKind::Script);
|
|
if (!token) {
|
|
JS_ReportErrorASCII(cx, "called runOffThreadScript when no compilation is pending");
|
|
return false;
|
|
}
|
|
|
|
RootedScript script(cx, JS::FinishOffThreadScript(cx, token));
|
|
if (!script)
|
|
return false;
|
|
|
|
return JS_ExecuteScript(cx, script, args.rval());
|
|
}
|
|
|
|
static bool
|
|
OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1 || !args[0].isString()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"offThreadCompileModule");
|
|
return false;
|
|
}
|
|
|
|
JSAutoByteString fileNameBytes;
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("js shell offThreadCompileModule")
|
|
.setFileAndLine("<string>", 1);
|
|
options.setIsRunOnce(true)
|
|
.setSourceIsLazy(false);
|
|
options.forceAsync = true;
|
|
|
|
JSString* scriptContents = args[0].toString();
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, scriptContents))
|
|
return false;
|
|
|
|
size_t length = scriptContents->length();
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
|
|
// Make sure we own the string's chars, so that they are not freed before
|
|
// the compilation is finished.
|
|
ScopedJSFreePtr<char16_t> ownedChars;
|
|
if (stableChars.maybeGiveOwnershipToCaller()) {
|
|
ownedChars = const_cast<char16_t*>(chars);
|
|
} else {
|
|
char16_t* copy = cx->pod_malloc<char16_t>(length);
|
|
if (!copy)
|
|
return false;
|
|
|
|
mozilla::PodCopy(copy, chars, length);
|
|
ownedChars = copy;
|
|
chars = copy;
|
|
}
|
|
|
|
if (!JS::CanCompileOffThread(cx, options, length)) {
|
|
JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
|
|
return false;
|
|
}
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
if (!sc->offThreadState.startIfIdle(cx, ScriptKind::Module, ownedChars)) {
|
|
JS_ReportErrorASCII(cx, "called offThreadCompileModule without receiving prior off-thread "
|
|
"compilation");
|
|
return false;
|
|
}
|
|
|
|
if (!JS::CompileOffThreadModule(cx, options, chars, length,
|
|
OffThreadCompileScriptCallback, sc))
|
|
{
|
|
sc->offThreadState.abandon(cx);
|
|
return false;
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (OffThreadParsingMustWaitForGC(cx))
|
|
gc::FinishGC(cx);
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
void* token = sc->offThreadState.waitUntilDone(cx, ScriptKind::Module);
|
|
if (!token) {
|
|
JS_ReportErrorASCII(cx, "called finishOffThreadModule when no compilation is pending");
|
|
return false;
|
|
}
|
|
|
|
RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
|
|
if (!module)
|
|
return false;
|
|
|
|
args.rval().setObject(*module);
|
|
return true;
|
|
}
|
|
|
|
struct MOZ_RAII FreeOnReturn
|
|
{
|
|
JSContext* cx;
|
|
const char* ptr;
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
|
|
explicit FreeOnReturn(JSContext* cx, const char* ptr = nullptr
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
|
: cx(cx), ptr(ptr)
|
|
{
|
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
|
}
|
|
|
|
void init(const char* ptr) {
|
|
MOZ_ASSERT(!this->ptr);
|
|
this->ptr = ptr;
|
|
}
|
|
|
|
~FreeOnReturn() {
|
|
JS_free(cx, (void*)ptr);
|
|
}
|
|
};
|
|
|
|
static int sArgc;
|
|
static char** sArgv;
|
|
|
|
class AutoCStringVector
|
|
{
|
|
Vector<char*> argv_;
|
|
public:
|
|
explicit AutoCStringVector(JSContext* cx) : argv_(cx) {}
|
|
~AutoCStringVector() {
|
|
for (size_t i = 0; i < argv_.length(); i++)
|
|
js_free(argv_[i]);
|
|
}
|
|
bool append(char* arg) {
|
|
if (!argv_.append(arg)) {
|
|
js_free(arg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
char* const* get() const {
|
|
return argv_.begin();
|
|
}
|
|
size_t length() const {
|
|
return argv_.length();
|
|
}
|
|
char* operator[](size_t i) const {
|
|
return argv_[i];
|
|
}
|
|
void replace(size_t i, char* arg) {
|
|
js_free(argv_[i]);
|
|
argv_[i] = arg;
|
|
}
|
|
char* back() const {
|
|
return argv_.back();
|
|
}
|
|
void replaceBack(char* arg) {
|
|
js_free(argv_.back());
|
|
argv_.back() = arg;
|
|
}
|
|
};
|
|
|
|
#if defined(XP_WIN)
|
|
static bool
|
|
EscapeForShell(AutoCStringVector& argv)
|
|
{
|
|
// Windows will break arguments in argv by various spaces, so we wrap each
|
|
// argument in quotes and escape quotes within. Even with quotes, \ will be
|
|
// treated like an escape character, so inflate each \ to \\.
|
|
|
|
for (size_t i = 0; i < argv.length(); i++) {
|
|
if (!argv[i])
|
|
continue;
|
|
|
|
size_t newLen = 3; // quotes before and after and null-terminator
|
|
for (char* p = argv[i]; *p; p++) {
|
|
newLen++;
|
|
if (*p == '\"' || *p == '\\')
|
|
newLen++;
|
|
}
|
|
|
|
char* escaped = (char*)js_malloc(newLen);
|
|
if (!escaped)
|
|
return false;
|
|
|
|
char* src = argv[i];
|
|
char* dst = escaped;
|
|
*dst++ = '\"';
|
|
while (*src) {
|
|
if (*src == '\"' || *src == '\\')
|
|
*dst++ = '\\';
|
|
*dst++ = *src++;
|
|
}
|
|
*dst++ = '\"';
|
|
*dst++ = '\0';
|
|
MOZ_ASSERT(escaped + newLen == dst);
|
|
|
|
argv.replace(i, escaped);
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static Vector<const char*, 4, js::SystemAllocPolicy> sPropagatedFlags;
|
|
|
|
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
|
|
static bool
|
|
PropagateFlagToNestedShells(const char* flag)
|
|
{
|
|
return sPropagatedFlags.append(flag);
|
|
}
|
|
#endif
|
|
|
|
static bool
|
|
NestedShell(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
AutoCStringVector argv(cx);
|
|
|
|
// The first argument to the shell is its path, which we assume is our own
|
|
// argv[0].
|
|
if (sArgc < 1) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
|
|
return false;
|
|
}
|
|
if (!argv.append(strdup(sArgv[0])))
|
|
return false;
|
|
|
|
// Propagate selected flags from the current shell
|
|
for (unsigned i = 0; i < sPropagatedFlags.length(); i++) {
|
|
char* cstr = strdup(sPropagatedFlags[i]);
|
|
if (!cstr || !argv.append(cstr))
|
|
return false;
|
|
}
|
|
|
|
// The arguments to nestedShell are stringified and append to argv.
|
|
RootedString str(cx);
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
str = ToString(cx, args[i]);
|
|
if (!str || !argv.append(JS_EncodeString(cx, str)))
|
|
return false;
|
|
|
|
// As a special case, if the caller passes "--js-cache", replace that
|
|
// with "--js-cache=$(jsCacheDir)"
|
|
if (!strcmp(argv.back(), "--js-cache") && jsCacheDir) {
|
|
char* newArg = JS_smprintf("--js-cache=%s", jsCacheDir);
|
|
if (!newArg)
|
|
return false;
|
|
argv.replaceBack(newArg);
|
|
}
|
|
}
|
|
|
|
// execv assumes argv is null-terminated
|
|
if (!argv.append(nullptr))
|
|
return false;
|
|
|
|
int status = 0;
|
|
#if defined(XP_WIN)
|
|
if (!EscapeForShell(argv))
|
|
return false;
|
|
status = _spawnv(_P_WAIT, sArgv[0], argv.get());
|
|
#else
|
|
pid_t pid = fork();
|
|
switch (pid) {
|
|
case -1:
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
|
|
return false;
|
|
case 0:
|
|
(void)execv(sArgv[0], argv.get());
|
|
exit(-1);
|
|
default: {
|
|
while (waitpid(pid, &status, 0) < 0 && errno == EINTR)
|
|
continue;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (status != 0) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL);
|
|
return false;
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DecompileFunction(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() < 1 || !args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
|
|
JSString* result = JS_DecompileFunction(cx, fun, 0);
|
|
if (!result)
|
|
return false;
|
|
args.rval().setString(result);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DecompileThisScript(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
NonBuiltinScriptFrameIter iter(cx);
|
|
if (iter.done()) {
|
|
args.rval().setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
|
|
{
|
|
JSAutoCompartment ac(cx, iter.script());
|
|
|
|
RootedScript script(cx, iter.script());
|
|
JSString* result = JS_DecompileScript(cx, script, "test", 0);
|
|
if (!result)
|
|
return false;
|
|
|
|
args.rval().setString(result);
|
|
}
|
|
|
|
return JS_WrapValue(cx, args.rval());
|
|
}
|
|
|
|
static bool
|
|
ThisFilename(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
JS::AutoFilename filename;
|
|
if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) {
|
|
args.rval().setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
|
|
JSString* str = JS_NewStringCopyZ(cx, filename.get());
|
|
if (!str)
|
|
return false;
|
|
|
|
args.rval().setString(str);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
WrapWithProto(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
Value obj = UndefinedValue(), proto = UndefinedValue();
|
|
if (args.length() == 2) {
|
|
obj = args[0];
|
|
proto = args[1];
|
|
}
|
|
if (!obj.isObject() || !proto.isObjectOrNull()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"wrapWithProto");
|
|
return false;
|
|
}
|
|
|
|
WrapperOptions options(cx);
|
|
options.setProto(proto.toObjectOrNull());
|
|
JSObject* wrapped = Wrapper::New(cx, &obj.toObject(),
|
|
&Wrapper::singletonWithPrototype, options);
|
|
if (!wrapped)
|
|
return false;
|
|
|
|
args.rval().setObject(*wrapped);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
NewGlobal(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
JSPrincipals* principals = nullptr;
|
|
|
|
JS::CompartmentOptions options;
|
|
JS::CompartmentCreationOptions& creationOptions = options.creationOptions();
|
|
JS::CompartmentBehaviors& behaviors = options.behaviors();
|
|
|
|
SetStandardCompartmentOptions(options);
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() == 1 && args[0].isObject()) {
|
|
RootedObject opts(cx, &args[0].toObject());
|
|
RootedValue v(cx);
|
|
|
|
if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v))
|
|
return false;
|
|
if (v.isBoolean())
|
|
creationOptions.setInvisibleToDebugger(v.toBoolean());
|
|
|
|
if (!JS_GetProperty(cx, opts, "cloneSingletons", &v))
|
|
return false;
|
|
if (v.isBoolean())
|
|
creationOptions.setCloneSingletons(v.toBoolean());
|
|
|
|
if (!JS_GetProperty(cx, opts, "sameZoneAs", &v))
|
|
return false;
|
|
if (v.isObject())
|
|
creationOptions.setSameZoneAs(UncheckedUnwrap(&v.toObject()));
|
|
|
|
if (!JS_GetProperty(cx, opts, "disableLazyParsing", &v))
|
|
return false;
|
|
if (v.isBoolean())
|
|
behaviors.setDisableLazyParsing(v.toBoolean());
|
|
|
|
if (!JS_GetProperty(cx, opts, "principal", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
uint32_t bits;
|
|
if (!ToUint32(cx, v, &bits))
|
|
return false;
|
|
principals = cx->new_<ShellPrincipals>(bits);
|
|
if (!principals)
|
|
return false;
|
|
JS_HoldPrincipals(principals);
|
|
}
|
|
}
|
|
|
|
RootedObject global(cx, NewGlobalObject(cx, options, principals));
|
|
if (principals)
|
|
JS_DropPrincipals(cx, principals);
|
|
if (!global)
|
|
return false;
|
|
|
|
if (!JS_WrapObject(cx, &global))
|
|
return false;
|
|
|
|
args.rval().setObject(*global);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
NukeCCW(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1 || !args[0].isObject() ||
|
|
!IsCrossCompartmentWrapper(&args[0].toObject()))
|
|
{
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"nukeCCW");
|
|
return false;
|
|
}
|
|
|
|
NukeCrossCompartmentWrapper(cx, &args[0].toObject());
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GetMaxArgs(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
args.rval().setInt32(ARGS_LENGTH_MAX);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ObjectEmulatingUndefined(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
static const JSClass cls = {
|
|
"ObjectEmulatingUndefined",
|
|
JSCLASS_EMULATES_UNDEFINED
|
|
};
|
|
|
|
RootedObject obj(cx, JS_NewObject(cx, &cls));
|
|
if (!obj)
|
|
return false;
|
|
args.rval().setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GetSelfHostedValue(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1 || !args[0].isString()) {
|
|
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
|
|
"getSelfHostedValue");
|
|
return false;
|
|
}
|
|
RootedAtom srcAtom(cx, ToAtom<CanGC>(cx, args[0]));
|
|
if (!srcAtom)
|
|
return false;
|
|
RootedPropertyName srcName(cx, srcAtom->asPropertyName());
|
|
return cx->runtime()->cloneSelfHostedValue(cx, srcName, args.rval());
|
|
}
|
|
|
|
class ShellSourceHook: public SourceHook {
|
|
// The function we should call to lazily retrieve source code.
|
|
PersistentRootedFunction fun;
|
|
|
|
public:
|
|
ShellSourceHook(JSContext* cx, JSFunction& fun) : fun(cx, &fun) {}
|
|
|
|
bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) {
|
|
RootedString str(cx, JS_NewStringCopyZ(cx, filename));
|
|
if (!str)
|
|
return false;
|
|
RootedValue filenameValue(cx, StringValue(str));
|
|
|
|
RootedValue result(cx);
|
|
if (!Call(cx, UndefinedHandleValue, fun, HandleValueArray(filenameValue), &result))
|
|
return false;
|
|
|
|
str = JS::ToString(cx, result);
|
|
if (!str)
|
|
return false;
|
|
|
|
*length = JS_GetStringLength(str);
|
|
*src = cx->pod_malloc<char16_t>(*length);
|
|
if (!*src)
|
|
return false;
|
|
|
|
JSLinearString* linear = str->ensureLinear(cx);
|
|
if (!linear)
|
|
return false;
|
|
|
|
CopyChars(*src, *linear);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
static bool
|
|
WithSourceHook(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject callee(cx, &args.callee());
|
|
|
|
if (args.length() != 2) {
|
|
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
|
|
return false;
|
|
}
|
|
|
|
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()
|
|
|| !args[1].isObject() || !args[1].toObject().is<JSFunction>()) {
|
|
ReportUsageErrorASCII(cx, callee, "First and second arguments must be functions.");
|
|
return false;
|
|
}
|
|
|
|
mozilla::UniquePtr<ShellSourceHook> hook =
|
|
mozilla::MakeUnique<ShellSourceHook>(cx, args[0].toObject().as<JSFunction>());
|
|
if (!hook)
|
|
return false;
|
|
|
|
mozilla::UniquePtr<SourceHook> savedHook = js::ForgetSourceHook(cx);
|
|
js::SetSourceHook(cx, Move(hook));
|
|
|
|
RootedObject fun(cx, &args[1].toObject());
|
|
bool result = Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), args.rval());
|
|
js::SetSourceHook(cx, Move(savedHook));
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
IsCachingEnabled(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
args.rval().setBoolean(jsCachingEnabled && jsCacheAsmJSPath != nullptr);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
SetCachingEnabled(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (GetShellContext(cx)->isWorker) {
|
|
JS_ReportErrorASCII(cx, "Caching is not supported in workers");
|
|
return false;
|
|
}
|
|
|
|
jsCachingEnabled = ToBoolean(args.get(0));
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
PrintProfilerEvents_Callback(const char* msg)
|
|
{
|
|
fprintf(stderr, "PROFILER EVENT: %s\n", msg);
|
|
}
|
|
|
|
static bool
|
|
PrintProfilerEvents(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (cx->runtime()->spsProfiler.enabled())
|
|
js::RegisterContextProfilingEventMarker(cx, &PrintProfilerEvents_Callback);
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
|
|
typedef Vector<char16_t, 0, SystemAllocPolicy> StackChars;
|
|
Vector<StackChars, 0, SystemAllocPolicy> stacks;
|
|
|
|
static void
|
|
SingleStepCallback(void* arg, jit::Simulator* sim, void* pc)
|
|
{
|
|
JSContext* cx = reinterpret_cast<JSContext*>(arg);
|
|
|
|
// If profiling is not enabled, don't do anything.
|
|
if (!cx->spsProfiler.enabled())
|
|
return;
|
|
|
|
JS::ProfilingFrameIterator::RegisterState state;
|
|
state.pc = pc;
|
|
#if defined(JS_SIMULATOR_ARM)
|
|
state.sp = (void*)sim->get_register(jit::Simulator::sp);
|
|
state.lr = (void*)sim->get_register(jit::Simulator::lr);
|
|
#elif defined(JS_SIMULATOR_MIPS64)
|
|
state.sp = (void*)sim->getRegister(jit::Simulator::sp);
|
|
state.lr = (void*)sim->getRegister(jit::Simulator::ra);
|
|
#endif
|
|
|
|
mozilla::DebugOnly<void*> lastStackAddress = nullptr;
|
|
StackChars stack;
|
|
uint32_t frameNo = 0;
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
|
|
MOZ_ASSERT(i.stackAddress() != nullptr);
|
|
MOZ_ASSERT(lastStackAddress <= i.stackAddress());
|
|
lastStackAddress = i.stackAddress();
|
|
JS::ProfilingFrameIterator::Frame frames[16];
|
|
uint32_t nframes = i.extractStack(frames, 0, 16);
|
|
for (uint32_t i = 0; i < nframes; i++) {
|
|
if (frameNo > 0) {
|
|
if (!stack.append(",", 1))
|
|
oomUnsafe.crash("stack.append");
|
|
}
|
|
auto chars = frames[i].label.get();
|
|
if (!stack.append(chars, strlen(chars)))
|
|
oomUnsafe.crash("stack.append");
|
|
frameNo++;
|
|
}
|
|
}
|
|
|
|
// Only append the stack if it differs from the last stack.
|
|
if (stacks.empty() ||
|
|
stacks.back().length() != stack.length() ||
|
|
!PodEqual(stacks.back().begin(), stack.begin(), stack.length()))
|
|
{
|
|
if (!stacks.append(Move(stack)))
|
|
oomUnsafe.crash("stacks.append");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static bool
|
|
EnableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
jit::Simulator* sim = cx->runtime()->simulator();
|
|
sim->enable_single_stepping(SingleStepCallback, cx);
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
#else
|
|
JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
DisableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
jit::Simulator* sim = cx->runtime()->simulator();
|
|
sim->disable_single_stepping();
|
|
|
|
AutoValueVector elems(cx);
|
|
for (size_t i = 0; i < stacks.length(); i++) {
|
|
JSString* stack = JS_NewUCStringCopyN(cx, stacks[i].begin(), stacks[i].length());
|
|
if (!stack)
|
|
return false;
|
|
if (!elems.append(StringValue(stack)))
|
|
return false;
|
|
}
|
|
|
|
JSObject* array = JS_NewArrayObject(cx, elems);
|
|
if (!array)
|
|
return false;
|
|
|
|
stacks.clear();
|
|
args.rval().setObject(*array);
|
|
return true;
|
|
#else
|
|
JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
IsLatin1(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
bool isLatin1 = args.get(0).isString() && args[0].toString()->hasLatin1Chars();
|
|
args.rval().setBoolean(isLatin1);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
EnableSPSProfiling(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
// Disable before re-enabling; see the assertion in |SPSProfiler::setProfilingStack|.
|
|
if (cx->spsProfiler.installed())
|
|
cx->spsProfiler.enable(false);
|
|
|
|
SetContextProfilingStack(cx, sc->spsProfilingStack, &sc->spsProfilingStackSize,
|
|
ShellContext::SpsProfilingMaxStackSize);
|
|
cx->spsProfiler.enableSlowAssertions(false);
|
|
cx->spsProfiler.enable(true);
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
EnableSPSProfilingWithSlowAssertions(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
args.rval().setUndefined();
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
if (cx->spsProfiler.enabled()) {
|
|
// If profiling already enabled with slow assertions disabled,
|
|
// this is a no-op.
|
|
if (cx->spsProfiler.slowAssertionsEnabled())
|
|
return true;
|
|
|
|
// Slow assertions are off. Disable profiling before re-enabling
|
|
// with slow assertions on.
|
|
cx->spsProfiler.enable(false);
|
|
}
|
|
|
|
// Disable before re-enabling; see the assertion in |SPSProfiler::setProfilingStack|.
|
|
if (cx->spsProfiler.installed())
|
|
cx->spsProfiler.enable(false);
|
|
|
|
SetContextProfilingStack(cx, sc->spsProfilingStack, &sc->spsProfilingStackSize,
|
|
ShellContext::SpsProfilingMaxStackSize);
|
|
cx->spsProfiler.enableSlowAssertions(true);
|
|
cx->spsProfiler.enable(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DisableSPSProfiling(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (cx->runtime()->spsProfiler.installed())
|
|
cx->runtime()->spsProfiler.enable(false);
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// Global mailbox that is used to communicate a SharedArrayBuffer
|
|
// value from one worker to another.
|
|
//
|
|
// For simplicity we store only the SharedArrayRawBuffer; retaining
|
|
// the SAB object would require per-runtime storage, and would have no
|
|
// real benefits.
|
|
//
|
|
// Invariant: when a SARB is in the mailbox its reference count is at
|
|
// least 1, accounting for the reference from the mailbox.
|
|
//
|
|
// The lock guards the mailbox variable and prevents a race where two
|
|
// workers try to set the mailbox at the same time to replace a SARB
|
|
// that is only referenced from the mailbox: the workers will both
|
|
// decrement the reference count on the old SARB, and one of those
|
|
// decrements will be on a garbage object. We could implement this
|
|
// with atomics and a CAS loop but it's not worth the bother.
|
|
|
|
static Mutex* sharedArrayBufferMailboxLock;
|
|
static SharedArrayRawBuffer* sharedArrayBufferMailbox;
|
|
|
|
static bool
|
|
InitSharedArrayBufferMailbox()
|
|
{
|
|
sharedArrayBufferMailboxLock = js_new<Mutex>(mutexid::ShellArrayBufferMailbox);
|
|
return sharedArrayBufferMailboxLock != nullptr;
|
|
}
|
|
|
|
static void
|
|
DestructSharedArrayBufferMailbox()
|
|
{
|
|
// All workers need to have terminated at this point.
|
|
if (sharedArrayBufferMailbox)
|
|
sharedArrayBufferMailbox->dropReference();
|
|
js_delete(sharedArrayBufferMailboxLock);
|
|
}
|
|
|
|
static bool
|
|
GetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
JSObject* newObj = nullptr;
|
|
bool rval = true;
|
|
|
|
sharedArrayBufferMailboxLock->lock();
|
|
SharedArrayRawBuffer* buf = sharedArrayBufferMailbox;
|
|
if (buf) {
|
|
buf->addReference();
|
|
// Shared memory is enabled globally in the shell: there can't be a worker
|
|
// that does not enable it if the main thread has it.
|
|
MOZ_ASSERT(cx->compartment()->creationOptions().getSharedMemoryAndAtomicsEnabled());
|
|
newObj = SharedArrayBufferObject::New(cx, buf);
|
|
if (!newObj) {
|
|
buf->dropReference();
|
|
rval = false;
|
|
}
|
|
}
|
|
sharedArrayBufferMailboxLock->unlock();
|
|
|
|
args.rval().setObjectOrNull(newObj);
|
|
return rval;
|
|
}
|
|
|
|
static bool
|
|
SetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
SharedArrayRawBuffer* newBuffer = nullptr;
|
|
|
|
if (argc == 0 || args.get(0).isNullOrUndefined()) {
|
|
// Clear out the mailbox
|
|
}
|
|
else if (args.get(0).isObject() && args[0].toObject().is<SharedArrayBufferObject>()) {
|
|
newBuffer = args[0].toObject().as<SharedArrayBufferObject>().rawBufferObject();
|
|
newBuffer->addReference();
|
|
} else {
|
|
JS_ReportErrorASCII(cx, "Only a SharedArrayBuffer can be installed in the global mailbox");
|
|
return false;
|
|
}
|
|
|
|
sharedArrayBufferMailboxLock->lock();
|
|
SharedArrayRawBuffer* oldBuffer = sharedArrayBufferMailbox;
|
|
if (oldBuffer)
|
|
oldBuffer->dropReference();
|
|
sharedArrayBufferMailbox = newBuffer;
|
|
sharedArrayBufferMailboxLock->unlock();
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
class SprintOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
|
|
{
|
|
Sprinter* sp;
|
|
bool startedTypes_;
|
|
bool hadError_;
|
|
|
|
public:
|
|
explicit SprintOptimizationTypeInfoOp(Sprinter* sp)
|
|
: sp(sp),
|
|
startedTypes_(false),
|
|
hadError_(false)
|
|
{ }
|
|
|
|
void readType(const char* keyedBy, const char* name,
|
|
const char* location, Maybe<unsigned> lineno) override
|
|
{
|
|
if (hadError_)
|
|
return;
|
|
|
|
do {
|
|
if (!startedTypes_) {
|
|
startedTypes_ = true;
|
|
if (sp->put("{\"typeset\": [") < 0)
|
|
break;
|
|
}
|
|
|
|
if (!sp->jsprintf("{\"keyedBy\":\"%s\"", keyedBy))
|
|
break;
|
|
|
|
if (name) {
|
|
if (!sp->jsprintf(",\"name\":\"%s\"", name))
|
|
break;
|
|
}
|
|
|
|
if (location) {
|
|
char buf[512];
|
|
PutEscapedString(buf, mozilla::ArrayLength(buf), location, strlen(location), '"');
|
|
if (!sp->jsprintf(",\"location\":%s", buf))
|
|
break;
|
|
}
|
|
if (lineno.isSome()) {
|
|
if (!sp->jsprintf(",\"line\":%u", *lineno))
|
|
break;
|
|
}
|
|
if (sp->put("},") < 0)
|
|
break;
|
|
|
|
return;
|
|
} while (false);
|
|
|
|
hadError_ = true;
|
|
}
|
|
|
|
void operator()(JS::TrackedTypeSite site, const char* mirType) override {
|
|
if (hadError_)
|
|
return;
|
|
|
|
do {
|
|
if (startedTypes_) {
|
|
// Clear trailing ,
|
|
if ((*sp)[sp->getOffset() - 1] == ',')
|
|
(*sp)[sp->getOffset() - 1] = ' ';
|
|
if (sp->put("],") < 0)
|
|
break;
|
|
|
|
startedTypes_ = false;
|
|
} else {
|
|
if (sp->put("{") < 0)
|
|
break;
|
|
}
|
|
|
|
if (!sp->jsprintf("\"site\":\"%s\",\"mirType\":\"%s\"},",
|
|
TrackedTypeSiteString(site), mirType))
|
|
{
|
|
break;
|
|
}
|
|
|
|
return;
|
|
} while (false);
|
|
|
|
hadError_ = true;
|
|
}
|
|
|
|
bool hadError() const {
|
|
return hadError_;
|
|
}
|
|
};
|
|
|
|
class SprintOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
|
|
{
|
|
Sprinter* sp;
|
|
bool hadError_;
|
|
|
|
public:
|
|
explicit SprintOptimizationAttemptsOp(Sprinter* sp)
|
|
: sp(sp), hadError_(false)
|
|
{ }
|
|
|
|
void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
|
|
if (hadError_)
|
|
return;
|
|
|
|
hadError_ = !sp->jsprintf("{\"strategy\":\"%s\",\"outcome\":\"%s\"},",
|
|
TrackedStrategyString(strategy), TrackedOutcomeString(outcome));
|
|
}
|
|
|
|
bool hadError() const {
|
|
return hadError_;
|
|
}
|
|
};
|
|
|
|
static bool
|
|
ReflectTrackedOptimizations(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject callee(cx, &args.callee());
|
|
JSRuntime* rt = cx->runtime();
|
|
|
|
if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(rt)) {
|
|
JS_ReportErrorASCII(cx, "Optimization tracking is off.");
|
|
return false;
|
|
}
|
|
|
|
if (args.length() != 1) {
|
|
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
|
|
ReportUsageErrorASCII(cx, callee, "Argument must be a function");
|
|
return false;
|
|
}
|
|
|
|
RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
|
|
if (!fun->hasScript() || !fun->nonLazyScript()->hasIonScript()) {
|
|
args.rval().setNull();
|
|
return true;
|
|
}
|
|
|
|
// Suppress GC for the unrooted JitcodeGlobalEntry below.
|
|
gc::AutoSuppressGC suppress(cx);
|
|
|
|
jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
|
jit::IonScript* ion = fun->nonLazyScript()->ionScript();
|
|
jit::JitcodeGlobalEntry& entry = table->lookupInfallible(ion->method()->raw());
|
|
|
|
if (!entry.hasTrackedOptimizations()) {
|
|
JSObject* obj = JS_NewPlainObject(cx);
|
|
if (!obj)
|
|
return false;
|
|
args.rval().setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
Sprinter sp(cx);
|
|
if (!sp.init())
|
|
return false;
|
|
|
|
const jit::IonTrackedOptimizationsRegionTable* regions =
|
|
entry.ionEntry().trackedOptimizationsRegionTable();
|
|
|
|
if (sp.put("{\"regions\": [") < 0)
|
|
return false;
|
|
|
|
for (uint32_t i = 0; i < regions->numEntries(); i++) {
|
|
jit::IonTrackedOptimizationsRegion region = regions->entry(i);
|
|
jit::IonTrackedOptimizationsRegion::RangeIterator iter = region.ranges();
|
|
while (iter.more()) {
|
|
uint32_t startOffset, endOffset;
|
|
uint8_t index;
|
|
iter.readNext(&startOffset, &endOffset, &index);
|
|
|
|
JSScript* script;
|
|
jsbytecode* pc;
|
|
// Use endOffset, as startOffset may be associated with a
|
|
// previous, adjacent region ending exactly at startOffset. That
|
|
// is, suppose we have two regions [0, startOffset], [startOffset,
|
|
// endOffset]. Since we are not querying a return address, we want
|
|
// the second region and not the first.
|
|
uint8_t* addr = ion->method()->raw() + endOffset;
|
|
entry.youngestFrameLocationAtAddr(rt, addr, &script, &pc);
|
|
|
|
if (!sp.jsprintf("{\"location\":\"%s:%" PRIuSIZE "\",\"offset\":%" PRIuSIZE ",\"index\":%u}%s",
|
|
script->filename(), script->lineno(), script->pcToOffset(pc), index,
|
|
iter.more() ? "," : ""))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sp.put("],") < 0)
|
|
return false;
|
|
|
|
if (sp.put("\"opts\": [") < 0)
|
|
return false;
|
|
|
|
for (uint8_t i = 0; i < entry.ionEntry().numOptimizationAttempts(); i++) {
|
|
if (!sp.jsprintf("%s{\"typeinfo\":[", i == 0 ? "" : ","))
|
|
return false;
|
|
|
|
SprintOptimizationTypeInfoOp top(&sp);
|
|
jit::IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(top);
|
|
entry.trackedOptimizationTypeInfo(i).forEach(adapter, entry.allTrackedTypes());
|
|
if (top.hadError())
|
|
return false;
|
|
|
|
// Clear the trailing ,
|
|
if (sp[sp.getOffset() - 1] == ',')
|
|
sp[sp.getOffset() - 1] = ' ';
|
|
|
|
if (sp.put("],\"attempts\":[") < 0)
|
|
return false;
|
|
|
|
SprintOptimizationAttemptsOp aop(&sp);
|
|
entry.trackedOptimizationAttempts(i).forEach(aop);
|
|
if (aop.hadError())
|
|
return false;
|
|
|
|
// Clear the trailing ,
|
|
if (sp[sp.getOffset() - 1] == ',')
|
|
sp[sp.getOffset() - 1] = ' ';
|
|
|
|
if (sp.put("]}") < 0)
|
|
return false;
|
|
}
|
|
|
|
if (sp.put("]}") < 0)
|
|
return false;
|
|
|
|
if (sp.hadOutOfMemory())
|
|
return false;
|
|
|
|
RootedString str(cx, JS_NewStringCopyZ(cx, sp.string()));
|
|
if (!str)
|
|
return false;
|
|
RootedValue jsonVal(cx);
|
|
if (!JS_ParseJSON(cx, str, &jsonVal))
|
|
return false;
|
|
|
|
args.rval().set(jsonVal);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DumpScopeChain(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject callee(cx, &args.callee());
|
|
|
|
if (args.length() != 1) {
|
|
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
if (!args[0].isObject() ||
|
|
!(args[0].toObject().is<JSFunction>() || args[0].toObject().is<ModuleObject>()))
|
|
{
|
|
ReportUsageErrorASCII(cx, callee, "Argument must be an interpreted function or a module");
|
|
return false;
|
|
}
|
|
|
|
RootedObject obj(cx, &args[0].toObject());
|
|
RootedScript script(cx);
|
|
|
|
if (obj->is<JSFunction>()) {
|
|
RootedFunction fun(cx, &obj->as<JSFunction>());
|
|
if (!fun->isInterpreted()) {
|
|
ReportUsageErrorASCII(cx, callee, "Argument must be an interpreted function");
|
|
return false;
|
|
}
|
|
script = JSFunction::getOrCreateScript(cx, fun);
|
|
} else {
|
|
script = obj->as<ModuleObject>().script();
|
|
}
|
|
|
|
script->bodyScope()->dump();
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
namespace js {
|
|
namespace shell {
|
|
|
|
class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
|
|
Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
|
|
bool oom;
|
|
bool enteredWithoutExit;
|
|
|
|
public:
|
|
explicit ShellAutoEntryMonitor(JSContext *cx)
|
|
: AutoEntryMonitor(cx),
|
|
oom(false),
|
|
enteredWithoutExit(false)
|
|
{ }
|
|
|
|
~ShellAutoEntryMonitor() {
|
|
MOZ_ASSERT(!enteredWithoutExit);
|
|
}
|
|
|
|
void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
|
|
const char* asyncCause) override {
|
|
MOZ_ASSERT(!enteredWithoutExit);
|
|
enteredWithoutExit = true;
|
|
|
|
RootedString displayId(cx, JS_GetFunctionDisplayId(function));
|
|
if (displayId) {
|
|
UniqueChars displayIdStr(JS_EncodeStringToUTF8(cx, displayId));
|
|
if (!displayIdStr) {
|
|
// We report OOM in buildResult.
|
|
cx->recoverFromOutOfMemory();
|
|
oom = true;
|
|
return;
|
|
}
|
|
oom = !log.append(mozilla::Move(displayIdStr));
|
|
return;
|
|
}
|
|
|
|
oom = !log.append(DuplicateString("anonymous"));
|
|
}
|
|
|
|
void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
|
|
const char* asyncCause) override {
|
|
MOZ_ASSERT(!enteredWithoutExit);
|
|
enteredWithoutExit = true;
|
|
|
|
UniqueChars label(JS_smprintf("eval:%s", JS_GetScriptFilename(script)));
|
|
oom = !label || !log.append(mozilla::Move(label));
|
|
}
|
|
|
|
void Exit(JSContext* cx) override {
|
|
MOZ_ASSERT(enteredWithoutExit);
|
|
enteredWithoutExit = false;
|
|
}
|
|
|
|
bool buildResult(JSContext *cx, MutableHandleValue resultValue) {
|
|
if (oom) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
RootedObject result(cx, JS_NewArrayObject(cx, log.length()));
|
|
if (!result)
|
|
return false;
|
|
|
|
for (size_t i = 0; i < log.length(); i++) {
|
|
char *name = log[i].get();
|
|
RootedString string(cx, Atomize(cx, name, strlen(name)));
|
|
if (!string)
|
|
return false;
|
|
RootedValue value(cx, StringValue(string));
|
|
if (!JS_SetElement(cx, result, i, value))
|
|
return false;
|
|
}
|
|
|
|
resultValue.setObject(*result.get());
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} // namespace shell
|
|
} // namespace js
|
|
|
|
static bool
|
|
EntryPoints(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
RootedObject opts(cx, ToObject(cx, args[0]));
|
|
if (!opts)
|
|
return false;
|
|
|
|
// { function: f } --- Call f.
|
|
{
|
|
RootedValue fun(cx), dummy(cx);
|
|
|
|
if (!JS_GetProperty(cx, opts, "function", &fun))
|
|
return false;
|
|
if (!fun.isUndefined()) {
|
|
js::shell::ShellAutoEntryMonitor sarep(cx);
|
|
if (!Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), &dummy))
|
|
return false;
|
|
return sarep.buildResult(cx, args.rval());
|
|
}
|
|
}
|
|
|
|
// { object: o, property: p, value: v } --- Fetch o[p], or if
|
|
// v is present, assign o[p] = v.
|
|
{
|
|
RootedValue objectv(cx), propv(cx), valuev(cx);
|
|
|
|
if (!JS_GetProperty(cx, opts, "object", &objectv) ||
|
|
!JS_GetProperty(cx, opts, "property", &propv))
|
|
return false;
|
|
if (!objectv.isUndefined() && !propv.isUndefined()) {
|
|
RootedObject object(cx, ToObject(cx, objectv));
|
|
if (!object)
|
|
return false;
|
|
|
|
RootedString string(cx, ToString(cx, propv));
|
|
if (!string)
|
|
return false;
|
|
RootedId id(cx);
|
|
if (!JS_StringToId(cx, string, &id))
|
|
return false;
|
|
|
|
if (!JS_GetProperty(cx, opts, "value", &valuev))
|
|
return false;
|
|
|
|
js::shell::ShellAutoEntryMonitor sarep(cx);
|
|
|
|
if (!valuev.isUndefined()) {
|
|
if (!JS_SetPropertyById(cx, object, id, valuev))
|
|
return false;
|
|
} else {
|
|
if (!JS_GetPropertyById(cx, object, id, &valuev))
|
|
return false;
|
|
}
|
|
|
|
return sarep.buildResult(cx, args.rval());
|
|
}
|
|
}
|
|
|
|
// { ToString: v } --- Apply JS::ToString to v.
|
|
{
|
|
RootedValue v(cx);
|
|
|
|
if (!JS_GetProperty(cx, opts, "ToString", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
js::shell::ShellAutoEntryMonitor sarep(cx);
|
|
if (!JS::ToString(cx, v))
|
|
return false;
|
|
return sarep.buildResult(cx, args.rval());
|
|
}
|
|
}
|
|
|
|
// { ToNumber: v } --- Apply JS::ToNumber to v.
|
|
{
|
|
RootedValue v(cx);
|
|
double dummy;
|
|
|
|
if (!JS_GetProperty(cx, opts, "ToNumber", &v))
|
|
return false;
|
|
if (!v.isUndefined()) {
|
|
js::shell::ShellAutoEntryMonitor sarep(cx);
|
|
if (!JS::ToNumber(cx, v, &dummy))
|
|
return false;
|
|
return sarep.buildResult(cx, args.rval());
|
|
}
|
|
}
|
|
|
|
// { eval: code } --- Apply ToString and then Evaluate to code.
|
|
{
|
|
RootedValue code(cx), dummy(cx);
|
|
|
|
if (!JS_GetProperty(cx, opts, "eval", &code))
|
|
return false;
|
|
if (!code.isUndefined()) {
|
|
RootedString codeString(cx, ToString(cx, code));
|
|
if (!codeString || !codeString->ensureFlat(cx))
|
|
return false;
|
|
|
|
AutoStableStringChars stableChars(cx);
|
|
if (!stableChars.initTwoByte(cx, codeString))
|
|
return false;
|
|
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
|
size_t length = codeString->length();
|
|
|
|
CompileOptions options(cx);
|
|
options.setIntroductionType("entryPoint eval")
|
|
.setFileAndLine("entryPoint eval", 1);
|
|
|
|
js::shell::ShellAutoEntryMonitor sarep(cx);
|
|
if (!JS::Evaluate(cx, options, chars, length, &dummy))
|
|
return false;
|
|
return sarep.buildResult(cx, args.rval());
|
|
}
|
|
}
|
|
|
|
JS_ReportErrorASCII(cx, "bad 'params' object");
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
SetARMHwCapFlags(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() != 1) {
|
|
JS_ReportErrorASCII(cx, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
RootedString flagsListString(cx, JS::ToString(cx, args.get(0)));
|
|
if (!flagsListString)
|
|
return false;
|
|
|
|
#if defined(JS_CODEGEN_ARM)
|
|
JSAutoByteString flagsList(cx, flagsListString);
|
|
if (!flagsList)
|
|
return false;
|
|
|
|
jit::ParseARMHwCapFlags(flagsList.ptr());
|
|
#endif
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
#ifndef __AFL_HAVE_MANUAL_CONTROL
|
|
# define __AFL_LOOP(x) true
|
|
#endif
|
|
|
|
static bool
|
|
WasmLoop(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject callee(cx, &args.callee());
|
|
|
|
if (args.length() < 1 || args.length() > 2) {
|
|
ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
|
|
return false;
|
|
}
|
|
|
|
if (!args[0].isString()) {
|
|
ReportUsageErrorASCII(cx, callee, "First argument must be a String");
|
|
return false;
|
|
}
|
|
|
|
RootedObject importObj(cx);
|
|
if (!args.get(1).isUndefined()) {
|
|
if (!args.get(1).isObject()) {
|
|
ReportUsageErrorASCII(cx, callee, "Second argument, if present, must be an Object");
|
|
return false;
|
|
}
|
|
importObj = &args[1].toObject();
|
|
}
|
|
|
|
RootedString givenPath(cx, args[0].toString());
|
|
RootedString filename(cx, ResolvePath(cx, givenPath, RootRelative));
|
|
if (!filename)
|
|
return false;
|
|
|
|
while (__AFL_LOOP(1000)) {
|
|
Rooted<JSObject*> ret(cx, FileAsTypedArray(cx, filename));
|
|
if (!ret)
|
|
return false;
|
|
|
|
Rooted<TypedArrayObject*> typedArray(cx, &ret->as<TypedArrayObject>());
|
|
RootedWasmInstanceObject instanceObj(cx);
|
|
if (!wasm::Eval(cx, typedArray, importObj, &instanceObj)) {
|
|
// Clear any pending exceptions, we don't care about them
|
|
cx->clearPendingException();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static const JSFunctionSpecWithHelp shell_functions[] = {
|
|
JS_FN_HELP("version", Version, 0, 0,
|
|
"version([number])",
|
|
" Get or force a script compilation version number."),
|
|
|
|
JS_FN_HELP("options", Options, 0, 0,
|
|
"options([option ...])",
|
|
" Get or toggle JavaScript options."),
|
|
|
|
JS_FN_HELP("load", Load, 1, 0,
|
|
"load(['foo.js' ...])",
|
|
" Load files named by string arguments. Filename is relative to the\n"
|
|
" current working directory."),
|
|
|
|
JS_FN_HELP("loadRelativeToScript", LoadScriptRelativeToScript, 1, 0,
|
|
"loadRelativeToScript(['foo.js' ...])",
|
|
" Load files named by string arguments. Filename is relative to the\n"
|
|
" calling script."),
|
|
|
|
JS_FN_HELP("evaluate", Evaluate, 2, 0,
|
|
"evaluate(code[, options])",
|
|
" Evaluate code as though it were the contents of a file.\n"
|
|
" options is an optional object that may have these properties:\n"
|
|
" isRunOnce: use the isRunOnce compiler option (default: false)\n"
|
|
" noScriptRval: use the no-script-rval compiler option (default: false)\n"
|
|
" fileName: filename for error messages and debug info\n"
|
|
" lineNumber: starting line number for error messages and debug info\n"
|
|
" columnNumber: starting column number for error messages and debug info\n"
|
|
" global: global in which to execute the code\n"
|
|
" newContext: if true, create and use a new cx (default: false)\n"
|
|
" catchTermination: if true, catch termination (failure without\n"
|
|
" an exception value, as for slow scripts or out-of-memory)\n"
|
|
" and return 'terminated'\n"
|
|
" element: if present with value |v|, convert |v| to an object |o| and\n"
|
|
" mark the source as being attached to the DOM element |o|. If the\n"
|
|
" property is omitted or |v| is null, don't attribute the source to\n"
|
|
" any DOM element.\n"
|
|
" elementAttributeName: if present and not undefined, the name of\n"
|
|
" property of 'element' that holds this code. This is what\n"
|
|
" Debugger.Source.prototype.elementAttributeName returns.\n"
|
|
" sourceMapURL: if present with value |v|, convert |v| to a string, and\n"
|
|
" provide that as the code's source map URL. If omitted, attach no\n"
|
|
" source map URL to the code (although the code may provide one itself,\n"
|
|
" via a //#sourceMappingURL comment).\n"
|
|
" sourceIsLazy: if present and true, indicates that, after compilation, \n"
|
|
"script source should not be cached by the JS engine and should be \n"
|
|
"lazily loaded from the embedding as-needed.\n"
|
|
" loadBytecode: if true, and if the source is a CacheEntryObject,\n"
|
|
" the bytecode would be loaded and decoded from the cache entry instead\n"
|
|
" of being parsed, then it would be executed as usual.\n"
|
|
" saveBytecode: if true, and if the source is a CacheEntryObject,\n"
|
|
" the bytecode would be encoded and saved into the cache entry after\n"
|
|
" the script execution.\n"
|
|
" assertEqBytecode: if true, and if both loadBytecode and saveBytecode are \n"
|
|
" true, then the loaded bytecode and the encoded bytecode are compared.\n"
|
|
" and an assertion is raised if they differ.\n"
|
|
),
|
|
|
|
JS_FN_HELP("run", Run, 1, 0,
|
|
"run('foo.js')",
|
|
" Run the file named by the first argument, returning the number of\n"
|
|
" of milliseconds spent compiling and executing it."),
|
|
|
|
JS_FN_HELP("readline", ReadLine, 0, 0,
|
|
"readline()",
|
|
" Read a single line from stdin."),
|
|
|
|
JS_FN_HELP("readlineBuf", ReadLineBuf, 1, 0,
|
|
"readlineBuf([ buf ])",
|
|
" Emulate readline() on the specified string. The first call with a string\n"
|
|
" argument sets the source buffer. Subsequent calls without an argument\n"
|
|
" then read from this buffer line by line.\n"),
|
|
|
|
JS_FN_HELP("print", Print, 0, 0,
|
|
"print([exp ...])",
|
|
" Evaluate and print expressions to stdout."),
|
|
|
|
JS_FN_HELP("printErr", PrintErr, 0, 0,
|
|
"printErr([exp ...])",
|
|
" Evaluate and print expressions to stderr."),
|
|
|
|
JS_FN_HELP("putstr", PutStr, 0, 0,
|
|
"putstr([exp])",
|
|
" Evaluate and print expression without newline."),
|
|
|
|
JS_FN_HELP("dateNow", Now, 0, 0,
|
|
"dateNow()",
|
|
" Return the current time with sub-ms precision."),
|
|
|
|
JS_FN_HELP("help", Help, 0, 0,
|
|
"help([name ...])",
|
|
" Display usage and help messages."),
|
|
|
|
JS_FN_HELP("quit", Quit, 0, 0,
|
|
"quit()",
|
|
" Quit the shell."),
|
|
|
|
JS_FN_HELP("assertEq", AssertEq, 2, 0,
|
|
"assertEq(actual, expected[, msg])",
|
|
" Throw if the first two arguments are not the same (both +0 or both -0,\n"
|
|
" both NaN, or non-zero and ===)."),
|
|
|
|
JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0,
|
|
"startTimingMutator()",
|
|
" Start accounting time to mutator vs GC."),
|
|
|
|
JS_FN_HELP("stopTimingMutator", StopTimingMutator, 0, 0,
|
|
"stopTimingMutator()",
|
|
" Stop accounting time to mutator vs GC and dump the results."),
|
|
|
|
JS_FN_HELP("throwError", ThrowError, 0, 0,
|
|
"throwError()",
|
|
" Throw an error from JS_ReportError."),
|
|
|
|
#ifdef DEBUG
|
|
JS_FN_HELP("disassemble", DisassembleToString, 1, 0,
|
|
"disassemble([fun/code])",
|
|
" Return the disassembly for the given function or code.\n"
|
|
" All disassembly functions take these options as leading string arguments:\n"
|
|
" \"-r\" (disassemble recursively)\n"
|
|
" \"-l\" (show line numbers)\n"
|
|
" \"-S\" (omit source notes)"),
|
|
|
|
JS_FN_HELP("dis", Disassemble, 1, 0,
|
|
"dis([fun/code])",
|
|
" Disassemble functions into bytecodes."),
|
|
|
|
JS_FN_HELP("disfile", DisassFile, 1, 0,
|
|
"disfile('foo.js')",
|
|
" Disassemble script file into bytecodes.\n"),
|
|
|
|
JS_FN_HELP("dissrc", DisassWithSrc, 1, 0,
|
|
"dissrc([fun/code])",
|
|
" Disassemble functions with source lines."),
|
|
|
|
JS_FN_HELP("notes", Notes, 1, 0,
|
|
"notes([fun])",
|
|
" Show source notes for functions."),
|
|
|
|
JS_FN_HELP("stackDump", StackDump, 3, 0,
|
|
"stackDump(showArgs, showLocals, showThisProps)",
|
|
" Tries to print a lot of information about the current stack. \n"
|
|
" Similar to the DumpJSStack() function in the browser."),
|
|
|
|
#endif
|
|
JS_FN_HELP("intern", Intern, 1, 0,
|
|
"intern(str)",
|
|
" Internalize str in the atom table."),
|
|
|
|
JS_FN_HELP("getslx", GetSLX, 1, 0,
|
|
"getslx(obj)",
|
|
" Get script line extent."),
|
|
|
|
JS_FN_HELP("evalcx", EvalInContext, 1, 0,
|
|
"evalcx(s[, o])",
|
|
" Evaluate s in optional sandbox object o.\n"
|
|
" if (s == '' && !o) return new o with eager standard classes\n"
|
|
" if (s == 'lazy' && !o) return new o with lazy standard classes"),
|
|
|
|
JS_FN_HELP("evalInWorker", EvalInWorker, 1, 0,
|
|
"evalInWorker(str)",
|
|
" Evaluate 'str' in a separate thread with its own runtime.\n"),
|
|
|
|
JS_FN_HELP("getSharedArrayBuffer", GetSharedArrayBuffer, 0, 0,
|
|
"getSharedArrayBuffer()",
|
|
" Retrieve the SharedArrayBuffer object from the cross-worker mailbox.\n"
|
|
" The object retrieved may not be identical to the object that was\n"
|
|
" installed, but it references the same shared memory.\n"
|
|
" getSharedArrayBuffer performs an ordering memory barrier.\n"),
|
|
|
|
JS_FN_HELP("setSharedArrayBuffer", SetSharedArrayBuffer, 0, 0,
|
|
"setSharedArrayBuffer()",
|
|
" Install the SharedArrayBuffer object in the cross-worker mailbox.\n"
|
|
" setSharedArrayBuffer performs an ordering memory barrier.\n"),
|
|
|
|
JS_FN_HELP("shapeOf", ShapeOf, 1, 0,
|
|
"shapeOf(obj)",
|
|
" Get the shape of obj (an implementation detail)."),
|
|
|
|
JS_FN_HELP("groupOf", GroupOf, 1, 0,
|
|
"groupOf(obj)",
|
|
" Get the group of obj (an implementation detail)."),
|
|
|
|
JS_FN_HELP("unwrappedObjectsHaveSameShape", UnwrappedObjectsHaveSameShape, 2, 0,
|
|
"unwrappedObjectsHaveSameShape(obj1, obj2)",
|
|
" Returns true iff obj1 and obj2 have the same shape, false otherwise. Both\n"
|
|
" objects are unwrapped first, so this can be used on objects from different\n"
|
|
" globals."),
|
|
|
|
#ifdef DEBUG
|
|
JS_FN_HELP("arrayInfo", ArrayInfo, 1, 0,
|
|
"arrayInfo(a1, a2, ...)",
|
|
" Report statistics about arrays."),
|
|
#endif
|
|
|
|
JS_FN_HELP("sleep", Sleep_fn, 1, 0,
|
|
"sleep(dt)",
|
|
" Sleep for dt seconds."),
|
|
|
|
JS_FN_HELP("compile", Compile, 1, 0,
|
|
"compile(code)",
|
|
" Compiles a string to bytecode, potentially throwing."),
|
|
|
|
JS_FN_HELP("parseModule", ParseModule, 1, 0,
|
|
"parseModule(code)",
|
|
" Parses source text as a module and returns a Module object."),
|
|
|
|
JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0,
|
|
"setModuleResolveHook(function(module, specifier) {})",
|
|
" Set the HostResolveImportedModule hook to |function|.\n"
|
|
" This hook is used to look up a previously loaded module object. It should\n"
|
|
" be implemented by the module loader."),
|
|
|
|
JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0,
|
|
"getModuleLoadPath()",
|
|
" Return any --module-load-path argument passed to the shell. Used by the\n"
|
|
" module loader.\n"),
|
|
|
|
JS_FN_HELP("parse", Parse, 1, 0,
|
|
"parse(code)",
|
|
" Parses a string, potentially throwing."),
|
|
|
|
JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
|
|
"syntaxParse(code)",
|
|
" Check the syntax of a string, returning success value"),
|
|
|
|
JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
|
|
"offThreadCompileScript(code[, options])",
|
|
" Compile |code| on a helper thread. To wait for the compilation to finish\n"
|
|
" and run the code, call |runOffThreadScript|. If present, |options| may\n"
|
|
" have properties saying how the code should be compiled:\n"
|
|
" noScriptRval: use the no-script-rval compiler option (default: false)\n"
|
|
" fileName: filename for error messages and debug info\n"
|
|
" lineNumber: starting line number for error messages and debug info\n"
|
|
" columnNumber: starting column number for error messages and debug info\n"
|
|
" element: if present with value |v|, convert |v| to an object |o| and\n"
|
|
" mark the source as being attached to the DOM element |o|. If the\n"
|
|
" property is omitted or |v| is null, don't attribute the source to\n"
|
|
" any DOM element.\n"
|
|
" elementAttributeName: if present and not undefined, the name of\n"
|
|
" property of 'element' that holds this code. This is what\n"
|
|
" Debugger.Source.prototype.elementAttributeName returns.\n"),
|
|
|
|
JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
|
|
"runOffThreadScript()",
|
|
" Wait for off-thread compilation to complete. If an error occurred,\n"
|
|
" throw the appropriate exception; otherwise, run the script and return\n"
|
|
" its value."),
|
|
|
|
JS_FN_HELP("offThreadCompileModule", OffThreadCompileModule, 1, 0,
|
|
"offThreadCompileModule(code)",
|
|
" Compile |code| on a helper thread. To wait for the compilation to finish\n"
|
|
" and get the module object, call |finishOffThreadModule|."),
|
|
|
|
JS_FN_HELP("finishOffThreadModule", FinishOffThreadModule, 0, 0,
|
|
"finishOffThreadModule()",
|
|
" Wait for off-thread compilation to complete. If an error occurred,\n"
|
|
" throw the appropriate exception; otherwise, return the module object"),
|
|
|
|
JS_FN_HELP("timeout", Timeout, 1, 0,
|
|
"timeout([seconds], [func])",
|
|
" Get/Set the limit in seconds for the execution time for the current context.\n"
|
|
" A negative value (default) means that the execution time is unlimited.\n"
|
|
" If a second argument is provided, it will be invoked when the timer elapses.\n"
|
|
" Calling this function will replace any callback set by |setInterruptCallback|.\n"),
|
|
|
|
JS_FN_HELP("interruptIf", InterruptIf, 1, 0,
|
|
"interruptIf(cond)",
|
|
" Requests interrupt callback if cond is true. If a callback function is set via\n"
|
|
" |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."),
|
|
|
|
JS_FN_HELP("invokeInterruptCallback", InvokeInterruptCallbackWrapper, 0, 0,
|
|
"invokeInterruptCallback(fun)",
|
|
" Forcefully set the interrupt flag and invoke the interrupt handler. If a\n"
|
|
" callback function is set via |timeout| or |setInterruptCallback|, it will\n"
|
|
" be called. Before returning, fun is called with the return value of the\n"
|
|
" interrupt handler."),
|
|
|
|
JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0,
|
|
"setInterruptCallback(func)",
|
|
" Sets func as the interrupt callback function.\n"
|
|
" Calling this function will replace any callback set by |timeout|.\n"),
|
|
|
|
JS_FN_HELP("enableLastWarning", EnableLastWarning, 0, 0,
|
|
"enableLastWarning()",
|
|
" Enable storing the last warning."),
|
|
|
|
JS_FN_HELP("disableLastWarning", DisableLastWarning, 0, 0,
|
|
"disableLastWarning()",
|
|
" Disable storing the last warning."),
|
|
|
|
JS_FN_HELP("getLastWarning", GetLastWarning, 0, 0,
|
|
"getLastWarning()",
|
|
" Returns an object that represents the last warning."),
|
|
|
|
JS_FN_HELP("clearLastWarning", ClearLastWarning, 0, 0,
|
|
"clearLastWarning()",
|
|
" Clear the last warning."),
|
|
|
|
JS_FN_HELP("elapsed", Elapsed, 0, 0,
|
|
"elapsed()",
|
|
" Execution time elapsed for the current thread."),
|
|
|
|
JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0,
|
|
"decompileFunction(func)",
|
|
" Decompile a function."),
|
|
|
|
JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0,
|
|
"decompileThis()",
|
|
" Decompile the currently executing script."),
|
|
|
|
JS_FN_HELP("thisFilename", ThisFilename, 0, 0,
|
|
"thisFilename()",
|
|
" Return the filename of the current script"),
|
|
|
|
JS_FN_HELP("newGlobal", NewGlobal, 1, 0,
|
|
"newGlobal([options])",
|
|
" Return a new global object in a new compartment. If options\n"
|
|
" is given, it may have any of the following properties:\n"
|
|
" sameZoneAs: the compartment will be in the same zone as the given object (defaults to a new zone)\n"
|
|
" invisibleToDebugger: the global will be invisible to the debugger (default false)\n"
|
|
" principal: if present, its value converted to a number must be an\n"
|
|
" integer that fits in 32 bits; use that as the new compartment's\n"
|
|
" principal. Shell principals are toys, meant only for testing; one\n"
|
|
" shell principal subsumes another if its set bits are a superset of\n"
|
|
" the other's. Thus, a principal of 0 subsumes nothing, while a\n"
|
|
" principals of ~0 subsumes all other principals. The absence of a\n"
|
|
" principal is treated as if its bits were 0xffff, for subsumption\n"
|
|
" purposes. If this property is omitted, supply no principal."),
|
|
|
|
JS_FN_HELP("nukeCCW", NukeCCW, 1, 0,
|
|
"nukeCCW(wrapper)",
|
|
" Nuke a CrossCompartmentWrapper, which turns it into a DeadProxyObject."),
|
|
|
|
JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0,
|
|
"createMappedArrayBuffer(filename, [offset, [size]])",
|
|
" Create an array buffer that mmaps the given file."),
|
|
|
|
JS_FN_HELP("addPromiseReactions", AddPromiseReactions, 3, 0,
|
|
"addPromiseReactions(promise, onResolve, onReject)",
|
|
" Calls the JS::AddPromiseReactions JSAPI function with the given arguments."),
|
|
|
|
JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
|
|
"getMaxArgs()",
|
|
" Return the maximum number of supported args for a call."),
|
|
|
|
JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0,
|
|
"objectEmulatingUndefined()",
|
|
" Return a new object obj for which typeof obj === \"undefined\", obj == null\n"
|
|
" and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"),
|
|
|
|
JS_FN_HELP("isCachingEnabled", IsCachingEnabled, 0, 0,
|
|
"isCachingEnabled()",
|
|
" Return whether JS caching is enabled."),
|
|
|
|
JS_FN_HELP("setCachingEnabled", SetCachingEnabled, 1, 0,
|
|
"setCachingEnabled(b)",
|
|
" Enable or disable JS caching."),
|
|
|
|
JS_FN_HELP("cacheEntry", CacheEntry, 1, 0,
|
|
"cacheEntry(code)",
|
|
" Return a new opaque object which emulates a cache entry of a script. This\n"
|
|
" object encapsulates the code and its cached content. The cache entry is filled\n"
|
|
" and read by the \"evaluate\" function by using it in place of the source, and\n"
|
|
" by setting \"saveBytecode\" and \"loadBytecode\" options."),
|
|
|
|
JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0,
|
|
"printProfilerEvents()",
|
|
" Register a callback with the profiler that prints javascript profiler events\n"
|
|
" to stderr. Callback is only registered if profiling is enabled."),
|
|
|
|
JS_FN_HELP("enableSingleStepProfiling", EnableSingleStepProfiling, 0, 0,
|
|
"enableSingleStepProfiling()",
|
|
" This function will fail on platforms that don't support single-step profiling\n"
|
|
" (currently everything but ARM-simulator). When enabled, at every instruction a\n"
|
|
" backtrace will be recorded and stored in an array. Adjacent duplicate backtraces\n"
|
|
" are discarded."),
|
|
|
|
JS_FN_HELP("disableSingleStepProfiling", DisableSingleStepProfiling, 0, 0,
|
|
"disableSingleStepProfiling()",
|
|
" Return the array of backtraces recorded by enableSingleStepProfiling."),
|
|
|
|
JS_FN_HELP("enableSPSProfiling", EnableSPSProfiling, 0, 0,
|
|
"enableSPSProfiling()",
|
|
" Enables SPS instrumentation and corresponding assertions, with slow\n"
|
|
" assertions disabled.\n"),
|
|
|
|
JS_FN_HELP("enableSPSProfilingWithSlowAssertions", EnableSPSProfilingWithSlowAssertions, 0, 0,
|
|
"enableSPSProfilingWithSlowAssertions()",
|
|
" Enables SPS instrumentation and corresponding assertions, with slow\n"
|
|
" assertions enabled.\n"),
|
|
|
|
JS_FN_HELP("disableSPSProfiling", DisableSPSProfiling, 0, 0,
|
|
"disableSPSProfiling()",
|
|
" Disables SPS instrumentation"),
|
|
|
|
JS_FN_HELP("isLatin1", IsLatin1, 1, 0,
|
|
"isLatin1(s)",
|
|
" Return true iff the string's characters are stored as Latin1."),
|
|
|
|
JS_FN_HELP("stackPointerInfo", StackPointerInfo, 0, 0,
|
|
"stackPointerInfo()",
|
|
" Return an int32 value which corresponds to the offset of the latest stack\n"
|
|
" pointer, such that one can take the differences of 2 to estimate a frame-size."),
|
|
|
|
JS_FN_HELP("entryPoints", EntryPoints, 1, 0,
|
|
"entryPoints(params)",
|
|
"Carry out some JSAPI operation as directed by |params|, and return an array of\n"
|
|
"objects describing which JavaScript entry points were invoked as a result.\n"
|
|
"|params| is an object whose properties indicate what operation to perform. Here\n"
|
|
"are the recognized groups of properties:\n"
|
|
"\n"
|
|
"{ function }: Call the object |params.function| with no arguments.\n"
|
|
"\n"
|
|
"{ object, property }: Fetch the property named |params.property| of\n"
|
|
"|params.object|.\n"
|
|
"\n"
|
|
"{ ToString }: Apply JS::ToString to |params.toString|.\n"
|
|
"\n"
|
|
"{ ToNumber }: Apply JS::ToNumber to |params.toNumber|.\n"
|
|
"\n"
|
|
"{ eval }: Apply JS::Evaluate to |params.eval|.\n"
|
|
"\n"
|
|
"The return value is an array of strings, with one element for each\n"
|
|
"JavaScript invocation that occurred as a result of the given\n"
|
|
"operation. Each element is the name of the function invoked, or the\n"
|
|
"string 'eval:FILENAME' if the code was invoked by 'eval' or something\n"
|
|
"similar.\n"),
|
|
|
|
JS_FN_HELP("drainJobQueue", DrainJobQueue, 0, 0,
|
|
"drainJobQueue()",
|
|
"Take jobs from the shell's job queue in FIFO order and run them until the\n"
|
|
"queue is empty.\n"),
|
|
|
|
JS_FN_HELP("setPromiseRejectionTrackerCallback", SetPromiseRejectionTrackerCallback, 1, 0,
|
|
"setPromiseRejectionTrackerCallback()",
|
|
"Sets the callback to be invoked whenever a Promise rejection is unhandled\n"
|
|
"or a previously-unhandled rejection becomes handled."),
|
|
|
|
JS_FN_HELP("addIntlExtras", AddIntlExtras, 1, 0,
|
|
"addIntlExtras(obj)",
|
|
"Adds various not-yet-standardized Intl functions as properties on the\n"
|
|
"provided object (this should generally be Intl itself). The added\n"
|
|
"functions and their behavior are experimental: don't depend upon them\n"
|
|
"unless you're willing to update your code if these experimental APIs change\n"
|
|
"underneath you."),
|
|
|
|
JS_FS_HELP_END
|
|
};
|
|
|
|
static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
|
|
JS_FN_HELP("clone", Clone, 1, 0,
|
|
"clone(fun[, scope])",
|
|
" Clone function object."),
|
|
|
|
JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
|
|
"getSelfHostedValue()",
|
|
" Get a self-hosted value by its name. Note that these values don't get \n"
|
|
" cached, so repeatedly getting the same value creates multiple distinct clones."),
|
|
|
|
JS_FN_HELP("line2pc", LineToPC, 0, 0,
|
|
"line2pc([fun,] line)",
|
|
" Map line number to PC."),
|
|
|
|
JS_FN_HELP("pc2line", PCToLine, 0, 0,
|
|
"pc2line(fun[, pc])",
|
|
" Map PC to line number."),
|
|
|
|
JS_FN_HELP("nestedShell", NestedShell, 0, 0,
|
|
"nestedShell(shellArgs...)",
|
|
" Execute the given code in a new JS shell process, passing this nested shell\n"
|
|
" the arguments passed to nestedShell. argv[0] of the nested shell will be argv[0]\n"
|
|
" of the current shell (which is assumed to be the actual path to the shell.\n"
|
|
" arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n"
|
|
" be argv[2], etc."),
|
|
|
|
JS_INLINABLE_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0, TestAssertFloat32,
|
|
"assertFloat32(value, isFloat32)",
|
|
" In IonMonkey only, asserts that value has (resp. hasn't) the MIRType::Float32 if isFloat32 is true (resp. false)."),
|
|
|
|
JS_INLINABLE_FN_HELP("assertRecoveredOnBailout", testingFunc_assertRecoveredOnBailout, 2, 0,
|
|
TestAssertRecoveredOnBailout,
|
|
"assertRecoveredOnBailout(var)",
|
|
" In IonMonkey only, asserts that variable has RecoveredOnBailout flag."),
|
|
|
|
JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0,
|
|
"withSourceHook(hook, fun)",
|
|
" Set this JS runtime's lazy source retrieval hook (that is, the hook\n"
|
|
" used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n"
|
|
" |hook|; call |fun| with no arguments; and then restore the runtime's\n"
|
|
" original hook. Return or throw whatever |fun| did. |hook| gets\n"
|
|
" passed the requested code's URL, and should return a string.\n"
|
|
"\n"
|
|
" Notes:\n"
|
|
"\n"
|
|
" 1) SpiderMonkey may assert if the returned code isn't close enough\n"
|
|
" to the script's real code, so this function is not fuzzer-safe.\n"
|
|
"\n"
|
|
" 2) The runtime can have only one source retrieval hook active at a\n"
|
|
" time. If |fun| is not careful, |hook| could be asked to retrieve the\n"
|
|
" source code for compilations that occurred long before it was set,\n"
|
|
" and that it knows nothing about. The reverse applies as well: the\n"
|
|
" original hook, that we reinstate after the call to |fun| completes,\n"
|
|
" might be asked for the source code of compilations that |fun|\n"
|
|
" performed, and which, presumably, only |hook| knows how to find.\n"),
|
|
|
|
JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0,
|
|
"wrapWithProto(obj)",
|
|
" Wrap an object into a noop wrapper with prototype semantics.\n"
|
|
" Note: This is not fuzzing safe because it can be used to construct\n"
|
|
" deeply nested wrapper chains that cannot exist in the wild."),
|
|
|
|
JS_FN_HELP("trackedOpts", ReflectTrackedOptimizations, 1, 0,
|
|
"trackedOpts(fun)",
|
|
" Returns an object describing the tracked optimizations of |fun|, if\n"
|
|
" any. If |fun| is not a scripted function or has not been compiled by\n"
|
|
" Ion, null is returned."),
|
|
|
|
JS_FN_HELP("dumpScopeChain", DumpScopeChain, 1, 0,
|
|
"dumpScopeChain(obj)",
|
|
" Prints the scope chain of an interpreted function or a module."),
|
|
|
|
JS_FN_HELP("crash", Crash, 0, 0,
|
|
"crash([message])",
|
|
" Crashes the process with a MOZ_CRASH."),
|
|
|
|
JS_FN_HELP("setARMHwCapFlags", SetARMHwCapFlags, 1, 0,
|
|
"setARMHwCapFlags(\"flag1,flag2 flag3\")",
|
|
" On non-ARM, no-op. On ARM, set the hardware capabilities. The list of \n"
|
|
" flags is available by calling this function with \"help\" as the flag's name"),
|
|
|
|
JS_FN_HELP("wasmLoop", WasmLoop, 2, 0,
|
|
"wasmLoop(filename, imports)",
|
|
" Performs an AFL-style persistent loop reading data from the given file and passing it\n"
|
|
" to the 'wasmEval' function together with the specified imports object."),
|
|
|
|
JS_FS_HELP_END
|
|
};
|
|
|
|
static const JSFunctionSpecWithHelp console_functions[] = {
|
|
JS_FN_HELP("log", Print, 0, 0,
|
|
"log([exp ...])",
|
|
" Evaluate and print expressions to stdout.\n"
|
|
" This function is an alias of the print() function."),
|
|
JS_FS_HELP_END
|
|
};
|
|
|
|
bool
|
|
DefineConsole(JSContext* cx, HandleObject global)
|
|
{
|
|
RootedObject obj(cx, JS_NewPlainObject(cx));
|
|
return obj &&
|
|
JS_DefineFunctionsWithHelp(cx, obj, console_functions) &&
|
|
JS_DefineProperty(cx, global, "console", obj, 0);
|
|
}
|
|
|
|
#ifdef MOZ_PROFILING
|
|
# define PROFILING_FUNCTION_COUNT 5
|
|
# ifdef MOZ_CALLGRIND
|
|
# define CALLGRIND_FUNCTION_COUNT 3
|
|
# else
|
|
# define CALLGRIND_FUNCTION_COUNT 0
|
|
# endif
|
|
# ifdef MOZ_VTUNE
|
|
# define VTUNE_FUNCTION_COUNT 4
|
|
# else
|
|
# define VTUNE_FUNCTION_COUNT 0
|
|
# endif
|
|
# define EXTERNAL_FUNCTION_COUNT (PROFILING_FUNCTION_COUNT + CALLGRIND_FUNCTION_COUNT + VTUNE_FUNCTION_COUNT)
|
|
#else
|
|
# define EXTERNAL_FUNCTION_COUNT 0
|
|
#endif
|
|
|
|
#undef PROFILING_FUNCTION_COUNT
|
|
#undef CALLGRIND_FUNCTION_COUNT
|
|
#undef VTUNE_FUNCTION_COUNT
|
|
#undef EXTERNAL_FUNCTION_COUNT
|
|
|
|
static bool
|
|
PrintHelpString(JSContext* cx, HandleValue v)
|
|
{
|
|
JSString* str = v.toString();
|
|
MOZ_ASSERT(gOutFile->isOpen());
|
|
|
|
JSLinearString* linear = str->ensureLinear(cx);
|
|
if (!linear)
|
|
return false;
|
|
|
|
JS::AutoCheckCannotGC nogc;
|
|
if (linear->hasLatin1Chars()) {
|
|
for (const Latin1Char* p = linear->latin1Chars(nogc); *p; p++)
|
|
fprintf(gOutFile->fp, "%c", char(*p));
|
|
} else {
|
|
for (const char16_t* p = linear->twoByteChars(nogc); *p; p++)
|
|
fprintf(gOutFile->fp, "%c", char(*p));
|
|
}
|
|
fprintf(gOutFile->fp, "\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
PrintHelp(JSContext* cx, HandleObject obj)
|
|
{
|
|
RootedValue usage(cx);
|
|
if (!JS_GetProperty(cx, obj, "usage", &usage))
|
|
return false;
|
|
RootedValue help(cx);
|
|
if (!JS_GetProperty(cx, obj, "help", &help))
|
|
return false;
|
|
|
|
if (!usage.isString() || !help.isString())
|
|
return true;
|
|
|
|
return PrintHelpString(cx, usage) && PrintHelpString(cx, help);
|
|
}
|
|
|
|
static bool
|
|
PrintEnumeratedHelp(JSContext* cx, HandleObject obj, bool brief)
|
|
{
|
|
AutoIdVector idv(cx);
|
|
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv))
|
|
return false;
|
|
|
|
for (size_t i = 0; i < idv.length(); i++) {
|
|
RootedValue v(cx);
|
|
RootedId id(cx, idv[i]);
|
|
if (!JS_GetPropertyById(cx, obj, id, &v))
|
|
return false;
|
|
if (v.isObject()) {
|
|
RootedObject funcObj(cx, &v.toObject());
|
|
if (!PrintHelp(cx, funcObj))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
Help(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
if (!gOutFile->isOpen()) {
|
|
JS_ReportErrorASCII(cx, "output file is closed");
|
|
return false;
|
|
}
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedObject obj(cx);
|
|
if (args.length() == 0) {
|
|
fprintf(gOutFile->fp, "%s\n", JS_GetImplementationVersion());
|
|
|
|
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
|
|
if (!PrintEnumeratedHelp(cx, global, false))
|
|
return false;
|
|
} else {
|
|
for (unsigned i = 0; i < args.length(); i++) {
|
|
if (args[i].isPrimitive()) {
|
|
JS_ReportErrorASCII(cx, "primitive arg");
|
|
return false;
|
|
}
|
|
obj = args[i].toObjectOrNull();
|
|
if (!PrintHelp(cx, obj))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
args.rval().setUndefined();
|
|
return true;
|
|
}
|
|
|
|
static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
|
|
#define MSG_DEF(name, count, exception, format) \
|
|
{ #name, format, count, JSEXN_ERR } ,
|
|
#include "jsshell.msg"
|
|
#undef MSG_DEF
|
|
};
|
|
|
|
const JSErrorFormatString*
|
|
js::shell::my_GetErrorMessage(void* userRef, const unsigned errorNumber)
|
|
{
|
|
if (errorNumber == 0 || errorNumber >= JSShellErr_Limit)
|
|
return nullptr;
|
|
|
|
return &jsShell_ErrorFormatString[errorNumber];
|
|
}
|
|
|
|
static bool
|
|
CreateLastWarningObject(JSContext* cx, JSErrorReport* report)
|
|
{
|
|
RootedObject warningObj(cx, JS_NewObject(cx, nullptr));
|
|
if (!warningObj)
|
|
return false;
|
|
|
|
RootedString nameStr(cx);
|
|
if (report->exnType == JSEXN_WARN)
|
|
nameStr = JS_NewStringCopyZ(cx, "Warning");
|
|
else
|
|
nameStr = GetErrorTypeName(cx, report->exnType);
|
|
if (!nameStr)
|
|
return false;
|
|
RootedValue nameVal(cx, StringValue(nameStr));
|
|
if (!DefineProperty(cx, warningObj, cx->names().name, nameVal))
|
|
return false;
|
|
|
|
RootedString messageStr(cx, report->newMessageString(cx));
|
|
if (!messageStr)
|
|
return false;
|
|
RootedValue messageVal(cx, StringValue(messageStr));
|
|
if (!DefineProperty(cx, warningObj, cx->names().message, messageVal))
|
|
return false;
|
|
|
|
RootedValue linenoVal(cx, Int32Value(report->lineno));
|
|
if (!DefineProperty(cx, warningObj, cx->names().lineNumber, linenoVal))
|
|
return false;
|
|
|
|
RootedValue columnVal(cx, Int32Value(report->column));
|
|
if (!DefineProperty(cx, warningObj, cx->names().columnNumber, columnVal))
|
|
return false;
|
|
|
|
RootedObject notesArray(cx, CreateErrorNotesArray(cx, report));
|
|
if (!notesArray)
|
|
return false;
|
|
|
|
RootedValue notesArrayVal(cx, ObjectValue(*notesArray));
|
|
if (!DefineProperty(cx, warningObj, cx->names().notes, notesArrayVal))
|
|
return false;
|
|
|
|
GetShellContext(cx)->lastWarning.setObject(*warningObj);
|
|
return true;
|
|
}
|
|
|
|
static FILE*
|
|
ErrorFilePointer()
|
|
{
|
|
if (gErrFile->isOpen())
|
|
return gErrFile->fp;
|
|
|
|
fprintf(stderr, "error file is closed; falling back to stderr\n");
|
|
return stderr;
|
|
}
|
|
|
|
static bool
|
|
PrintStackTrace(JSContext* cx, HandleValue exn)
|
|
{
|
|
if (!exn.isObject())
|
|
return false;
|
|
|
|
Maybe<JSAutoCompartment> ac;
|
|
RootedObject exnObj(cx, &exn.toObject());
|
|
if (IsCrossCompartmentWrapper(exnObj)) {
|
|
exnObj = UncheckedUnwrap(exnObj);
|
|
ac.emplace(cx, exnObj);
|
|
}
|
|
|
|
// Ignore non-ErrorObject thrown by |throw| statement.
|
|
if (!exnObj->is<ErrorObject>())
|
|
return true;
|
|
|
|
// Exceptions thrown while compiling top-level script have no stack.
|
|
RootedObject stackObj(cx, exnObj->as<ErrorObject>().stack());
|
|
if (!stackObj)
|
|
return true;
|
|
|
|
RootedString stackStr(cx);
|
|
if (!BuildStackString(cx, stackObj, &stackStr, 2))
|
|
return false;
|
|
|
|
UniqueChars stack(JS_EncodeStringToUTF8(cx, stackStr));
|
|
if (!stack)
|
|
return false;
|
|
|
|
FILE* fp = ErrorFilePointer();
|
|
fputs("Stack:\n", fp);
|
|
fputs(stack.get(), fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
js::shell::AutoReportException::~AutoReportException()
|
|
{
|
|
if (!JS_IsExceptionPending(cx))
|
|
return;
|
|
|
|
// Get exception object before printing and clearing exception.
|
|
RootedValue exn(cx);
|
|
(void) JS_GetPendingException(cx, &exn);
|
|
|
|
JS_ClearPendingException(cx);
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
js::ErrorReport report(cx);
|
|
if (!report.init(cx, exn, js::ErrorReport::WithSideEffects)) {
|
|
fprintf(stderr, "out of memory initializing ErrorReport\n");
|
|
fflush(stderr);
|
|
JS_ClearPendingException(cx);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!JSREPORT_IS_WARNING(report.report()->flags));
|
|
|
|
FILE* fp = ErrorFilePointer();
|
|
PrintError(cx, fp, report.toStringResult(), report.report(), reportWarnings);
|
|
|
|
{
|
|
JS::AutoSaveExceptionState savedExc(cx);
|
|
if (!PrintStackTrace(cx, exn))
|
|
fputs("(Unable to print stack trace)\n", fp);
|
|
savedExc.restore();
|
|
}
|
|
|
|
if (report.report()->errorNumber == JSMSG_OUT_OF_MEMORY)
|
|
sc->exitCode = EXITCODE_OUT_OF_MEMORY;
|
|
else
|
|
sc->exitCode = EXITCODE_RUNTIME_ERROR;
|
|
|
|
JS_ClearPendingException(cx);
|
|
}
|
|
|
|
void
|
|
js::shell::WarningReporter(JSContext* cx, JSErrorReport* report)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
FILE* fp = ErrorFilePointer();
|
|
|
|
MOZ_ASSERT(report);
|
|
MOZ_ASSERT(JSREPORT_IS_WARNING(report->flags));
|
|
|
|
if (sc->lastWarningEnabled) {
|
|
JS::AutoSaveExceptionState savedExc(cx);
|
|
if (!CreateLastWarningObject(cx, report)) {
|
|
fputs("Unhandled error happened while creating last warning object.\n", fp);
|
|
fflush(fp);
|
|
}
|
|
savedExc.restore();
|
|
}
|
|
|
|
// Print the warning.
|
|
PrintError(cx, fp, JS::ConstUTF8CharsZ(), report, reportWarnings);
|
|
}
|
|
|
|
static bool
|
|
global_enumerate(JSContext* cx, HandleObject obj)
|
|
{
|
|
#ifdef LAZY_STANDARD_CLASSES
|
|
return JS_EnumerateStandardClasses(cx, obj);
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static bool
|
|
global_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
|
|
{
|
|
#ifdef LAZY_STANDARD_CLASSES
|
|
if (!JS_ResolveStandardClass(cx, obj, id, resolvedp))
|
|
return false;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
global_mayResolve(const JSAtomState& names, jsid id, JSObject* maybeObj)
|
|
{
|
|
return JS_MayResolveStandardClass(names, id, maybeObj);
|
|
}
|
|
|
|
static const JSClassOps global_classOps = {
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
global_enumerate, global_resolve, global_mayResolve,
|
|
nullptr,
|
|
nullptr, nullptr, nullptr,
|
|
JS_GlobalObjectTraceHook
|
|
};
|
|
|
|
static const JSClass global_class = {
|
|
"global", JSCLASS_GLOBAL_FLAGS,
|
|
&global_classOps
|
|
};
|
|
|
|
/*
|
|
* Define a FakeDOMObject constructor. It returns an object with a getter,
|
|
* setter and method with attached JitInfo. This object can be used to test
|
|
* IonMonkey DOM optimizations in the shell.
|
|
*/
|
|
static const uint32_t DOM_OBJECT_SLOT = 0;
|
|
|
|
static bool
|
|
dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
|
|
|
|
static bool
|
|
dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
|
|
|
|
static bool
|
|
dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
|
|
|
|
#ifdef DEBUG
|
|
static const JSClass* GetDomClass();
|
|
#endif
|
|
|
|
static bool
|
|
dom_get_x(JSContext* cx, HandleObject obj, void* self, JSJitGetterCallArgs args)
|
|
{
|
|
MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
|
|
MOZ_ASSERT(self == (void*)0x1234);
|
|
args.rval().set(JS_NumberValue(double(3.14)));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
dom_set_x(JSContext* cx, HandleObject obj, void* self, JSJitSetterCallArgs args)
|
|
{
|
|
MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
|
|
MOZ_ASSERT(self == (void*)0x1234);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
dom_doFoo(JSContext* cx, HandleObject obj, void* self, const JSJitMethodCallArgs& args)
|
|
{
|
|
MOZ_ASSERT(JS_GetClass(obj) == GetDomClass());
|
|
MOZ_ASSERT(self == (void*)0x1234);
|
|
|
|
/* Just return args.length(). */
|
|
args.rval().setInt32(args.length());
|
|
return true;
|
|
}
|
|
|
|
static const JSJitInfo dom_x_getterinfo = {
|
|
{ (JSJitGetterOp)dom_get_x },
|
|
{ 0 }, /* protoID */
|
|
{ 0 }, /* depth */
|
|
JSJitInfo::AliasNone, /* aliasSet */
|
|
JSJitInfo::Getter,
|
|
JSVAL_TYPE_UNKNOWN, /* returnType */
|
|
true, /* isInfallible. False in setters. */
|
|
true, /* isMovable */
|
|
true, /* isEliminatable */
|
|
false, /* isAlwaysInSlot */
|
|
false, /* isLazilyCachedInSlot */
|
|
false, /* isTypedMethod */
|
|
0 /* slotIndex */
|
|
};
|
|
|
|
static const JSJitInfo dom_x_setterinfo = {
|
|
{ (JSJitGetterOp)dom_set_x },
|
|
{ 0 }, /* protoID */
|
|
{ 0 }, /* depth */
|
|
JSJitInfo::Setter,
|
|
JSJitInfo::AliasEverything, /* aliasSet */
|
|
JSVAL_TYPE_UNKNOWN, /* returnType */
|
|
false, /* isInfallible. False in setters. */
|
|
false, /* isMovable. */
|
|
false, /* isEliminatable. */
|
|
false, /* isAlwaysInSlot */
|
|
false, /* isLazilyCachedInSlot */
|
|
false, /* isTypedMethod */
|
|
0 /* slotIndex */
|
|
};
|
|
|
|
static const JSJitInfo doFoo_methodinfo = {
|
|
{ (JSJitGetterOp)dom_doFoo },
|
|
{ 0 }, /* protoID */
|
|
{ 0 }, /* depth */
|
|
JSJitInfo::Method,
|
|
JSJitInfo::AliasEverything, /* aliasSet */
|
|
JSVAL_TYPE_UNKNOWN, /* returnType */
|
|
false, /* isInfallible. False in setters. */
|
|
false, /* isMovable */
|
|
false, /* isEliminatable */
|
|
false, /* isAlwaysInSlot */
|
|
false, /* isLazilyCachedInSlot */
|
|
false, /* isTypedMethod */
|
|
0 /* slotIndex */
|
|
};
|
|
|
|
static const JSPropertySpec dom_props[] = {
|
|
{"x",
|
|
JSPROP_SHARED | JSPROP_ENUMERATE,
|
|
{ {
|
|
{ { dom_genericGetter, &dom_x_getterinfo } },
|
|
{ { dom_genericSetter, &dom_x_setterinfo } }
|
|
} },
|
|
},
|
|
JS_PS_END
|
|
};
|
|
|
|
static const JSFunctionSpec dom_methods[] = {
|
|
JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3, JSPROP_ENUMERATE),
|
|
JS_FS_END
|
|
};
|
|
|
|
static const JSClass dom_class = {
|
|
"FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2)
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
static const JSClass* GetDomClass() {
|
|
return &dom_class;
|
|
}
|
|
#endif
|
|
|
|
static bool
|
|
dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
|
|
if (!obj)
|
|
return false;
|
|
|
|
if (JS_GetClass(obj) != &dom_class) {
|
|
args.rval().set(UndefinedValue());
|
|
return true;
|
|
}
|
|
|
|
JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
|
|
|
|
const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
|
|
MOZ_ASSERT(info->type() == JSJitInfo::Getter);
|
|
JSJitGetterOp getter = info->getter;
|
|
return getter(cx, obj, val.toPrivate(), JSJitGetterCallArgs(args));
|
|
}
|
|
|
|
static bool
|
|
dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
|
|
if (!obj)
|
|
return false;
|
|
|
|
MOZ_ASSERT(args.length() == 1);
|
|
|
|
if (JS_GetClass(obj) != &dom_class) {
|
|
args.rval().set(UndefinedValue());
|
|
return true;
|
|
}
|
|
|
|
JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
|
|
|
|
const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
|
|
MOZ_ASSERT(info->type() == JSJitInfo::Setter);
|
|
JSJitSetterOp setter = info->setter;
|
|
if (!setter(cx, obj, val.toPrivate(), JSJitSetterCallArgs(args)))
|
|
return false;
|
|
args.rval().set(UndefinedValue());
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
|
|
if (!obj)
|
|
return false;
|
|
|
|
if (JS_GetClass(obj) != &dom_class) {
|
|
args.rval().set(UndefinedValue());
|
|
return true;
|
|
}
|
|
|
|
JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
|
|
|
|
const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
|
|
MOZ_ASSERT(info->type() == JSJitInfo::Method);
|
|
JSJitMethodOp method = info->method;
|
|
return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args));
|
|
}
|
|
|
|
static void
|
|
InitDOMObject(HandleObject obj)
|
|
{
|
|
/* Fow now just initialize to a constant we can check. */
|
|
SetReservedSlot(obj, DOM_OBJECT_SLOT, PrivateValue((void*)0x1234));
|
|
}
|
|
|
|
static bool
|
|
dom_constructor(JSContext* cx, unsigned argc, JS::Value* vp)
|
|
{
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
|
|
RootedObject callee(cx, &args.callee());
|
|
RootedValue protov(cx);
|
|
if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov))
|
|
return false;
|
|
|
|
if (!protov.isObject()) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE,
|
|
"FakeDOMObject");
|
|
return false;
|
|
}
|
|
|
|
RootedObject proto(cx, &protov.toObject());
|
|
RootedObject domObj(cx, JS_NewObjectWithGivenProto(cx, &dom_class, proto));
|
|
if (!domObj)
|
|
return false;
|
|
|
|
InitDOMObject(domObj);
|
|
|
|
args.rval().setObject(*domObj);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
InstanceClassHasProtoAtDepth(const Class* clasp, uint32_t protoID, uint32_t depth)
|
|
{
|
|
/* There's only a single (fake) DOM object in the shell, so just return true. */
|
|
return true;
|
|
}
|
|
|
|
class ScopedFileDesc
|
|
{
|
|
intptr_t fd_;
|
|
public:
|
|
enum LockType { READ_LOCK, WRITE_LOCK };
|
|
ScopedFileDesc(int fd, LockType lockType)
|
|
: fd_(fd)
|
|
{
|
|
if (fd == -1)
|
|
return;
|
|
if (!jsCacheOpened.compareExchange(false, true)) {
|
|
close(fd_);
|
|
fd_ = -1;
|
|
return;
|
|
}
|
|
}
|
|
~ScopedFileDesc() {
|
|
if (fd_ == -1)
|
|
return;
|
|
MOZ_ASSERT(jsCacheOpened == true);
|
|
jsCacheOpened = false;
|
|
close(fd_);
|
|
}
|
|
operator intptr_t() const {
|
|
return fd_;
|
|
}
|
|
intptr_t forget() {
|
|
intptr_t ret = fd_;
|
|
fd_ = -1;
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
// To guard against corrupted cache files generated by previous crashes, write
|
|
// asmJSCacheCookie to the first uint32_t of the file only after the file is
|
|
// fully serialized and flushed to disk.
|
|
static const uint32_t asmJSCacheCookie = 0xabbadaba;
|
|
|
|
static bool
|
|
ShellOpenAsmJSCacheEntryForRead(HandleObject global, const char16_t* begin, const char16_t* limit,
|
|
size_t* serializedSizeOut, const uint8_t** memoryOut,
|
|
intptr_t* handleOut)
|
|
{
|
|
if (!jsCachingEnabled || !jsCacheAsmJSPath)
|
|
return false;
|
|
|
|
ScopedFileDesc fd(open(jsCacheAsmJSPath, O_RDWR), ScopedFileDesc::READ_LOCK);
|
|
if (fd == -1)
|
|
return false;
|
|
|
|
// Get the size and make sure we can dereference at least one uint32_t.
|
|
off_t off = lseek(fd, 0, SEEK_END);
|
|
if (off == -1 || off < (off_t)sizeof(uint32_t))
|
|
return false;
|
|
|
|
// Map the file into memory.
|
|
void* memory;
|
|
#ifdef XP_WIN
|
|
HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd);
|
|
HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr);
|
|
if (!fileMapping)
|
|
return false;
|
|
|
|
memory = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0);
|
|
CloseHandle(fileMapping);
|
|
if (!memory)
|
|
return false;
|
|
#else
|
|
memory = mmap(nullptr, off, PROT_READ, MAP_SHARED, fd, 0);
|
|
if (memory == MAP_FAILED)
|
|
return false;
|
|
#endif
|
|
|
|
// Perform check described by asmJSCacheCookie comment.
|
|
if (*(uint32_t*)memory != asmJSCacheCookie) {
|
|
#ifdef XP_WIN
|
|
UnmapViewOfFile(memory);
|
|
#else
|
|
munmap(memory, off);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// The embedding added the cookie so strip it off of the buffer returned to
|
|
// the JS engine.
|
|
*serializedSizeOut = off - sizeof(uint32_t);
|
|
*memoryOut = (uint8_t*)memory + sizeof(uint32_t);
|
|
*handleOut = fd.forget();
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
ShellCloseAsmJSCacheEntryForRead(size_t serializedSize, const uint8_t* memory, intptr_t handle)
|
|
{
|
|
// Undo the cookie adjustment done when opening the file.
|
|
memory -= sizeof(uint32_t);
|
|
serializedSize += sizeof(uint32_t);
|
|
|
|
// Release the memory mapping and file.
|
|
#ifdef XP_WIN
|
|
UnmapViewOfFile(const_cast<uint8_t*>(memory));
|
|
#else
|
|
munmap(const_cast<uint8_t*>(memory), serializedSize);
|
|
#endif
|
|
|
|
MOZ_ASSERT(jsCacheOpened == true);
|
|
jsCacheOpened = false;
|
|
close(handle);
|
|
}
|
|
|
|
static JS::AsmJSCacheResult
|
|
ShellOpenAsmJSCacheEntryForWrite(HandleObject global, bool installed,
|
|
const char16_t* begin, const char16_t* end,
|
|
size_t serializedSize, uint8_t** memoryOut, intptr_t* handleOut)
|
|
{
|
|
if (!jsCachingEnabled || !jsCacheAsmJSPath)
|
|
return JS::AsmJSCache_Disabled_ShellFlags;
|
|
|
|
// Create the cache directory if it doesn't already exist.
|
|
struct stat dirStat;
|
|
if (stat(jsCacheDir, &dirStat) == 0) {
|
|
if (!(dirStat.st_mode & S_IFDIR))
|
|
return JS::AsmJSCache_InternalError;
|
|
} else {
|
|
#ifdef XP_WIN
|
|
if (mkdir(jsCacheDir) != 0)
|
|
return JS::AsmJSCache_InternalError;
|
|
#else
|
|
if (mkdir(jsCacheDir, 0777) != 0)
|
|
return JS::AsmJSCache_InternalError;
|
|
#endif
|
|
}
|
|
|
|
ScopedFileDesc fd(open(jsCacheAsmJSPath, O_CREAT|O_RDWR, 0660), ScopedFileDesc::WRITE_LOCK);
|
|
if (fd == -1)
|
|
return JS::AsmJSCache_InternalError;
|
|
|
|
// Include extra space for the asmJSCacheCookie.
|
|
serializedSize += sizeof(uint32_t);
|
|
|
|
// Resize the file to the appropriate size after zeroing their contents.
|
|
#ifdef XP_WIN
|
|
if (chsize(fd, 0))
|
|
return JS::AsmJSCache_InternalError;
|
|
if (chsize(fd, serializedSize))
|
|
return JS::AsmJSCache_InternalError;
|
|
#else
|
|
if (ftruncate(fd, 0))
|
|
return JS::AsmJSCache_InternalError;
|
|
if (ftruncate(fd, serializedSize))
|
|
return JS::AsmJSCache_InternalError;
|
|
#endif
|
|
|
|
// Map the file into memory.
|
|
void* memory;
|
|
#ifdef XP_WIN
|
|
HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd);
|
|
HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr);
|
|
if (!fileMapping)
|
|
return JS::AsmJSCache_InternalError;
|
|
|
|
memory = MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0);
|
|
CloseHandle(fileMapping);
|
|
if (!memory)
|
|
return JS::AsmJSCache_InternalError;
|
|
MOZ_ASSERT(*(uint32_t*)memory == 0);
|
|
#else
|
|
memory = mmap(nullptr, serializedSize, PROT_READ, MAP_SHARED, fd, 0);
|
|
if (memory == MAP_FAILED)
|
|
return JS::AsmJSCache_InternalError;
|
|
MOZ_ASSERT(*(uint32_t*)memory == 0);
|
|
if (mprotect(memory, serializedSize, PROT_WRITE))
|
|
return JS::AsmJSCache_InternalError;
|
|
#endif
|
|
|
|
// The embedding added the cookie so strip it off of the buffer returned to
|
|
// the JS engine. The asmJSCacheCookie will be written on close, below.
|
|
*memoryOut = (uint8_t*)memory + sizeof(uint32_t);
|
|
*handleOut = fd.forget();
|
|
return JS::AsmJSCache_Success;
|
|
}
|
|
|
|
static void
|
|
ShellCloseAsmJSCacheEntryForWrite(size_t serializedSize, uint8_t* memory, intptr_t handle)
|
|
{
|
|
// Undo the cookie adjustment done when opening the file.
|
|
memory -= sizeof(uint32_t);
|
|
serializedSize += sizeof(uint32_t);
|
|
|
|
// Write the magic cookie value after flushing the entire cache entry.
|
|
#ifdef XP_WIN
|
|
FlushViewOfFile(memory, serializedSize);
|
|
FlushFileBuffers(HANDLE(_get_osfhandle(handle)));
|
|
#else
|
|
msync(memory, serializedSize, MS_SYNC);
|
|
#endif
|
|
|
|
MOZ_ASSERT(*(uint32_t*)memory == 0);
|
|
*(uint32_t*)memory = asmJSCacheCookie;
|
|
|
|
// Free the memory mapping and file.
|
|
#ifdef XP_WIN
|
|
UnmapViewOfFile(const_cast<uint8_t*>(memory));
|
|
#else
|
|
munmap(memory, serializedSize);
|
|
#endif
|
|
|
|
MOZ_ASSERT(jsCacheOpened == true);
|
|
jsCacheOpened = false;
|
|
close(handle);
|
|
}
|
|
|
|
static bool
|
|
ShellBuildId(JS::BuildIdCharVector* buildId)
|
|
{
|
|
// The browser embeds the date into the buildid and the buildid is embedded
|
|
// in the binary, so every 'make' necessarily builds a new firefox binary.
|
|
// Fortunately, the actual firefox executable is tiny -- all the code is in
|
|
// libxul.so and other shared modules -- so this isn't a big deal. Not so
|
|
// for the statically-linked JS shell. To avoid recompiling js.cpp and
|
|
// re-linking 'js' on every 'make', we use a constant buildid and rely on
|
|
// the shell user to manually clear the cache (deleting the dir passed to
|
|
// --js-cache) between cache-breaking updates. Note: jit_tests.py does this
|
|
// on every run).
|
|
const char buildid[] = "JS-shell";
|
|
return buildId->append(buildid, sizeof(buildid));
|
|
}
|
|
|
|
static const JS::AsmJSCacheOps asmJSCacheOps = {
|
|
ShellOpenAsmJSCacheEntryForRead,
|
|
ShellCloseAsmJSCacheEntryForRead,
|
|
ShellOpenAsmJSCacheEntryForWrite,
|
|
ShellCloseAsmJSCacheEntryForWrite
|
|
};
|
|
|
|
static JSObject*
|
|
NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
|
|
JSPrincipals* principals)
|
|
{
|
|
RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, principals,
|
|
JS::DontFireOnNewGlobalHook, options));
|
|
if (!glob)
|
|
return nullptr;
|
|
|
|
{
|
|
JSAutoCompartment ac(cx, glob);
|
|
|
|
#ifndef LAZY_STANDARD_CLASSES
|
|
if (!JS_InitStandardClasses(cx, glob))
|
|
return nullptr;
|
|
#endif
|
|
|
|
bool succeeded;
|
|
if (!JS_SetImmutablePrototype(cx, glob, &succeeded))
|
|
return nullptr;
|
|
MOZ_ASSERT(succeeded,
|
|
"a fresh, unexposed global object is always capable of "
|
|
"having its [[Prototype]] be immutable");
|
|
|
|
#ifdef JS_HAS_CTYPES
|
|
if (!JS_InitCTypesClass(cx, glob))
|
|
return nullptr;
|
|
#endif
|
|
if (!JS_InitReflectParse(cx, glob))
|
|
return nullptr;
|
|
if (!JS_DefineDebuggerObject(cx, glob))
|
|
return nullptr;
|
|
if (!JS::RegisterPerfMeasurement(cx, glob))
|
|
return nullptr;
|
|
if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) ||
|
|
!JS_DefineProfilingFunctions(cx, glob))
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe, disableOOMFunctions))
|
|
return nullptr;
|
|
|
|
if (!fuzzingSafe) {
|
|
if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions))
|
|
return nullptr;
|
|
if (!DefineConsole(cx, glob))
|
|
return nullptr;
|
|
}
|
|
|
|
if (!DefineOS(cx, glob, fuzzingSafe, &gOutFile, &gErrFile))
|
|
return nullptr;
|
|
|
|
RootedObject performanceObj(cx, JS_NewObject(cx, nullptr));
|
|
if (!performanceObj)
|
|
return nullptr;
|
|
RootedObject mozMemoryObj(cx, JS_NewObject(cx, nullptr));
|
|
if (!mozMemoryObj)
|
|
return nullptr;
|
|
RootedObject gcObj(cx, gc::NewMemoryInfoObject(cx));
|
|
if (!gcObj)
|
|
return nullptr;
|
|
if (!JS_DefineProperty(cx, glob, "performance", performanceObj, JSPROP_ENUMERATE))
|
|
return nullptr;
|
|
if (!JS_DefineProperty(cx, performanceObj, "mozMemory", mozMemoryObj, JSPROP_ENUMERATE))
|
|
return nullptr;
|
|
if (!JS_DefineProperty(cx, mozMemoryObj, "gc", gcObj, JSPROP_ENUMERATE))
|
|
return nullptr;
|
|
|
|
/* Initialize FakeDOMObject. */
|
|
static const js::DOMCallbacks DOMcallbacks = {
|
|
InstanceClassHasProtoAtDepth
|
|
};
|
|
SetDOMCallbacks(cx, &DOMcallbacks);
|
|
|
|
RootedObject domProto(cx, JS_InitClass(cx, glob, nullptr, &dom_class, dom_constructor,
|
|
0, dom_props, dom_methods, nullptr, nullptr));
|
|
if (!domProto)
|
|
return nullptr;
|
|
|
|
/* Initialize FakeDOMObject.prototype */
|
|
InitDOMObject(domProto);
|
|
}
|
|
|
|
JS_FireOnNewGlobalObject(cx, glob);
|
|
|
|
return glob;
|
|
}
|
|
|
|
static bool
|
|
BindScriptArgs(JSContext* cx, OptionParser* op)
|
|
{
|
|
AutoReportException are(cx);
|
|
|
|
MultiStringRange msr = op->getMultiStringArg("scriptArgs");
|
|
RootedObject scriptArgs(cx);
|
|
scriptArgs = JS_NewArrayObject(cx, 0);
|
|
if (!scriptArgs)
|
|
return false;
|
|
|
|
if (!JS_DefineProperty(cx, cx->global(), "scriptArgs", scriptArgs, 0))
|
|
return false;
|
|
|
|
for (size_t i = 0; !msr.empty(); msr.popFront(), ++i) {
|
|
const char* scriptArg = msr.front();
|
|
JS::RootedString str(cx, JS_NewStringCopyZ(cx, scriptArg));
|
|
if (!str ||
|
|
!JS_DefineElement(cx, scriptArgs, i, str, JSPROP_ENUMERATE))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const char* scriptPath = op->getStringArg("script");
|
|
RootedValue scriptPathValue(cx);
|
|
if (scriptPath) {
|
|
RootedString scriptPathString(cx, JS_NewStringCopyZ(cx, scriptPath));
|
|
if (!scriptPathString)
|
|
return false;
|
|
scriptPathValue = StringValue(scriptPathString);
|
|
} else {
|
|
scriptPathValue = UndefinedValue();
|
|
}
|
|
|
|
if (!JS_DefineProperty(cx, cx->global(), "scriptPath", scriptPathValue, 0))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
OptionFailure(const char* option, const char* str)
|
|
{
|
|
fprintf(stderr, "Unrecognized option for %s: %s\n", option, str);
|
|
return false;
|
|
}
|
|
|
|
static MOZ_MUST_USE bool
|
|
ProcessArgs(JSContext* cx, OptionParser* op)
|
|
{
|
|
ShellContext* sc = GetShellContext(cx);
|
|
|
|
if (op->getBoolOption('s'))
|
|
JS::ContextOptionsRef(cx).toggleExtraWarnings();
|
|
|
|
/* |scriptArgs| gets bound on the global before any code is run. */
|
|
if (!BindScriptArgs(cx, op))
|
|
return false;
|
|
|
|
MultiStringRange filePaths = op->getMultiStringOption('f');
|
|
MultiStringRange codeChunks = op->getMultiStringOption('e');
|
|
MultiStringRange modulePaths = op->getMultiStringOption('m');
|
|
|
|
if (filePaths.empty() &&
|
|
codeChunks.empty() &&
|
|
modulePaths.empty() &&
|
|
!op->getStringArg("script"))
|
|
{
|
|
return Process(cx, nullptr, true); /* Interactive. */
|
|
}
|
|
|
|
if (const char* path = op->getStringOption("module-load-path"))
|
|
moduleLoadPath = path;
|
|
|
|
if (!modulePaths.empty() && !InitModuleLoader(cx))
|
|
return false;
|
|
|
|
while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) {
|
|
size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno();
|
|
size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno();
|
|
size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno();
|
|
|
|
if (fpArgno < ccArgno && fpArgno < mpArgno) {
|
|
char* path = filePaths.front();
|
|
if (!Process(cx, path, false, FileScript))
|
|
return false;
|
|
filePaths.popFront();
|
|
} else if (ccArgno < fpArgno && ccArgno < mpArgno) {
|
|
const char* code = codeChunks.front();
|
|
RootedValue rval(cx);
|
|
JS::CompileOptions opts(cx);
|
|
opts.setFileAndLine("-e", 1);
|
|
if (!JS::Evaluate(cx, opts, code, strlen(code), &rval))
|
|
return false;
|
|
codeChunks.popFront();
|
|
if (sc->quitting)
|
|
break;
|
|
} else {
|
|
MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ccArgno);
|
|
char* path = modulePaths.front();
|
|
if (!Process(cx, path, false, FileModule))
|
|
return false;
|
|
modulePaths.popFront();
|
|
}
|
|
}
|
|
|
|
if (sc->quitting)
|
|
return false;
|
|
|
|
/* The |script| argument is processed after all options. */
|
|
if (const char* path = op->getStringArg("script")) {
|
|
if (!Process(cx, path, false))
|
|
return false;
|
|
}
|
|
|
|
DrainJobQueue(cx);
|
|
|
|
if (op->getBoolOption('i')) {
|
|
if (!Process(cx, nullptr, true))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
SetContextOptions(JSContext* cx, const OptionParser& op)
|
|
{
|
|
enableBaseline = !op.getBoolOption("no-baseline");
|
|
enableIon = !op.getBoolOption("no-ion");
|
|
enableAsmJS = !op.getBoolOption("no-asmjs");
|
|
enableWasm = !op.getBoolOption("no-wasm");
|
|
enableNativeRegExp = !op.getBoolOption("no-native-regexp");
|
|
enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
|
|
enableArrayProtoValues = !op.getBoolOption("no-array-proto-values");
|
|
|
|
JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
|
|
.setIon(enableIon)
|
|
.setAsmJS(enableAsmJS)
|
|
.setWasm(enableWasm)
|
|
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
|
|
.setNativeRegExp(enableNativeRegExp)
|
|
.setArrayProtoValues(enableArrayProtoValues);
|
|
|
|
if (op.getBoolOption("wasm-check-bce"))
|
|
jit::JitOptions.wasmAlwaysCheckBounds = true;
|
|
|
|
if (const char* str = op.getStringOption("cache-ir-stubs")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableCacheIR = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableCacheIR = true;
|
|
else
|
|
return OptionFailure("cache-ir-stubs", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-scalar-replacement")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableScalarReplacement = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableScalarReplacement = true;
|
|
else
|
|
return OptionFailure("ion-scalar-replacement", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-shared-stubs")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableSharedStubs = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableSharedStubs = true;
|
|
else
|
|
return OptionFailure("ion-shared-stubs", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-gvn")) {
|
|
if (strcmp(str, "off") == 0) {
|
|
jit::JitOptions.disableGvn = true;
|
|
} else if (strcmp(str, "on") != 0 &&
|
|
strcmp(str, "optimistic") != 0 &&
|
|
strcmp(str, "pessimistic") != 0)
|
|
{
|
|
// We accept "pessimistic" and "optimistic" as synonyms for "on"
|
|
// for backwards compatibility.
|
|
return OptionFailure("ion-gvn", str);
|
|
}
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-aa")) {
|
|
if (strcmp(str, "flow-sensitive") == 0)
|
|
jit::JitOptions.disableFlowAA = false;
|
|
else if (strcmp(str, "flow-insensitive") == 0)
|
|
jit::JitOptions.disableFlowAA = true;
|
|
else
|
|
return OptionFailure("ion-aa", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-licm")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableLicm = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableLicm = true;
|
|
else
|
|
return OptionFailure("ion-licm", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-edgecase-analysis")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableEdgeCaseAnalysis = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableEdgeCaseAnalysis = true;
|
|
else
|
|
return OptionFailure("ion-edgecase-analysis", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-pgo")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disablePgo = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disablePgo = true;
|
|
else
|
|
return OptionFailure("ion-pgo", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-range-analysis")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableRangeAnalysis = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableRangeAnalysis = true;
|
|
else
|
|
return OptionFailure("ion-range-analysis", str);
|
|
}
|
|
|
|
if (const char *str = op.getStringOption("ion-sincos")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableSincos = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableSincos = true;
|
|
else
|
|
return OptionFailure("ion-sincos", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-sink")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableSink = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableSink = true;
|
|
else
|
|
return OptionFailure("ion-sink", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-loop-unrolling")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableLoopUnrolling = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableLoopUnrolling = true;
|
|
else
|
|
return OptionFailure("ion-loop-unrolling", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-instruction-reordering")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableInstructionReordering = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableInstructionReordering = true;
|
|
else
|
|
return OptionFailure("ion-instruction-reordering", str);
|
|
}
|
|
|
|
if (op.getBoolOption("ion-check-range-analysis"))
|
|
jit::JitOptions.checkRangeAnalysis = true;
|
|
|
|
if (op.getBoolOption("ion-extra-checks"))
|
|
jit::JitOptions.runExtraChecks = true;
|
|
|
|
if (const char* str = op.getStringOption("ion-inlining")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.disableInlining = false;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.disableInlining = true;
|
|
else
|
|
return OptionFailure("ion-inlining", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-osr")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.osr = true;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.osr = false;
|
|
else
|
|
return OptionFailure("ion-osr", str);
|
|
}
|
|
|
|
if (const char* str = op.getStringOption("ion-limit-script-size")) {
|
|
if (strcmp(str, "on") == 0)
|
|
jit::JitOptions.limitScriptSize = true;
|
|
else if (strcmp(str, "off") == 0)
|
|
jit::JitOptions.limitScriptSize = false;
|
|
else
|
|
return OptionFailure("ion-limit-script-size", str);
|
|
}
|
|
|
|
int32_t warmUpThreshold = op.getIntOption("ion-warmup-threshold");
|
|
if (warmUpThreshold >= 0)
|
|
jit::JitOptions.setCompilerWarmUpThreshold(warmUpThreshold);
|
|
|
|
warmUpThreshold = op.getIntOption("baseline-warmup-threshold");
|
|
if (warmUpThreshold >= 0)
|
|
jit::JitOptions.baselineWarmUpThreshold = warmUpThreshold;
|
|
|
|
if (op.getBoolOption("baseline-eager"))
|
|
jit::JitOptions.baselineWarmUpThreshold = 0;
|
|
|
|
if (const char* str = op.getStringOption("ion-regalloc")) {
|
|
jit::JitOptions.forcedRegisterAllocator = jit::LookupRegisterAllocator(str);
|
|
if (!jit::JitOptions.forcedRegisterAllocator.isSome())
|
|
return OptionFailure("ion-regalloc", str);
|
|
}
|
|
|
|
if (op.getBoolOption("ion-eager"))
|
|
jit::JitOptions.setEagerCompilation();
|
|
|
|
offthreadCompilation = true;
|
|
if (const char* str = op.getStringOption("ion-offthread-compile")) {
|
|
if (strcmp(str, "off") == 0)
|
|
offthreadCompilation = false;
|
|
else if (strcmp(str, "on") != 0)
|
|
return OptionFailure("ion-offthread-compile", str);
|
|
}
|
|
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
|
|
|
|
if (op.getStringOption("ion-parallel-compile")) {
|
|
fprintf(stderr, "--ion-parallel-compile is deprecated. Please use --ion-offthread-compile instead.\n");
|
|
return false;
|
|
}
|
|
|
|
#ifdef ENABLE_SHARED_ARRAY_BUFFER
|
|
if (const char* str = op.getStringOption("shared-memory")) {
|
|
if (strcmp(str, "off") == 0)
|
|
enableSharedMemory = false;
|
|
else if (strcmp(str, "on") == 0)
|
|
enableSharedMemory = true;
|
|
else
|
|
return OptionFailure("shared-memory", str);
|
|
}
|
|
#endif
|
|
|
|
#if defined(JS_CODEGEN_ARM)
|
|
if (const char* str = op.getStringOption("arm-hwcap"))
|
|
jit::ParseARMHwCapFlags(str);
|
|
|
|
int32_t fill = op.getIntOption("arm-asm-nop-fill");
|
|
if (fill >= 0)
|
|
jit::Assembler::NopFill = fill;
|
|
|
|
int32_t poolMaxOffset = op.getIntOption("asm-pool-max-offset");
|
|
if (poolMaxOffset >= 5 && poolMaxOffset <= 1024)
|
|
jit::Assembler::AsmPoolMaxOffset = poolMaxOffset;
|
|
#endif
|
|
|
|
#if defined(JS_SIMULATOR_ARM)
|
|
if (op.getBoolOption("arm-sim-icache-checks"))
|
|
jit::Simulator::ICacheCheckingEnabled = true;
|
|
|
|
int32_t stopAt = op.getIntOption("arm-sim-stop-at");
|
|
if (stopAt >= 0)
|
|
jit::Simulator::StopSimAt = stopAt;
|
|
#elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
|
if (op.getBoolOption("mips-sim-icache-checks"))
|
|
jit::Simulator::ICacheCheckingEnabled = true;
|
|
|
|
int32_t stopAt = op.getIntOption("mips-sim-stop-at");
|
|
if (stopAt >= 0)
|
|
jit::Simulator::StopSimAt = stopAt;
|
|
#endif
|
|
|
|
reportWarnings = op.getBoolOption('w');
|
|
compileOnly = op.getBoolOption('c');
|
|
printTiming = op.getBoolOption('b');
|
|
enableCodeCoverage = op.getBoolOption("code-coverage");
|
|
enableDisassemblyDumps = op.getBoolOption('D');
|
|
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
|
|
|
|
jsCacheDir = op.getStringOption("js-cache");
|
|
if (jsCacheDir) {
|
|
if (!op.getBoolOption("no-js-cache-per-process"))
|
|
jsCacheDir = JS_smprintf("%s/%u", jsCacheDir, (unsigned)getpid());
|
|
else
|
|
jsCacheDir = JS_strdup(cx, jsCacheDir);
|
|
if (!jsCacheDir)
|
|
return false;
|
|
jsCacheAsmJSPath = JS_smprintf("%s/asmjs.cache", jsCacheDir);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
dumpEntrainedVariables = op.getBoolOption("dump-entrained-variables");
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
SetWorkerContextOptions(JSContext* cx)
|
|
{
|
|
// Copy option values from the main thread.
|
|
JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
|
|
.setIon(enableIon)
|
|
.setAsmJS(enableAsmJS)
|
|
.setWasm(enableWasm)
|
|
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
|
|
.setNativeRegExp(enableNativeRegExp)
|
|
.setArrayProtoValues(enableArrayProtoValues);
|
|
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
|
|
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
|
|
|
|
JS_SetNativeStackQuota(cx, gMaxStackSize);
|
|
}
|
|
|
|
static int
|
|
Shell(JSContext* cx, OptionParser* op, char** envp)
|
|
{
|
|
Maybe<JS::AutoDisableGenerationalGC> noggc;
|
|
if (op->getBoolOption("no-ggc"))
|
|
noggc.emplace(cx->runtime());
|
|
|
|
Maybe<AutoDisableCompactingGC> nocgc;
|
|
if (op->getBoolOption("no-cgc"))
|
|
nocgc.emplace(cx);
|
|
|
|
JSAutoRequest ar(cx);
|
|
|
|
if (op->getBoolOption("fuzzing-safe"))
|
|
fuzzingSafe = true;
|
|
else
|
|
fuzzingSafe = (getenv("MOZ_FUZZING_SAFE") && getenv("MOZ_FUZZING_SAFE")[0] != '0');
|
|
|
|
if (op->getBoolOption("disable-oom-functions"))
|
|
disableOOMFunctions = true;
|
|
|
|
JS::CompartmentOptions options;
|
|
SetStandardCompartmentOptions(options);
|
|
RootedObject glob(cx, NewGlobalObject(cx, options, nullptr));
|
|
if (!glob)
|
|
return 1;
|
|
|
|
JSAutoCompartment ac(cx, glob);
|
|
|
|
ShellContext* sc = GetShellContext(cx);
|
|
int result = EXIT_SUCCESS;
|
|
{
|
|
AutoReportException are(cx);
|
|
if (!ProcessArgs(cx, op) && !sc->quitting)
|
|
result = EXITCODE_RUNTIME_ERROR;
|
|
}
|
|
|
|
if (sc->exitCode)
|
|
result = sc->exitCode;
|
|
|
|
if (enableDisassemblyDumps) {
|
|
AutoReportException are(cx);
|
|
if (!js::DumpCompartmentPCCounts(cx))
|
|
result = EXITCODE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (!op->getBoolOption("no-js-cache-per-process")) {
|
|
if (jsCacheAsmJSPath) {
|
|
unlink(jsCacheAsmJSPath);
|
|
JS_free(cx, const_cast<char*>(jsCacheAsmJSPath));
|
|
}
|
|
if (jsCacheDir) {
|
|
rmdir(jsCacheDir);
|
|
JS_free(cx, const_cast<char*>(jsCacheDir));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
SetOutputFile(const char* const envVar,
|
|
RCFile* defaultOut,
|
|
RCFile** outFileP)
|
|
{
|
|
RCFile* outFile;
|
|
|
|
const char* outPath = getenv(envVar);
|
|
FILE* newfp;
|
|
if (outPath && *outPath && (newfp = fopen(outPath, "w")))
|
|
outFile = js_new<RCFile>(newfp);
|
|
else
|
|
outFile = defaultOut;
|
|
|
|
outFile->acquire();
|
|
*outFileP = outFile;
|
|
}
|
|
|
|
static void
|
|
PreInit()
|
|
{
|
|
#ifdef XP_WIN
|
|
const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
|
|
if (crash_option && crash_option[0] == '1') {
|
|
// Disable the segfault dialog. We want to fail the tests immediately
|
|
// instead of hanging automation.
|
|
UINT newMode = SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
|
|
UINT prevMode = SetErrorMode(newMode);
|
|
SetErrorMode(prevMode | newMode);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int
|
|
main(int argc, char** argv, char** envp)
|
|
{
|
|
PreInit();
|
|
|
|
sArgc = argc;
|
|
sArgv = argv;
|
|
|
|
int result;
|
|
|
|
#ifdef HAVE_SETLOCALE
|
|
setlocale(LC_ALL, "");
|
|
#endif
|
|
|
|
// Special-case stdout and stderr. We bump their refcounts to prevent them
|
|
// from getting closed and then having some printf fail somewhere.
|
|
RCFile rcStdout(stdout);
|
|
rcStdout.acquire();
|
|
RCFile rcStderr(stderr);
|
|
rcStderr.acquire();
|
|
|
|
SetOutputFile("JS_STDOUT", &rcStdout, &gOutFile);
|
|
SetOutputFile("JS_STDERR", &rcStderr, &gErrFile);
|
|
|
|
OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]");
|
|
|
|
op.setDescription("The SpiderMonkey shell provides a command line interface to the "
|
|
"JavaScript engine. Code and file options provided via the command line are "
|
|
"run left to right. If provided, the optional script argument is run after "
|
|
"all options have been processed. Just-In-Time compilation modes may be enabled via "
|
|
"command line options.");
|
|
op.setDescriptionWidth(72);
|
|
op.setHelpWidth(80);
|
|
op.setVersion(JS_GetImplementationVersion());
|
|
|
|
if (!op.addMultiStringOption('f', "file", "PATH", "File path to run")
|
|
|| !op.addMultiStringOption('m', "module", "PATH", "Module path to run")
|
|
|| !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run")
|
|
|| !op.addBoolOption('i', "shell", "Enter prompt after running code")
|
|
|| !op.addBoolOption('c', "compileonly", "Only compile, don't run (syntax checking mode)")
|
|
|| !op.addBoolOption('w', "warnings", "Emit warnings")
|
|
|| !op.addBoolOption('W', "nowarnings", "Don't emit warnings")
|
|
|| !op.addBoolOption('s', "strict", "Check strictness")
|
|
|| !op.addBoolOption('D', "dump-bytecode", "Dump bytecode with exec count for all scripts")
|
|
|| !op.addBoolOption('b', "print-timing", "Print sub-ms runtime for each file that's run")
|
|
|| !op.addStringOption('\0', "js-cache", "[path]",
|
|
"Enable the JS cache by specifying the path of the directory to use "
|
|
"to hold cache files")
|
|
|| !op.addBoolOption('\0', "no-js-cache-per-process",
|
|
"Deactivates cache per process. Otherwise, generate a separate cache"
|
|
"sub-directory for this process inside the cache directory"
|
|
"specified by --js-cache. This cache directory will be removed"
|
|
"when the js shell exits. This is useful for running tests in"
|
|
"parallel.")
|
|
|| !op.addBoolOption('\0', "code-coverage", "Enable code coverage instrumentation.")
|
|
#ifdef DEBUG
|
|
|| !op.addBoolOption('O', "print-alloc", "Print the number of allocations at exit")
|
|
#endif
|
|
|| !op.addOptionalStringArg("script", "A script to execute (after all options)")
|
|
|| !op.addOptionalMultiStringArg("scriptArgs",
|
|
"String arguments to bind as |scriptArgs| in the "
|
|
"shell's global")
|
|
|| !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads "
|
|
"(default: # of cores - 1)", -1)
|
|
|| !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)")
|
|
|| !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
|
|
|| !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
|
|
|| !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation")
|
|
|| !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
|
|
|| !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
|
|
|| !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|
|
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")
|
|
#ifdef ENABLE_SHARED_ARRAY_BUFFER
|
|
|| !op.addStringOption('\0', "shared-memory", "on/off",
|
|
"SharedArrayBuffer and Atomics "
|
|
# if SHARED_MEMORY_DEFAULT
|
|
"(default: on, off to disable)"
|
|
# else
|
|
"(default: off, on to enable)"
|
|
# endif
|
|
)
|
|
#endif
|
|
|| !op.addStringOption('\0', "cache-ir-stubs", "on/off",
|
|
"Use CacheIR stubs (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-shared-stubs", "on/off",
|
|
"Use shared stubs (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
|
|
"Scalar Replacement (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-gvn", "[mode]",
|
|
"Specify Ion global value numbering:\n"
|
|
" off: disable GVN\n"
|
|
" on: enable GVN (default)\n")
|
|
|| !op.addStringOption('\0', "ion-licm", "on/off",
|
|
"Loop invariant code motion (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-aa", "flow-sensitive/flow-insensitive",
|
|
"Specify wheter or not to use flow sensitive Alias Analysis"
|
|
"(default: flow-insensitive)")
|
|
|| !op.addStringOption('\0', "ion-edgecase-analysis", "on/off",
|
|
"Find edge cases where Ion can avoid bailouts (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-pgo", "on/off",
|
|
"Profile guided optimization (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-range-analysis", "on/off",
|
|
"Range analysis (default: on, off to disable)")
|
|
#if defined(__APPLE__)
|
|
|| !op.addStringOption('\0', "ion-sincos", "on/off",
|
|
"Replace sin(x)/cos(x) to sincos(x) (default: on, off to disable)")
|
|
#else
|
|
|| !op.addStringOption('\0', "ion-sincos", "on/off",
|
|
"Replace sin(x)/cos(x) to sincos(x) (default: off, on to enable)")
|
|
#endif
|
|
|| !op.addStringOption('\0', "ion-sink", "on/off",
|
|
"Sink code motion (default: off, on to enable)")
|
|
|| !op.addStringOption('\0', "ion-loop-unrolling", "on/off",
|
|
"Loop unrolling (default: off, on to enable)")
|
|
|| !op.addStringOption('\0', "ion-instruction-reordering", "on/off",
|
|
"Instruction reordering (default: off, on to enable)")
|
|
|| !op.addBoolOption('\0', "ion-check-range-analysis",
|
|
"Range analysis checking")
|
|
|| !op.addBoolOption('\0', "ion-extra-checks",
|
|
"Perform extra dynamic validation checks")
|
|
|| !op.addStringOption('\0', "ion-inlining", "on/off",
|
|
"Inline methods where possible (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-osr", "on/off",
|
|
"On-Stack Replacement (default: on, off to disable)")
|
|
|| !op.addStringOption('\0', "ion-limit-script-size", "on/off",
|
|
"Don't compile very large scripts (default: on, off to disable)")
|
|
|| !op.addIntOption('\0', "ion-warmup-threshold", "COUNT",
|
|
"Wait for COUNT calls or iterations before compiling "
|
|
"(default: 1000)", -1)
|
|
|| !op.addStringOption('\0', "ion-regalloc", "[mode]",
|
|
"Specify Ion register allocation:\n"
|
|
" backtracking: Priority based backtracking register allocation (default)\n"
|
|
" testbed: Backtracking allocator with experimental features\n"
|
|
" stupid: Simple block local register allocation")
|
|
|| !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods (implies --baseline-eager)")
|
|
|| !op.addStringOption('\0', "ion-offthread-compile", "on/off",
|
|
"Compile scripts off thread (default: on)")
|
|
|| !op.addStringOption('\0', "ion-parallel-compile", "on/off",
|
|
"--ion-parallel compile is deprecated. Use --ion-offthread-compile.")
|
|
|| !op.addBoolOption('\0', "baseline", "Enable baseline compiler (default)")
|
|
|| !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler")
|
|
|| !op.addBoolOption('\0', "baseline-eager", "Always baseline-compile methods")
|
|
|| !op.addIntOption('\0', "baseline-warmup-threshold", "COUNT",
|
|
"Wait for COUNT calls or iterations before baseline-compiling "
|
|
"(default: 10)", -1)
|
|
|| !op.addBoolOption('\0', "non-writable-jitcode", "(NOP for fuzzers) Allocate JIT code as non-writable memory.")
|
|
|| !op.addBoolOption('\0', "no-sse3", "Pretend CPU does not support SSE3 instructions and above "
|
|
"to test JIT codegen (no-op on platforms other than x86 and x64).")
|
|
|| !op.addBoolOption('\0', "no-sse4", "Pretend CPU does not support SSE4 instructions"
|
|
"to test JIT codegen (no-op on platforms other than x86 and x64).")
|
|
|| !op.addBoolOption('\0', "enable-avx", "AVX is disabled by default. Enable AVX. "
|
|
"(no-op on platforms other than x86 and x64).")
|
|
|| !op.addBoolOption('\0', "no-avx", "No-op. AVX is currently disabled by default.")
|
|
|| !op.addBoolOption('\0', "fuzzing-safe", "Don't expose functions that aren't safe for "
|
|
"fuzzers to call")
|
|
|| !op.addBoolOption('\0', "disable-oom-functions", "Disable functions that cause "
|
|
"artificial OOMs")
|
|
|| !op.addBoolOption('\0', "no-threads", "Disable helper threads")
|
|
#ifdef DEBUG
|
|
|| !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are "
|
|
"unnecessarily entrained by inner functions")
|
|
#endif
|
|
|| !op.addBoolOption('\0', "no-ggc", "Disable Generational GC")
|
|
|| !op.addBoolOption('\0', "no-cgc", "Disable Compacting GC")
|
|
|| !op.addBoolOption('\0', "no-incremental-gc", "Disable Incremental GC")
|
|
|| !op.addIntOption('\0', "available-memory", "SIZE",
|
|
"Select GC settings based on available memory (MB)", 0)
|
|
#if defined(JS_CODEGEN_ARM)
|
|
|| !op.addStringOption('\0', "arm-hwcap", "[features]",
|
|
"Specify ARM code generation features, or 'help' to list all features.")
|
|
|| !op.addIntOption('\0', "arm-asm-nop-fill", "SIZE",
|
|
"Insert the given number of NOP instructions at all possible pool locations.", 0)
|
|
|| !op.addIntOption('\0', "asm-pool-max-offset", "OFFSET",
|
|
"The maximum pc relative OFFSET permitted in pool reference instructions.", 1024)
|
|
#endif
|
|
#if defined(JS_SIMULATOR_ARM)
|
|
|| !op.addBoolOption('\0', "arm-sim-icache-checks", "Enable icache flush checks in the ARM "
|
|
"simulator.")
|
|
|| !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER", "Stop the ARM simulator after the given "
|
|
"NUMBER of instructions.", -1)
|
|
#elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
|
|| !op.addBoolOption('\0', "mips-sim-icache-checks", "Enable icache flush checks in the MIPS "
|
|
"simulator.")
|
|
|| !op.addIntOption('\0', "mips-sim-stop-at", "NUMBER", "Stop the MIPS simulator after the given "
|
|
"NUMBER of instructions.", -1)
|
|
#endif
|
|
|| !op.addIntOption('\0', "nursery-size", "SIZE-MB", "Set the maximum nursery size in MB", 16)
|
|
|| !op.addStringOption('\0', "module-load-path", "DIR", "Set directory to load modules from")
|
|
)
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
op.setArgTerminatesOptions("script", true);
|
|
op.setArgCapturesRest("scriptArgs");
|
|
|
|
switch (op.parseArgs(argc, argv)) {
|
|
case OptionParser::EarlyExit:
|
|
return EXIT_SUCCESS;
|
|
case OptionParser::ParseError:
|
|
op.printHelp(argv[0]);
|
|
return EXIT_FAILURE;
|
|
case OptionParser::Fail:
|
|
return EXIT_FAILURE;
|
|
case OptionParser::Okay:
|
|
break;
|
|
}
|
|
|
|
if (op.getHelpOption())
|
|
return EXIT_SUCCESS;
|
|
|
|
#ifdef DEBUG
|
|
/*
|
|
* Process OOM options as early as possible so that we can observe as many
|
|
* allocations as possible.
|
|
*/
|
|
OOM_printAllocationCount = op.getBoolOption('O');
|
|
#endif
|
|
|
|
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
|
|
if (op.getBoolOption("no-sse3")) {
|
|
js::jit::CPUInfo::SetSSE3Disabled();
|
|
PropagateFlagToNestedShells("--no-sse3");
|
|
}
|
|
if (op.getBoolOption("no-sse4")) {
|
|
js::jit::CPUInfo::SetSSE4Disabled();
|
|
PropagateFlagToNestedShells("--no-sse4");
|
|
}
|
|
if (op.getBoolOption("enable-avx")) {
|
|
js::jit::CPUInfo::SetAVXEnabled();
|
|
PropagateFlagToNestedShells("--enable-avx");
|
|
}
|
|
#endif
|
|
|
|
if (op.getBoolOption("no-threads"))
|
|
js::DisableExtraThreads();
|
|
|
|
// Start the engine.
|
|
if (!JS_Init())
|
|
return 1;
|
|
|
|
if (!InitSharedArrayBufferMailbox())
|
|
return 1;
|
|
|
|
// The fake thread count must be set before initializing the Runtime,
|
|
// which spins up the thread pool.
|
|
int32_t threadCount = op.getIntOption("thread-count");
|
|
if (threadCount >= 0)
|
|
SetFakeCPUCount(threadCount);
|
|
|
|
size_t nurseryBytes = JS::DefaultNurseryBytes;
|
|
nurseryBytes = op.getIntOption("nursery-size") * 1024L * 1024L;
|
|
|
|
/* Use the same parameters as the browser in xpcjsruntime.cpp. */
|
|
JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes, nurseryBytes);
|
|
if (!cx)
|
|
return 1;
|
|
|
|
UniquePtr<ShellContext> sc = MakeUnique<ShellContext>(cx);
|
|
if (!sc)
|
|
return 1;
|
|
|
|
JS_SetContextPrivate(cx, sc.get());
|
|
// Waiting is allowed on the shell's main thread, for now.
|
|
JS_SetFutexCanWait(cx);
|
|
JS::SetWarningReporter(cx, WarningReporter);
|
|
if (!SetContextOptions(cx, op))
|
|
return 1;
|
|
|
|
JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
|
|
|
|
size_t availMem = op.getIntOption("available-memory");
|
|
if (availMem > 0)
|
|
JS_SetGCParametersBasedOnAvailableMemory(cx, availMem);
|
|
|
|
JS_SetTrustedPrincipals(cx, &ShellPrincipals::fullyTrusted);
|
|
JS_SetSecurityCallbacks(cx, &ShellPrincipals::securityCallbacks);
|
|
JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
|
|
|
|
JS_AddInterruptCallback(cx, ShellInterruptCallback);
|
|
JS::SetBuildIdOp(cx, ShellBuildId);
|
|
JS::SetAsmJSCacheOps(cx, &asmJSCacheOps);
|
|
|
|
JS_SetNativeStackQuota(cx, gMaxStackSize);
|
|
|
|
JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
|
|
|
|
if (!JS::InitSelfHostedCode(cx))
|
|
return 1;
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
sc->jobQueue.init(cx, JobQueue(SystemAllocPolicy()));
|
|
JS::SetEnqueuePromiseJobCallback(cx, ShellEnqueuePromiseJobCallback);
|
|
JS::SetGetIncumbentGlobalCallback(cx, ShellGetIncumbentGlobalCallback);
|
|
JS::SetAsyncTaskCallbacks(cx, ShellStartAsyncTaskCallback, ShellFinishAsyncTaskCallback);
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
EnvironmentPreparer environmentPreparer(cx);
|
|
|
|
JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
|
|
|
|
JS::SetLargeAllocationFailureCallback(cx, my_LargeAllocFailCallback, (void*)cx);
|
|
|
|
// Set some parameters to allow incremental GC in low memory conditions,
|
|
// as is done for the browser, except in more-deterministic builds or when
|
|
// disabled by command line options.
|
|
#ifndef JS_MORE_DETERMINISTIC
|
|
if (!op.getBoolOption("no-incremental-gc")) {
|
|
JS_SetGCParameter(cx, JSGC_DYNAMIC_HEAP_GROWTH, 1);
|
|
JS_SetGCParameter(cx, JSGC_DYNAMIC_MARK_SLICE, 1);
|
|
JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET, 10);
|
|
}
|
|
#endif
|
|
|
|
js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
|
|
|
|
result = Shell(cx, &op, envp);
|
|
|
|
#ifdef DEBUG
|
|
if (OOM_printAllocationCount)
|
|
printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
|
|
#endif
|
|
|
|
JS::SetLargeAllocationFailureCallback(cx, nullptr, nullptr);
|
|
|
|
#ifdef SPIDERMONKEY_PROMISE
|
|
JS::SetGetIncumbentGlobalCallback(cx, nullptr);
|
|
JS::SetEnqueuePromiseJobCallback(cx, nullptr);
|
|
sc->jobQueue.reset();
|
|
#endif // SPIDERMONKEY_PROMISE
|
|
|
|
KillWatchdog(cx);
|
|
|
|
KillWorkerThreads();
|
|
|
|
DestructSharedArrayBufferMailbox();
|
|
|
|
JS_DestroyContext(cx);
|
|
JS_ShutDown();
|
|
return result;
|
|
}
|