blob: cc4e44501e9c3582bb5246193f256507de8c981d [file] [log] [blame]
/* -*- 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/DebugOnly.h"
#include "mozilla/GuardObjects.h"
#if !defined(STARBOARD)
// mozalloc is only (minimally) used in the shell, and thus is not fully
// starboardized. Don't bother including it and just ifdef out references to
// stuff from it.
#include "mozilla/mozalloc.h"
#endif // !defined(STARBOARD)
#include "mozilla/PodOperations.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 "jslock.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/OSObject.h"
#include "vm/ArgumentsObject.h"
#include "vm/Compression.h"
#include "vm/Debugger.h"
#include "vm/HelperThreads.h"
#include "vm/Monitor.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 "jscompartmentinlines.h"
#include "jsobjinlines.h"
#include "vm/ErrorObject-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Stack-inl.h"
// Unified leak fix:
#include "jsshell.h"
#if defined(STARBOARD)
#if !defined(PATH_MAX)
#include "starboard/configuration.h"
#define PATH_MAX (SB_FILE_MAX_PATH + 1)
#endif
#include "starboard/client_porting/wrap_main/wrap_main.h"
#include "starboard/memory.h"
#endif
using namespace js;
using namespace js::cli;
using namespace js::shell;
using mozilla::ArrayLength;
using mozilla::Atomic;
using mozilla::MakeUnique;
using mozilla::Maybe;
using mozilla::NumberEqualsInt32;
using mozilla::PodCopy;
using mozilla::PodEqual;
using mozilla::UniquePtr;
// Unified leak fix:
using namespace JS;
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/XPCJSRuntime.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_INTERVAL = 1800.0;
// Per-runtime shell state.
struct ShellRuntime
{
ShellRuntime();
bool isWorker;
double timeoutInterval;
Atomic<bool> serviceInterrupt;
JS::PersistentRootedValue interruptFunc;
bool lastWarningEnabled;
JS::PersistentRootedValue lastWarning;
/*
* Watchdog thread state.
*/
PRLock* watchdogLock;
PRCondVar* watchdogWakeup;
PRThread* watchdogThread;
bool watchdogHasTimeout;
int64_t watchdogTimeout;
PRCondVar* sleepWakeup;
int exitCode;
bool quitting;
bool gotError;
};
// 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 enableNativeRegExp = false;
static bool enableUnboxedArrays = false;
#ifdef JS_GC_ZEAL
static char gZealStr[128];
#endif
static bool printTiming = false;
static const char* jsCacheDir = nullptr;
static const char* jsCacheAsmJSPath = nullptr;
static FILE* gErrFile = nullptr;
static FILE* 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 bool
InitWatchdog(JSRuntime* rt);
static void
KillWatchdog(JSRuntime *rt);
static bool
ScheduleWatchdog(JSRuntime* rt, double t);
static void
CancelExecution(JSRuntime* rt);
static JSContext*
NewContext(JSRuntime* rt);
static void
DestroyContext(JSContext* cx, bool withGC);
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 {
MOZ_ASSERT(false, "not implemented");
return false;
}
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
ShellRuntime::ShellRuntime()
: isWorker(false),
timeoutInterval(-1.0),
serviceInterrupt(false),
lastWarningEnabled(false),
watchdogLock(nullptr),
watchdogWakeup(nullptr),
watchdogThread(nullptr),
watchdogHasTimeout(false),
watchdogTimeout(0),
sleepWakeup(nullptr),
exitCode(0),
quitting(false),
gotError(false)
{}
static ShellRuntime*
GetShellRuntime(JSRuntime *rt)
{
ShellRuntime* sr = static_cast<ShellRuntime*>(JS_GetRuntimePrivate(rt));
MOZ_ASSERT(sr);
return sr;
}
static ShellRuntime*
GetShellRuntime(JSContext* cx)
{
return GetShellRuntime(cx->runtime());
}
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') {
fprintf(gOutFile, "%s", prompt);
fflush(gOutFile);
}
size_t size = 80;
char* buffer = static_cast<char*>(malloc(size));
if (!buffer)
return nullptr;
char* current = buffer;
while (true) {
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;
}
if (len && !ferror(file))
return buffer;
free(buffer);
return nullptr;
}
/* State to store as JSContext private. */
struct JSShellContextData {
/* Creation timestamp, used by the elapsed() shell builtin. */
int64_t startTime;
};
static JSShellContextData*
NewContextData()
{
JSShellContextData* data = (JSShellContextData*)
js_calloc(sizeof(JSShellContextData), 1);
if (!data)
return nullptr;
data->startTime = PRMJ_Now();
return data;
}
static inline JSShellContextData*
GetContextData(JSContext* cx)
{
JSShellContextData* data = (JSShellContextData*) JS_GetContextPrivate(cx);
MOZ_ASSERT(data);
return data;
}
static bool
ShellInterruptCallback(JSContext* cx)
{
ShellRuntime* sr = GetShellRuntime(cx);
if (!sr->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.
sr->serviceInterrupt = false;
bool result;
RootedValue interruptFunc(cx, sr->interruptFunc);
if (!interruptFunc.isNull()) {
JS::AutoSaveExceptionState savedExc(cx);
JSAutoCompartment ac(cx, &interruptFunc.toObject());
RootedValue rval(cx);
if (!JS_CallFunctionValue(cx, nullptr, interruptFunc,
JS::HandleValueArray::empty(), &rval))
{
return false;
}
if (rval.isBoolean())
result = rval.toBoolean();
else
result = false;
} else {
result = false;
}
if (!result && sr->exitCode == 0)
sr->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);
}
static void
RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
{
ShellRuntime* sr = GetShellRuntime(cx);
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);
sr->gotError = false;
(void) JS::Compile(cx, options, file, &script);
MOZ_ASSERT_IF(!script, sr->gotError);
}
#ifdef DEBUG
if (dumpEntrainedVariables)
AnalyzeEntrainedVariables(cx, script);
#endif
if (script && !compileOnly) {
if (!JS_ExecuteScript(cx, script)) {
if (!sr->quitting && sr->exitCode != EXITCODE_TIMEOUT)
sr->exitCode = EXITCODE_RUNTIME_ERROR;
}
int64_t t2 = PRMJ_Now() - t1;
if (printTiming)
printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
}
}
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 void
RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
{
// Exectute a module by calling |Reflect.Loader.import(filename)|.
ShellRuntime* sr = GetShellRuntime(cx);
RootedObject loaderObj(cx);
MOZ_ALWAYS_TRUE(GetLoaderObject(cx, &loaderObj));
RootedFunction importFun(cx);
MOZ_ALWAYS_TRUE(GetImportMethod(cx, loaderObj, &importFun));
AutoValueArray<2> args(cx);
args[0].setString(JS_NewStringCopyZ(cx, filename));
args[1].setUndefined();
RootedValue value(cx);
if (!JS_CallFunction(cx, loaderObj, importFun, args, &value)) {
sr->exitCode = EXITCODE_RUNTIME_ERROR;
return;
}
}
static bool
EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
int lineno, bool compileOnly, FILE* out)
{
// 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()) {
// Print.
RootedString str(cx);
str = JS_ValueToSource(cx, result);
if (!str)
return false;
char* utf8chars = JS_EncodeStringToUTF8(cx, str);
if (!utf8chars)
return false;
fprintf(out, "%s\n", utf8chars);
JS_free(cx, utf8chars);
}
return true;
}
static void
ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly)
{
ShellRuntime* sr = GetShellRuntime(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;
CharBuffer buffer(cx);
do {
ScheduleWatchdog(cx->runtime(), -1);
sr->serviceInterrupt = false;
errno = 0;
char* line = GetLine(in, startline == lineno ? "js> " : "");
if (!line) {
if (errno) {
JS_ReportError(cx, strerror(errno));
return;
}
hitEOF = true;
break;
}
if (!buffer.append(line, strlen(line)) || !buffer.append('\n'))
return;
lineno++;
if (!ScheduleWatchdog(cx->runtime(), sr->timeoutInterval)) {
hitEOF = true;
break;
}
} while (!JS_BufferIsCompilableUnit(cx, cx->global(), buffer.begin(), buffer.length()));
if (hitEOF && buffer.empty())
break;
if (!EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly,
out))
{
// Catch the error, report it, and keep going.
JS_ReportPendingException(cx);
}
} while (!hitEOF && !sr->quitting);
fprintf(out, "\n");
}
enum FileKind
{
FileScript,
FileModule
};
static void
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) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_CANT_OPEN, filename, strerror(errno));
return;
}
}
AutoCloseFile autoClose(file);
if (!forceTTY && !isatty(fileno(file))) {
// It's not interactive - just execute it.
if (kind == FileScript)
RunFile(cx, filename, file, compileOnly);
else
RunModule(cx, filename, file, compileOnly);
} else {
// It's an interactive filehandle; drop into read-eval-print loop.
MOZ_ASSERT(kind == FileScript);
ReadEvalPrintLoop(cx, file, gOutFile, compileOnly);
}
}
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_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "version");
return false;
}
JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
args.rval().setInt32(origVersion);
}
return true;
}
static bool
CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1 || args.length() > 3) {
JS_ReportErrorNumber(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 (offset > size) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
return false;
}
}
FILE* file = fopen(filename.ptr(), "r");
if (!file) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_CANT_OPEN, filename.ptr(), strerror(errno));
return false;
}
AutoCloseFile autoClose(file);
if (!sizeGiven) {
struct stat st;
if (fstat(fileno(file), &st) < 0) {
JS_ReportError(cx, "Unable to stat file");
return false;
}
if (st.st_size < off_t(offset)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
return false;
}
size = st.st_size - offset;
}
void* contents = JS_CreateMappedArrayBufferContents(fileno(file), offset, size);
if (!contents) {
JS_ReportError(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;
}
static bool
Options(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS::RuntimeOptions oldRuntimeOptions = JS::RuntimeOptionsRef(cx);
for (unsigned i = 0; i < args.length(); i++) {
JSString* str = JS::ToString(cx, args[i]);
if (!str)
return false;
args[i].setString(str);
JSAutoByteString opt(cx, str);
if (!opt)
return false;
if (strcmp(opt.ptr(), "strict") == 0)
JS::RuntimeOptionsRef(cx).toggleExtraWarnings();
else if (strcmp(opt.ptr(), "werror") == 0)
JS::RuntimeOptionsRef(cx).toggleWerror();
else if (strcmp(opt.ptr(), "throw_on_asmjs_validation_failure") == 0)
JS::RuntimeOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
else if (strcmp(opt.ptr(), "strict_mode") == 0)
JS::RuntimeOptionsRef(cx).toggleStrictMode();
else {
JS_ReportError(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 && oldRuntimeOptions.extraWarnings()) {
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict");
found = true;
}
if (names && oldRuntimeOptions.werror()) {
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror");
found = true;
}
if (names && oldRuntimeOptions.throwOnAsmJSValidationFailure()) {
names = JS_sprintf_append(names, "%s%s", found ? "," : "", "throw_on_asmjs_validation_failure");
found = true;
}
if (names && oldRuntimeOptions.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_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "load");
return false;
}
str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative);
if (!str) {
JS_ReportError(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;
}
class AutoNewContext
{
private:
JSContext* oldcx;
JSContext* newcx;
Maybe<JSAutoRequest> newRequest;
Maybe<AutoCompartment> newCompartment;
AutoNewContext(const AutoNewContext&) = delete;
public:
AutoNewContext() : oldcx(nullptr), newcx(nullptr) {}
bool enter(JSContext* cx) {
MOZ_ASSERT(!JS_IsExceptionPending(cx));
oldcx = cx;
newcx = NewContext(JS_GetRuntime(cx));
if (!newcx)
return false;
JS::ContextOptionsRef(newcx).setDontReportUncaught(true);
newRequest.emplace(newcx);
newCompartment.emplace(newcx, JS::CurrentGlobalOrNull(cx));
return true;
}
JSContext* get() { return newcx; }
~AutoNewContext() {
if (newcx) {
RootedValue exc(oldcx);
bool throwing = JS_IsExceptionPending(newcx);
if (throwing)
JS_GetPendingException(newcx, &exc);
newCompartment.reset();
newRequest.reset();
if (throwing && JS_WrapValue(oldcx, &exc))
JS_SetPendingException(oldcx, exc);
DestroyContext(newcx, false);
}
}
};
static void
my_LargeAllocFailCallback(void* data)
{
JSContext* cx = (JSContext*)data;
JSRuntime* rt = cx->runtime();
if (!cx->isJSContext())
return;
MOZ_ASSERT(!rt->isHeapBusy());
MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess());
JS::PrepareForFullGC(rt);
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_ReportErrorNumber(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;
}
class AutoSaveFrameChain
{
JSContext* cx_;
bool saved_;
public:
explicit AutoSaveFrameChain(JSContext* cx)
: cx_(cx),
saved_(false)
{}
bool save() {
if (!JS_SaveFrameChain(cx_))
return false;
saved_ = true;
return true;
}
~AutoSaveFrameChain() {
if (saved_)
JS_RestoreFrameChain(cx_);
}
};
static bool
Evaluate(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1 || args.length() > 2) {
JS_ReportErrorNumber(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_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
return false;
}
CompileOptions options(cx);
JSAutoByteString fileNameBytes;
bool newContext = false;
RootedString displayURL(cx);
RootedString sourceMapURL(cx);
RootedObject global(cx, nullptr);
bool catchTermination = false;
bool saveFrameChain = 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, "newContext", &v))
return false;
if (!v.isUndefined())
newContext = ToBoolean(v);
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_ReportErrorNumber(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, "saveFrameChain", &v))
return false;
if (!v.isUndefined())
saveFrameChain = 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_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
"evaluate");
return false;
}
}
}
AutoStableStringChars codeChars(cx);
if (!codeChars.initTwoByte(cx, code))
return false;
AutoNewContext ancx;
if (newContext) {
if (!ancx.enter(cx))
return false;
cx = ancx.get();
}
uint32_t loadLength = 0;
uint8_t* loadBuffer = nullptr;
uint32_t saveLength = 0;
ScopedJSFreePtr<uint8_t> saveBuffer;
if (loadBytecode) {
loadBuffer = CacheEntry_getBytecode(cacheEntry, &loadLength);
if (!loadBuffer)
return false;
}
{
AutoSaveFrameChain asfc(cx);
if (saveFrameChain && !asfc.save())
return false;
JSAutoCompartment ac(cx, global);
RootedScript script(cx);
{
if (saveBytecode) {
if (!JS::CompartmentOptionsRef(cx).cloneSingletons()) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_CACHE_SINGLETON_FAILED);
return false;
}
// cloneSingletons implies that singletons are used as template objects.
MOZ_ASSERT(JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates());
}
if (loadBytecode) {
script = JS_DecodeScript(cx, loadBuffer, loadLength);
} else {
mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
(void) JS::Compile(cx, options, chars.start().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().start().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().start().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) {
saveBuffer = reinterpret_cast<uint8_t*>(JS_EncodeScript(cx, script, &saveLength));
if (!saveBuffer)
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 (saveLength != loadLength) {
char loadLengthStr[16];
JS_snprintf(loadLengthStr, sizeof(loadLengthStr), "%u", loadLength);
char saveLengthStr[16];
JS_snprintf(saveLengthStr, sizeof(saveLengthStr), "%u", saveLength);
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED,
loadLengthStr, saveLengthStr);
return false;
}
if (!PodEqual(loadBuffer, saveBuffer.get(), loadLength)) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_CACHE_EQ_CONTENT_FAILED);
return false;
}
}
if (!CacheEntry_setBytecode(cx, cacheEntry, saveBuffer, saveLength))
return false;
saveBuffer.forget();
}
return JS_WrapValue(cx, args.rval());
}
JSString*
js::shell::FileAsString(JSContext* cx, const char* pathname)
{
FILE* file;
RootedString str(cx);
size_t len, cc;
char* buf;
file = fopen(pathname, "rb");
if (!file) {
JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno));
return nullptr;
}
AutoCloseFile autoClose(file);
if (fseek(file, 0, SEEK_END) != 0) {
JS_ReportError(cx, "can't seek end of %s", pathname);
} else {
len = ftell(file);
if (fseek(file, 0, SEEK_SET) != 0) {
JS_ReportError(cx, "can't seek start of %s", pathname);
} else {
buf = (char*) JS_malloc(cx, len + 1);
if (buf) {
cc = fread(buf, 1, len, file);
if (cc != len) {
JS_ReportError(cx, "can't read %s: %s", pathname,
(ptrdiff_t(cc) < 0) ? strerror(errno) : "short read");
} else {
char16_t* ucbuf =
JS::UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf, len), &len).get();
if (!ucbuf) {
JS_ReportError(cx, "Invalid UTF-8 in file '%s'", pathname);
return nullptr;
}
str = JS_NewUCStringCopyN(cx, ucbuf, len);
free(ucbuf);
}
JS_free(cx, buf);
}
}
}
return str;
}
/*
* 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_ReportErrorNumber(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);
JSAutoByteString filename(cx, str);
if (!filename)
return false;
str = FileAsString(cx, filename.ptr());
if (!str)
return false;
AutoStableStringChars chars(cx);
if (!chars.initTwoByte(cx, str))
return false;
const char16_t* ucbuf = chars.twoByteRange().start().get();
size_t buflen = str->length();
RootedScript script(cx);
int64_t startClock = PRMJ_Now();
{
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;
}
static bool
PutStr(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 0) {
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);
JS_free(cx, bytes);
fflush(gOutFile);
}
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, FILE* file)
{
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, "%s%s", i ? " " : "", bytes);
JS_free(cx, bytes);
}
fputc('\n', file);
fflush(file);
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)
{
ShellRuntime* sr = GetShellRuntime(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_ReportError(cx, "quit exit code should be in range 0-127");
return false;
}
sr->exitCode = code;
sr->quitting = true;
return false;
}
static bool
StartTimingMutator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_TOO_MANY_ARGS, "startTimingMutator");
return false;
}
if (!cx->runtime()->gc.stats.startTimingMutator()) {
JS_ReportError(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_ReportErrorNumber(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_ReportError(cx, "stopTimingMutator called when not timing the mutator");
return false;
}
double total_ms = mutator_ms + gc_ms;
if (total_ms > 0) {
fprintf(gOutFile, "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_ReportErrorNumber(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_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED,
actual, expected);
} else {
JSAutoByteString bytes2(cx, args[2].toString());
if (!bytes2)
return false;
JS_ReportErrorNumber(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, Value vArg, JSFunction** funp = nullptr)
{
RootedValue v(cx, vArg);
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().start().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;
}
if (!fun->isInterpreted()) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY);
return nullptr;
}
JSScript* script = fun->getOrCreateScript(cx);
if (!script)
return nullptr;
if (fun && funp)
*funp = fun;
return script;
}
static JSScript*
GetTopScript(JSContext* cx)
{
NonBuiltinScriptFrameIter iter(cx);
return iter.done() ? nullptr : iter.script();
}
static bool
GetScriptAndPCArgs(JSContext* cx, unsigned argc, Value* argv, MutableHandleScript scriptp,
int32_t* ip)
{
RootedScript script(cx, GetTopScript(cx));
*ip = 0;
if (argc != 0) {
Value v = argv[0];
unsigned intarg = 0;
if (v.isObject() &&
JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) {
script = ValueToScript(cx, v);
if (!script)
return false;
intarg++;
}
if (argc > intarg) {
if (!JS::ToInt32(cx, HandleValue::fromMarkedLocation(&argv[intarg]), ip))
return false;
if ((uint32_t)*ip >= script->length()) {
JS_ReportError(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_ReportErrorNumber(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.length(), args.array(), &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 void
SrcNotes(JSContext* cx, HandleScript script, Sprinter* sp)
{
Sprint(sp, "\nSource notes:\n");
Sprint(sp, "%4s %4s %5s %6s %-8s %s\n",
"ofs", "line", "pc", "delta", "desc", "args");
Sprint(sp, "---- ---- ----- ------ -------- ------\n");
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;
Sprint(sp, "%3u: %4u %5u [%4u] %-8s", unsigned(sn - notes), lineno, offset, delta, name);
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));
Sprint(sp, "%d", colspan);
break;
case SRC_SETLINE:
lineno = GetSrcNoteOffset(sn, 0);
Sprint(sp, " lineno %u", lineno);
break;
case SRC_NEWLINE:
++lineno;
break;
case SRC_FOR:
Sprint(sp, " cond %u update %u tail %u",
unsigned(GetSrcNoteOffset(sn, 0)),
unsigned(GetSrcNoteOffset(sn, 1)),
unsigned(GetSrcNoteOffset(sn, 2)));
break;
case SRC_IF_ELSE:
Sprint(sp, " else %u", unsigned(GetSrcNoteOffset(sn, 0)));
break;
case SRC_FOR_IN:
case SRC_FOR_OF:
Sprint(sp, " closingjump %u", unsigned(GetSrcNoteOffset(sn, 0)));
break;
case SRC_COND:
case SRC_WHILE:
case SRC_NEXTCASE:
Sprint(sp, " offset %u", unsigned(GetSrcNoteOffset(sn, 0)));
break;
case SRC_TABLESWITCH: {
JSOp op = JSOp(script->code()[offset]);
MOZ_ASSERT(op == JSOP_TABLESWITCH);
Sprint(sp, " length %u", unsigned(GetSrcNoteOffset(sn, 0)));
UpdateSwitchTableBounds(cx, script, offset,
&switchTableStart, &switchTableEnd);
break;
}
case SRC_CONDSWITCH: {
JSOp op = JSOp(script->code()[offset]);
MOZ_ASSERT(op == JSOP_CONDSWITCH);
Sprint(sp, " length %u", unsigned(GetSrcNoteOffset(sn, 0)));
unsigned caseOff = (unsigned) GetSrcNoteOffset(sn, 1);
if (caseOff)
Sprint(sp, " first case offset %u", caseOff);
UpdateSwitchTableBounds(cx, script, offset,
&switchTableStart, &switchTableEnd);
break;
}
case SRC_TRY:
MOZ_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY);
Sprint(sp, " offset to jump %u", unsigned(GetSrcNoteOffset(sn, 0)));
break;
default:
MOZ_ASSERT(0);
break;
}
Sprint(sp, "\n");
}
}
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;
SrcNotes(cx, script, &sprinter);
}
JSString* str = JS_NewStringCopyZ(cx, sprinter.string());
if (!str)
return false;
args.rval().setString(str);
return true;
}
JS_STATIC_ASSERT(JSTRY_CATCH == 0);
JS_STATIC_ASSERT(JSTRY_FINALLY == 1);
JS_STATIC_ASSERT(JSTRY_FOR_IN == 2);
static const char* const TryNoteNames[] = { "catch", "finally", "for-in", "for-of", "loop" };
static bool
TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
{
if (!script->hasTrynotes())
return true;
JSTryNote* tn = script->trynotes()->vector;
JSTryNote* tnlimit = tn + script->trynotes()->length;
Sprint(sp, "\nException table:\nkind stack start end\n");
do {
MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames));
Sprint(sp, " %-7s %6u %8u %8u\n",
TryNoteNames[tn->kind], tn->stackDepth,
tn->start, tn->start + tn->length);
} while (++tn != tnlimit);
return true;
}
static bool
BlockNotes(JSContext* cx, HandleScript script, Sprinter* sp)
{
if (!script->hasBlockScopes())
return true;
Sprint(sp, "\nBlock table:\n index parent start end\n");
BlockScopeArray* scopes = script->blockScopes();
for (uint32_t i = 0; i < scopes->length; i++) {
const BlockScopeNote* note = &scopes->vector[i];
if (note->index == BlockScopeNote::NoBlockScopeIndex)
Sprint(sp, "%8s ", "(none)");
else
Sprint(sp, "%8u ", note->index);
if (note->parent == BlockScopeNote::NoBlockScopeIndex)
Sprint(sp, "%8s ", "(none)");
else
Sprint(sp, "%8u ", note->parent);
Sprint(sp, "%8u %8u\n", note->start, note->start + note->length);
}
return true;
}
static bool
DisassembleScript(JSContext* cx, HandleScript script, HandleFunction fun,
bool lines, bool recursive, bool sourceNotes, Sprinter* sp)
{
if (fun) {
Sprint(sp, "flags:");
if (fun->isLambda())
Sprint(sp, " LAMBDA");
if (fun->needsCallObject())
Sprint(sp, " NEEDS_CALLOBJECT");
if (fun->isConstructor())
Sprint(sp, " CONSTRUCTOR");
if (fun->isExprBody())
Sprint(sp, " EXPRESSION_CLOSURE");
if (fun->isFunctionPrototype())
Sprint(sp, " Function.prototype");
if (fun->isSelfHostedBuiltin())
Sprint(sp, " SELF_HOSTED");
if (fun->isArrow())
Sprint(sp, " ARROW");
Sprint(sp, "\n");
}
if (!Disassemble(cx, script, lines, sp))
return false;
if (sourceNotes)
SrcNotes(cx, script, sp);
TryNotes(cx, script, sp);
BlockNotes(cx, script, sp);
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>()) {
Sprint(sp, "\n");
RootedFunction fun(cx, &obj->as<JSFunction>());
if (fun->isInterpreted()) {
RootedScript script(cx, fun->getOrCreateScript(cx));
if (script) {
if (!DisassembleScript(cx, script, fun, lines, recursive, sourceNotes, sp))
return false;
}
} else {
Sprint(sp, "[native code]\n");
}
}
}
}
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;
SrcNotes(cx, script, sprinter);
TryNotes(cx, script, sprinter);
BlockNotes(cx, script, sprinter);
}
} 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);
Sprinter sprinter(cx);
if (!sprinter.init())
return false;
if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter))
return false;
fprintf(stdout, "%s\n", sprinter.string());
args.rval().setUndefined();
return true;
}
static bool
DisassFile(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* 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(stdout, "%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);
#define LINE_BUF_LEN 512
unsigned len, line1, line2, bupline;
FILE* file;
char linebuf[LINE_BUF_LEN];
static const char sep[] = ";-------------------------";
bool ok = true;
RootedScript script(cx);
for (unsigned i = 0; ok && i < args.length(); i++) {
script = ValueToScript(cx, args[i]);
if (!script)
return false;
if (!script->filename()) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_FILE_SCRIPTS_ONLY);
return false;
}
file = fopen(script->filename(), "r");
if (!file) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_CANT_OPEN, script->filename(),
strerror(errno));
return false;
}
jsbytecode* pc = script->code();
jsbytecode* end = script->codeEnd();
Sprinter sprinter(cx);
if (!sprinter.init()) {
ok = false;
goto bail;
}
/* burn the leading lines */
line2 = PCToLineNumber(script, pc);
for (line1 = 0; line1 < line2 - 1; line1++) {
char* tmp = fgets(linebuf, LINE_BUF_LEN, file);
if (!tmp) {
JS_ReportError(cx, "failed to read %s fully", script->filename());
ok = false;
goto bail;
}
}
bupline = 0;
while (pc < end) {
line2 = PCToLineNumber(script, pc);
if (line2 < line1) {
if (bupline != line2) {
bupline = line2;
Sprint(&sprinter, "%s %3u: BACKUP\n", sep, line2);
}
} else {
if (bupline && line1 == line2)
Sprint(&sprinter, "%s %3u: RESTORE\n", sep, line2);
bupline = 0;
while (line1 < line2) {
if (!fgets(linebuf, LINE_BUF_LEN, file)) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
JSSMSG_UNEXPECTED_EOF,
script->filename());
ok = false;
goto bail;
}
line1++;
Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf);
}
}
len = Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter);
if (!len) {
ok = false;
goto bail;
}
pc += len;
}
fprintf(stdout, "%s\n", sprinter.string());
bail:
fclose(file);
}
args.rval().setUndefined();
return ok;
#undef LINE_BUF_LEN
}
#endif /* DEBUG */
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.start().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_ReportError(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
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_ReportError(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 JSClass sandbox_class = {
"sandbox",
JSCLASS_GLOBAL_FLAGS,
nullptr, nullptr, nullptr, nullptr,
sandbox_enumerate, sandbox_resolve,
nullptr, nullptr,
nullptr, nullptr, nullptr,
JS_GlobalObjectTraceHook
};
static JSObject*
NewSandbox(JSContext* cx, bool lazy)
{
RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, nullptr,
JS::DontFireOnNewGlobalHook));
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.start().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_ReportError(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
{
JSRuntime* runtime;
char16_t* chars;
size_t length;
WorkerInput(JSRuntime* runtime, char16_t* chars, size_t length)
: runtime(runtime), chars(chars), length(length)
{}
~WorkerInput() {
js_free(chars);
}
};
static void SetWorkerRuntimeOptions(JSRuntime* rt);
static void
WorkerMain(void* arg)
{
WorkerInput* input = (WorkerInput*) arg;
JSRuntime* rt = JS_NewRuntime(8L * 1024L * 1024L, 2L * 1024L * 1024L, input->runtime);
if (!rt) {
js_delete(input);
return;
}
mozilla::UniquePtr<ShellRuntime> sr = MakeUnique<ShellRuntime>();
if (!sr) {
JS_DestroyRuntime(rt);
js_delete(input);
return;
}
sr->isWorker = true;
JS_SetRuntimePrivate(rt, sr.get());
JS_SetErrorReporter(rt, my_ErrorReporter);
SetWorkerRuntimeOptions(rt);
if (!InitWatchdog(rt)) {
JS_DestroyRuntime(rt);
js_delete(input);
return;
}
JSContext* cx = NewContext(rt);
if (!cx) {
JS_DestroyRuntime(rt);
js_delete(input);
return;
}
JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx);
do {
JSAutoRequest ar(cx);
JS::CompartmentOptions compartmentOptions;
compartmentOptions.setVersion(JSVERSION_DEFAULT);
RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr));
if (!global)
break;
JSAutoCompartment ac(cx, global);
JS::CompileOptions options(cx);
options.setFileAndLine("<string>", 1)
.setIsRunOnce(true);
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(rt, nullptr, nullptr);
DestroyContext(cx, false);
JS_DestroyRuntime(rt);
js_delete(input);
}
Vector<PRThread*, 0, SystemAllocPolicy> workerThreads;
static bool
EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
{
if (!CanUseExtraThreads()) {
JS_ReportError(cx, "Can't create worker threads with --no-threads");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isString()) {
JS_ReportError(cx, "Invalid arguments to evalInWorker");
return false;
}
if (!args[0].toString()->ensureLinear(cx))
return false;
JSLinearString* str = &args[0].toString()->asLinear();
char16_t* chars = (char16_t*) js_malloc(str->length() * sizeof(char16_t));
if (!chars)
return false;
CopyChars(chars, *str);
WorkerInput* input = js_new<WorkerInput>(cx->runtime(), chars, str->length());
if (!input)
return false;
PRThread* thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
gMaxStackSize + 128 * 1024);
if (!thread || !workerThreads.append(thread))
return false;
return true;
}
static bool
ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.get(0).isObject()) {
JS_ReportError(cx, "shapeOf: object expected");
return false;
}
JSObject* obj = &args[0].toObject();
args.rval().set(JS_NumberValue(double(uintptr_t(obj->maybeShape()) >> 3)));
return true;
}
/*
* Check that t1 comes strictly before t2. The function correctly deals with
* wrap-around between t2 and t1 assuming that t2 and t1 stays within INT32_MAX
* from each other. We use MAX_TIMEOUT_INTERVAL to enforce this restriction.
*/
static bool
IsBefore(int64_t t1, int64_t t2)
{
return int32_t(t1 - t2) < 0;
}
static bool
Sleep_fn(JSContext* cx, unsigned argc, Value* vp)
{
ShellRuntime* sr = GetShellRuntime(cx);
CallArgs args = CallArgsFromVp(argc, vp);
int64_t t_ticks;
if (args.length() == 0) {
t_ticks = 0;
} else {