blob: 03d001097e123b6f386e83f9d2638389b798ec5b [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 <errno.h>
#include <locale.h>
#include <math.h>
#if !defined(STARBOARD)
#include <signal.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mozilla/DebugOnly.h"
#include "mozilla/GuardObjects.h"
#include "mozilla/Util.h"
#include "jstypes.h"
#include "jsutil.h"
#include "jsprf.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jscntxt.h"
#include "jsdate.h"
#include "jsdbgapi.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsiter.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
#include "json.h"
#include "jsreflect.h"
#include "jsscript.h"
#include "jstypedarray.h"
#include "jstypedarrayinlines.h"
#include "jsworkers.h"
#include "jswrapper.h"
#include "jsperf.h"
#include "builtin/TestingFunctions.h"
#include "frontend/BytecodeEmitter.h"
#include "frontend/Parser.h"
#include "vm/Shape.h"
#include "prmjtime.h"
#include "jsoptparse.h"
#include "jsheaptools.h"
#include "jsinferinlines.h"
#include "jsscriptinlines.h"
#include "jit/Ion.h"
#include "vm/Interpreter-inl.h"
#ifdef XP_UNIX
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
#if defined(XP_WIN) || defined(XP_OS2)
#include <io.h> /* for isatty() */
#endif
#ifdef XP_WIN
# include <io.h>
# include <direct.h>
# include "jswin.h"
# define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
#elif !defined(STARBOARD)
# include <libgen.h>
#endif
#if JS_TRACE_LOGGING
#include "TraceLogging.h"
#endif
#if defined(DEBUG) && defined(STARBOARD)
// On Starboard platforms, DEBUG will get #undef'd when we #include zlib.h
#define STARBOARD_DEBUG
#endif
#ifdef USE_ZLIB
#include "zlib.h"
#endif
#include "starboard/client_porting/wrap_main/wrap_main.h"
#include "starboard/memory.h"
using namespace js;
using namespace js::cli;
using mozilla::ArrayLength;
using mozilla::Maybe;
typedef enum JSShellExitCode {
EXITCODE_RUNTIME_ERROR = 3,
EXITCODE_FILE_NOT_FOUND = 4,
EXITCODE_OUT_OF_MEMORY = 5,
EXITCODE_TIMEOUT = 6
} JSShellExitCode;
size_t gStackChunkSize = 8192;
#if defined(STARBOARD)
size_t CalculateStackQuota() {
void* stack_high;
void* stack_low;
SbMemoryGetStackBounds(&stack_high, &stack_low);
return 3 * (reinterpret_cast<intptr_t>(stack_high) - reinterpret_cast<intptr_t>(stack_low)) / 4;
}
#else
/*
* 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))
size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
#else
size_t gMaxStackSize = 128 * sizeof(size_t) * 1024;
#endif
#endif
#ifdef JS_THREADSAFE
#if defined(STARBOARD)
static PRTLSIndex gStackBaseThreadIndex;
#else
static unsigned gStackBaseThreadIndex;
#endif // defined(STARBOARD)
#else
static uintptr_t gStackBase;
#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 double MAX_TIMEOUT_INTERVAL = 1800.0;
static double gTimeoutInterval = -1.0;
static volatile bool gTimedOut = false;
static JS::Value gTimeoutFunc;
static bool enableTypeInference = true;
static bool enableDisassemblyDumps = false;
static bool enableIon = true;
static bool enableBaseline = true;
static bool enableAsmJS = true;
static bool printTiming = false;
static JSBool
SetTimeoutValue(JSContext *cx, double t);
static bool
InitWatchdog(JSRuntime *rt);
static void
KillWatchdog();
static bool
ScheduleWatchdog(JSRuntime *rt, double t);
static void
CancelExecution(JSRuntime *rt);
/*
* Watchdog thread state.
*/
#ifdef JS_THREADSAFE
static PRLock *gWatchdogLock = NULL;
static PRCondVar *gWatchdogWakeup = NULL;
static PRThread *gWatchdogThread = NULL;
static bool gWatchdogHasTimeout = false;
static int64_t gWatchdogTimeout = 0;
static PRCondVar *gSleepWakeup = NULL;
#else
static JSRuntime *gRuntime = NULL;
#endif
int gExitCode = 0;
bool gQuitting = false;
bool gGotError = false;
FILE *gErrFile = NULL;
FILE *gOutFile = NULL;
static bool reportWarnings = true;
static bool compileOnly = false;
static bool fuzzingSafe = false;
#if defined(DEBUG) || defined(STARBOARD_DEBUG)
static bool OOM_printAllocationCount = false;
#endif
typedef enum JSShellErrNum {
#define MSG_DEF(name, number, count, exception, format) \
name = number,
#include "jsshell.msg"
#undef MSG_DEF
JSShellErr_Limit
} JSShellErrNum;
static JSContext *
NewContext(JSRuntime *rt);
static void
DestroyContext(JSContext *cx, bool withGC);
static const JSErrorFormatString *
my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber);
#ifdef EDITLINE
JS_BEGIN_EXTERN_C
extern JS_EXPORT_API(char *) readline(const char *prompt);
extern JS_EXPORT_API(void) add_history(char *line);
JS_END_EXTERN_C
#endif
static void
ReportException(JSContext *cx)
{
if (JS_IsExceptionPending(cx)) {
if (!JS_ReportPendingException(cx))
JS_ClearPendingException(cx);
}
}
class ToStringHelper
{
public:
ToStringHelper(JSContext *aCx, HandleValue v, bool aThrow = false)
: cx(aCx), mStr(cx, JS_ValueToString(cx, v))
{
if (!aThrow && !mStr)
ReportException(cx);
}
ToStringHelper(JSContext *aCx, HandleId id, bool aThrow = false)
: cx(aCx), mStr(cx, JS_ValueToString(cx, IdToValue(id)))
{
if (!aThrow && !mStr)
ReportException(cx);
}
bool threw() { return !mStr; }
jsval getJSVal() { return STRING_TO_JSVAL(mStr); }
const char *getBytes() {
if (mStr && (mBytes.ptr() || mBytes.encodeLatin1(cx, mStr)))
return mBytes.ptr();
return "(error converting value)";
}
private:
JSContext *cx;
RootedString mStr; // Objects of this class are always stack-allocated.
JSAutoByteString mBytes;
};
static char *
GetLine(FILE *file, const char * prompt)
{
size_t size;
char *buffer;
#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 NULL;
if (linep[0] != '\0')
add_history(linep);
return linep;
}
#endif
size_t len = 0;
if (*prompt != '\0') {
fprintf(gOutFile, "%s", prompt);
fflush(gOutFile);
}
size = 80;
buffer = (char *) js_malloc(size);
if (!buffer)
return NULL;
char *current = buffer;
while (fgets(current, size - len, file)) {
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 = (char *) js_realloc(buffer, size);
if (!tmp) {
js_free(buffer);
return NULL;
}
buffer = tmp;
}
current = buffer + len;
}
if (len && !ferror(file))
return buffer;
js_free(buffer);
return NULL;
}
static char *
JSStringToUTF8(JSContext *cx, JSString *str)
{
JSLinearString *linear = str->ensureLinear(cx);
if (!linear)
return NULL;
return TwoByteCharsToNewUTF8CharsZ(cx, linear->range()).c_str();
}
/*
* State to store as JSContext private.
*
* We declare such timestamp as volatile as they are updated in the operation
* callback without taking any locks. Any possible race can only lead to more
* frequent callback calls. This is safe as the callback does everything based
* on timing.
*/
struct JSShellContextData {
volatile int64_t startTime;
};
static JSShellContextData *
NewContextData()
{
/* Prevent creation of new contexts after we have been canceled. */
if (gTimedOut)
return NULL;
JSShellContextData *data = (JSShellContextData *)
js_calloc(sizeof(JSShellContextData), 1);
if (!data)
return NULL;
data->startTime = PRMJ_Now();
return data;
}
static inline JSShellContextData *
GetContextData(JSContext *cx)
{
JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx);
JS_ASSERT(data);
return data;
}
static JSBool
ShellOperationCallback(JSContext *cx)
{
if (!gTimedOut)
return true;
JS_ClearPendingException(cx);
bool result;
if (!gTimeoutFunc.isNull()) {
RootedValue returnedValue(cx);
if (!JS_CallFunctionValue(cx, NULL, gTimeoutFunc, 0, NULL, returnedValue.address()))
return false;
if (returnedValue.isBoolean())
result = returnedValue.toBoolean();
else
result = false;
} else {
result = false;
}
if (!result && gExitCode == 0)
gExitCode = EXITCODE_TIMEOUT;
return result;
}
static void
SetContextOptions(JSContext *cx)
{
JS_SetOperationCallback(cx, ShellOperationCallback);
}
/*
* 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
#if defined(STARBOARD)
RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, SbFile file,
bool compileOnly)
{
// We may not handle lines starting with # correctly without
// adding more here.
#else
RunFile(JSContext *cx, Handle<JSObject*> obj, 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);
#endif
int64_t t1 = PRMJ_Now();
uint32_t oldopts = JS_GetOptions(cx);
gGotError = false;
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
CompileOptions options(cx);
options.setUTF8(true)
.setFileAndLine(filename, 1);
RootedScript script(cx);
script = JS::Compile(cx, obj, options, file);
JS_SetOptions(cx, oldopts);
JS_ASSERT_IF(!script, gGotError);
if (script && !compileOnly) {
if (!JS_ExecuteScript(cx, obj, script, NULL)) {
if (!gQuitting && !gTimedOut)
gExitCode = EXITCODE_RUNTIME_ERROR;
}
int64_t t2 = PRMJ_Now() - t1;
if (printTiming)
printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
}
}
static bool
EvalAndPrint(JSContext *cx, Handle<JSObject*> global, const char *bytes, size_t length,
int lineno, bool compileOnly, FILE *out)
{
// Eval.
JS::CompileOptions options(cx);
options.utf8 = true;
options.compileAndGo = true;
options.filename = "typein";
options.lineno = lineno;
RootedScript script(cx);
script = JS::Compile(cx, global, options, bytes, length);
if (!script)
return false;
if (compileOnly)
return true;
RootedValue result(cx);
if (!JS_ExecuteScript(cx, global, script, result.address()))
return false;
if (!result.isUndefined()) {
// Print.
RootedString str(cx);
str = JS_ValueToSource(cx, result);
if (!str)
return false;
char *utf8chars = JSStringToUTF8(cx, str);
if (!utf8chars)
return false;
fprintf(out, "%s\n", utf8chars);
JS_free(cx, utf8chars);
}
return true;
}
static void
ReadEvalPrintLoop(JSContext *cx, Handle<JSObject*> global, FILE *in, FILE *out, bool compileOnly)
{
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, ContextAllocPolicy> CharBuffer;
CharBuffer buffer(cx);
do {
ScheduleWatchdog(cx->runtime(), -1);
gTimedOut = 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(), gTimeoutInterval)) {
hitEOF = true;
break;
}
} while (!JS_BufferIsCompilableUnit(cx, global, buffer.begin(), buffer.length()));
if (hitEOF && buffer.empty())
break;
if (!EvalAndPrint(cx, global, buffer.begin(), buffer.length(), startline, compileOnly, out))
{
// Catch the error, report it, and keep going.
JS_ReportPendingException(cx);
}
} while (!hitEOF && !gQuitting);
fprintf(out, "\n");
}
class AutoCloseInputFile
{
private:
FILE *f_;
public:
explicit AutoCloseInputFile(FILE *f) : f_(f) {}
~AutoCloseInputFile() {
if (f_ && f_ != stdin)
fclose(f_);
}
};
#if defined(STARBOARD)
static void
Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY)
{
// No interactive mode for Starboard : stdin not guaranteed.
if (forceTTY) {
return;
}
RootedObject obj(cx, obj_);
SbFile file = SbFileOpen(filename, kSbFileOpenOnly | kSbFileRead, NULL,
NULL);
if (!SbFileIsValid(file)) {
SbFileClose(file);
return;
}
SetContextOptions(cx);
RunFile(cx, obj, filename, file, compileOnly);
SbFileClose(file);
}
#else
static void
Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY)
{
RootedObject obj(cx, obj_);
FILE *file;
if (forceTTY || !filename || strcmp(filename, "-") == 0) {
file = stdin;
} else {
file = fopen(filename, "r");
if (!file) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_CANT_OPEN, filename, strerror(errno));
gExitCode = EXITCODE_FILE_NOT_FOUND;
return;
}
}
AutoCloseInputFile autoClose(file);
SetContextOptions(cx);
if (!forceTTY && !isatty(fileno(file))) {
// It's not interactive - just execute it.
RunFile(cx, obj, filename, file, compileOnly);
} else {
// It's an interactive filehandle; drop into read-eval-print loop.
ReadEvalPrintLoop(cx, obj, file, gOutFile, compileOnly);
}
}
#endif // STARBOARD
/*
* JSContext option name to flag map. The option names are in alphabetical
* order for better reporting.
*/
static const struct JSOption {
const char *name;
uint32_t flag;
} js_options[] = {
{"strict", JSOPTION_EXTRA_WARNINGS},
{"typeinfer", JSOPTION_TYPE_INFERENCE},
{"werror", JSOPTION_WERROR},
{"strict_mode", JSOPTION_STRICT_MODE},
};
static uint32_t
MapContextOptionNameToFlag(JSContext* cx, const char* name)
{
for (size_t i = 0; i < ArrayLength(js_options); ++i) {
if (strcmp(name, js_options[i].name) == 0)
return js_options[i].flag;
}
char* msg = JS_sprintf_append(NULL,
"unknown option name '%s'."
" The valid names are ", name);
for (size_t i = 0; i < ArrayLength(js_options); ++i) {
if (!msg)
break;
msg = JS_sprintf_append(msg, "%s%s", js_options[i].name,
(i + 2 < ArrayLength(js_options)
? ", "
: i + 2 == ArrayLength(js_options)
? " and "
: "."));
}
if (!msg) {
JS_ReportOutOfMemory(cx);
} else {
JS_ReportError(cx, msg);
JS_xprintf_free(msg);
}
return 0;
}
extern JSClass global_class;
static JSBool
Version(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSVersion origVersion = JS_GetVersion(cx);
if (args.length() == 0 || JSVAL_IS_VOID(args[0])) {
/* 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();
if (int32_t(fv) == fv)
v = int32_t(fv);
}
if (v < 0 || v > JSVERSION_LATEST) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "version");
return false;
}
JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v));
args.rval().setInt32(origVersion);
}
return true;
}
static JSScript *
GetTopScript(JSContext *cx)
{
RootedScript script(cx);
JS_DescribeScriptedCaller(cx, script.address(), NULL);
return script;
}
/*
* Resolve a (possibly) relative filename to an absolute path. If
* |scriptRelative| is true, then the result will be relative to the directory
* containing the currently-running script, or the current working directory if
* the currently-running script is "-e" (namely, you're using it from the
* command line.) Otherwise, it will be relative to the current working
* directory.
*/
static JSString *
ResolvePath(JSContext *cx, HandleString filenameStr, bool scriptRelative)
{
JSAutoByteString filename(cx, filenameStr);
if (!filename)
return NULL;
const char *pathname = filename.ptr();
if (pathname[0] == '/')
return filenameStr;
#ifdef XP_WIN
// Various forms of absolute paths per http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
// "\..."
if (pathname[0] == '\\')
return filenameStr;
// "C:\..."
if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\')
return filenameStr;
// "\\..."
if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\')
return filenameStr;
#endif
/* Get the currently executing script's name. */
RootedScript script(cx, GetTopScript(cx));
if (!script->filename())
return NULL;
if (strcmp(script->filename(), "-e") == 0 || strcmp(script->filename(), "typein") == 0)
scriptRelative = false;
static char buffer[PATH_MAX+1];
if (scriptRelative) {
#if defined(STARBOARD)
// We do not expect relative path scripts to work in Starboard.
return NULL;
#elif defined(XP_WIN)
// The docs say it can return EINVAL, but the compiler says it's void
_splitpath(script->filename(), NULL, buffer, NULL, NULL);
#else
strncpy(buffer, script->filename(), PATH_MAX+1);
if (buffer[PATH_MAX] != '\0')
return NULL;
// dirname(buffer) might return buffer, or it might return a
// statically-allocated string
memmove(buffer, dirname(buffer), strlen(buffer) + 1);
#endif
} else {
#if defined(STARBOARD)
// For Starboard, the tests scripts are in
// content/dir_source_root/mozjs/tests
// so we let that play the role of the cwd.
bool result = SbSystemGetPath(kSbSystemPathSourceDirectory,
buffer, PATH_MAX + 1);
if (!result) {
return NULL;
}
// Append subdirectory /mozjs/tests.
SbStringConcat(buffer, "/mozjs/tests", PATH_MAX + 1);
#else
const char *cwd = getcwd(buffer, PATH_MAX);
if (!cwd)
return NULL;
#endif
}
size_t len = strlen(buffer);
buffer[len] = '/';
strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1));
if (buffer[PATH_MAX] != '\0')
return NULL;
return JS_NewStringCopyZ(cx, buffer);
}
static JSBool
Options(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
uint32_t flag;
JSString *str;
char *names;
bool found;
uint32_t optset = 0;
for (unsigned i = 0; i < args.length(); i++) {
str = JS_ValueToString(cx, args[i]);
if (!str)
return false;
args[i] = STRING_TO_JSVAL(str);
JSAutoByteString opt(cx, str);
if (!opt)
return false;
flag = MapContextOptionNameToFlag(cx, opt.ptr());
if (!flag)
return false;
optset |= flag;
}
optset = JS_ToggleOptions(cx, optset);
names = NULL;
found = false;
for (size_t i = 0; i < ArrayLength(js_options); i++) {
if (js_options[i].flag & optset) {
found = true;
names = JS_sprintf_append(names, "%s%s",
names ? "," : "", js_options[i].name);
if (!names)
break;
}
}
if (!found)
names = js_strdup((char*)"");
if (!names) {
JS_ReportOutOfMemory(cx);
return false;
}
str = JS_NewStringCopyZ(cx, names);
JS_xprintf_free(names);
if (!str)
return false;
args.rval().setString(str);
return true;
}
static JSBool
LoadScript(JSContext *cx, unsigned argc, jsval *vp, bool scriptRelative)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp));
if (!thisobj)
return false;
RootedString str(cx);
for (unsigned i = 0; i < args.length(); i++) {
str = JS_ValueToString(cx, args[i]);
if (!str) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "load");
return false;
}
str = ResolvePath(cx, str, scriptRelative);
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.setUTF8(true).setCompileAndGo(true).setNoScriptRval(true);
if ((compileOnly && !Compile(cx, thisobj, opts, filename.ptr())) ||
!Evaluate(cx, thisobj, opts, filename.ptr(), NULL))
{
return false;
}
}
args.rval().setUndefined();
return true;
}
static JSBool
Load(JSContext *cx, unsigned argc, jsval *vp)
{
return LoadScript(cx, argc, vp, false);
}
static JSBool
LoadScriptRelativeToScript(JSContext *cx, unsigned argc, jsval *vp)
{
return LoadScript(cx, argc, vp, true);
}
class AutoNewContext
{
private:
JSContext *oldcx;
JSContext *newcx;
Maybe<JSAutoRequest> newRequest;
Maybe<AutoCompartment> newCompartment;
AutoNewContext(const AutoNewContext &) MOZ_DELETE;
public:
AutoNewContext() : oldcx(NULL), newcx(NULL) {}
bool enter(JSContext *cx) {
JS_ASSERT(!JS_IsExceptionPending(cx));
oldcx = cx;
newcx = NewContext(JS_GetRuntime(cx));
if (!newcx)
return false;
JS_SetOptions(newcx, JS_GetOptions(newcx) | JSOPTION_DONT_REPORT_UNCAUGHT);
JS_SetGlobalObject(newcx, JS_GetGlobalForScopeChain(cx));
newRequest.construct(newcx);
newCompartment.construct(newcx, JS_GetGlobalForScopeChain(cx));
return true;
}
JSContext *get() { return newcx; }
~AutoNewContext() {
if (newcx) {
RootedValue exc(oldcx);
bool throwing = JS_IsExceptionPending(newcx);
if (throwing)
JS_GetPendingException(newcx, exc.address());
newCompartment.destroy();
newRequest.destroy();
if (throwing)
JS_SetPendingException(oldcx, exc);
DestroyContext(newcx, false);
}
}
};
class AutoSaveFrameChain
{
JSContext *cx_;
bool saved_;
public:
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 JSBool
Evaluate(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1 || args.length() > 2) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
"evaluate");
return false;
}
if (!args[0].isString() || (args.length() == 2 && args[1].isPrimitive())) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "evaluate");
return false;
}
bool newContext = false;
bool compileAndGo = true;
bool noScriptRval = false;
const char *fileName = "@evaluate";
RootedObject element(cx);
JSAutoByteString fileNameBytes;
RootedString sourceMapURL(cx);
unsigned lineNumber = 1;
RootedObject global(cx, NULL);
bool catchTermination = false;
bool saveFrameChain = false;
RootedObject callerGlobal(cx, cx->global());
global = JS_GetGlobalForObject(cx, &args.callee());
if (!global)
return false;
if (args.length() == 2) {
RootedObject opts(cx, &args[1].toObject());
RootedValue v(cx);
if (!JS_GetProperty(cx, opts, "newContext", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
JSBool b;
if (!JS_ValueToBoolean(cx, v, &b))
return false;
newContext = b;
}
if (!JS_GetProperty(cx, opts, "compileAndGo", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
JSBool b;
if (!JS_ValueToBoolean(cx, v, &b))
return false;
compileAndGo = b;
}
if (!JS_GetProperty(cx, opts, "noScriptRval", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
JSBool b;
if (!JS_ValueToBoolean(cx, v, &b))
return false;
noScriptRval = b;
}
if (!JS_GetProperty(cx, opts, "fileName", v.address()))
return false;
if (JSVAL_IS_NULL(v)) {
fileName = NULL;
} else if (!JSVAL_IS_VOID(v)) {
JSString *s = JS_ValueToString(cx, v);
if (!s)
return false;
fileName = fileNameBytes.encodeLatin1(cx, s);
if (!fileName)
return false;
}
if (!JS_GetProperty(cx, opts, "element", v.address()))
return false;
if (!JSVAL_IS_PRIMITIVE(v))
element = JSVAL_TO_OBJECT(v);
if (!JS_GetProperty(cx, opts, "sourceMapURL", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
sourceMapURL = JS_ValueToString(cx, v);
if (!sourceMapURL)
return false;
}
if (!JS_GetProperty(cx, opts, "lineNumber", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
uint32_t u;
if (!JS_ValueToECMAUint32(cx, v, &u))
return false;
lineNumber = u;
}
if (!JS_GetProperty(cx, opts, "global", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
global = JSVAL_IS_PRIMITIVE(v) ? NULL : JSVAL_TO_OBJECT(v);
if (global) {
global = js::UncheckedUnwrap(global);
if (!global)
return false;
}
if (!global || !(JS_GetClass(global)->flags & JSCLASS_IS_GLOBAL)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
"\"global\" passed to evaluate()", "not a global object");
return false;
}
}
if (!JS_GetProperty(cx, opts, "catchTermination", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
JSBool b;
if (!JS_ValueToBoolean(cx, v, &b))
return false;
catchTermination = b;
}
if (!JS_GetProperty(cx, opts, "saveFrameChain", v.address()))
return false;
if (!JSVAL_IS_VOID(v)) {
JSBool b;
if (!JS_ValueToBoolean(cx, v, &b))
return false;
saveFrameChain = b;
}
}
RootedString code(cx, args[0].toString());
size_t codeLength;
const jschar *codeChars = JS_GetStringCharsAndLength(cx, code, &codeLength);
if (!codeChars)
return false;
AutoNewContext ancx;
if (newContext) {
if (!ancx.enter(cx))
return false;
cx = ancx.get();
}
{
AutoSaveFrameChain asfc(cx);
if (saveFrameChain && !asfc.save())
return false;
JSAutoCompartment ac(cx, global);
uint32_t oldopts = JS_GetOptions(cx);
uint32_t opts = oldopts & ~(JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
if (compileAndGo)
opts |= JSOPTION_COMPILE_N_GO;
if (noScriptRval)
opts |= JSOPTION_NO_SCRIPT_RVAL;
JS_SetOptions(cx, opts);
CompileOptions options(cx);
options.setFileAndLine(fileName, lineNumber);
options.setElement(element);
RootedScript script(cx, JS::Compile(cx, global, options, codeChars, codeLength));
JS_SetOptions(cx, oldopts);
if (!script)
return false;
if (sourceMapURL && !script->scriptSource()->hasSourceMap()) {
const jschar *smurl = JS_GetStringCharsZ(cx, sourceMapURL);
if (!smurl)
return false;
jschar *smurl_copy = js_strdup(cx, smurl);
if (!smurl_copy || !script->scriptSource()->setSourceMap(cx, smurl_copy, script->filename()))
return false;
}
if (!JS_ExecuteScript(cx, global, script, vp)) {
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;
}
}
return JS_WrapValue(cx, vp);
}
static JSString *
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 NULL;
}
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 {
jschar *ucbuf;
size_t uclen;
len = (size_t)cc;
if (!InflateUTF8StringToBuffer(cx, buf, len, NULL, &uclen)) {
JS_ReportError(cx, "Invalid UTF-8 in file '%s'", pathname);
gExitCode = EXITCODE_RUNTIME_ERROR;
return NULL;
}
ucbuf = (jschar*)js_malloc(uclen * sizeof(jschar));
InflateUTF8StringToBuffer(cx, buf, len, ucbuf, &uclen);
str = JS_NewUCStringCopyN(cx, ucbuf, uclen);
js_free(ucbuf);
}
JS_free(cx, buf);
}
}
}
fclose(file);
return str;
}
static JSObject *
FileAsTypedArray(JSContext *cx, const char *pathname)
{
FILE *file = fopen(pathname, "rb");
if (!file) {
JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno));
return NULL;
}
RootedObject obj(cx);
if (fseek(file, 0, SEEK_END) != 0) {
JS_ReportError(cx, "can't seek end of %s", pathname);
} else {
size_t len = ftell(file);
if (fseek(file, 0, SEEK_SET) != 0) {
JS_ReportError(cx, "can't seek start of %s", pathname);
} else {
obj = JS_NewUint8Array(cx, len);
if (!obj)
return NULL;
char *buf = (char *) TypedArray::viewData(obj);
size_t 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");
obj = NULL;
}
}
}
fclose(file);
return obj;
}
/*
* 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 JSBool
Run(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "run");
return false;
}
RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp));
if (!thisobj)
return false;
JSString *str = JS_ValueToString(cx, args[0]);
if (!str)
return false;
args[0] = STRING_TO_JSVAL(str);
JSAutoByteString filename(cx, str);
if (!filename)
return false;
const jschar *ucbuf = NULL;
size_t buflen;
str = FileAsString(cx, filename.ptr());
if (str)
ucbuf = JS_GetStringCharsAndLength(cx, str, &buflen);
if (!ucbuf)
return false;
JS::Anchor<JSString *> a_str(str);
uint32_t oldopts = JS_GetOptions(cx);
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
int64_t startClock = PRMJ_Now();
RootedScript script(cx, JS_CompileUCScript(cx, thisobj, ucbuf, buflen, filename.ptr(), 1));
JS_SetOptions(cx, oldopts);
if (!script || !JS_ExecuteScript(cx, thisobj, script, NULL))
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 JSBool
ReadLine(JSContext *cx, unsigned argc, jsval *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 = (char *) JS_realloc(cx, buf, bufsize);
} else {
JS_ReportOutOfMemory(cx);
tmp = NULL;
}
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, 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 JSBool
PutStr(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 0) {
JSString *str = JS_ValueToString(cx, args[0]);
if (!str)
return false;
char *bytes = JSStringToUTF8(cx, str);
if (!bytes)
return false;
fputs(bytes, gOutFile);
JS_free(cx, bytes);
fflush(gOutFile);
}
args.rval().setUndefined();
return true;
}
static JSBool
Now(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC);
args.rval().setDouble(now);
return true;
}
static JSBool
PrintInternal(JSContext *cx, const CallArgs &args, FILE *file)
{
for (unsigned i = 0; i < args.length(); i++) {
JSString *str = JS_ValueToString(cx, args[i]);
if (!str)
return false;
char *bytes = JSStringToUTF8(cx, str);
if (!bytes)
return false;
fprintf(file, "%s%s", i ? " " : "", bytes);
#if JS_TRACE_LOGGING
TraceLog(TraceLogging::defaultLogger(), bytes);
#endif
JS_free(cx, bytes);
}
fputc('\n', file);
fflush(file);
args.rval().setUndefined();
return true;
}
static JSBool
Print(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return PrintInternal(cx, args, gOutFile);
}
static JSBool
PrintErr(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return PrintInternal(cx, args, gErrFile);
}
static JSBool
Help(JSContext *cx, unsigned argc, jsval *vp);
static JSBool
Quit(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ConvertArguments(cx, args.length(), args.array(), "/ i", &gExitCode);
gQuitting = true;
return false;
}
static const char *
ToSource(JSContext *cx, jsval *vp, JSAutoByteString *bytes)
{
JSString *str = JS_ValueToSource(cx, *vp);
if (str) {
*vp = STRING_TO_JSVAL(str);
if (bytes->encodeLatin1(cx, str))
return bytes->ptr();
}
JS_ClearPendingException(cx);
return "<<error converting value to string>>";
}
static JSBool
AssertEq(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
(args.length() < 2)
? JSSMSG_NOT_ENOUGH_ARGS
: (args.length() == 3)
? JSSMSG_INVALID_ARGS
: JSSMSG_TOO_MANY_ARGS,
"assertEq");
return false;
}
JSBool 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, NULL, JSSMSG_ASSERT_EQ_FAILED,
actual, expected);
} else {
JSAutoByteString bytes2(cx, args[2].toString());
if (!bytes2)
return false;
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_ASSERT_EQ_FAILED_MSG,
actual, expected, bytes2.ptr());
}
return false;
}
args.rval().setUndefined();
return true;
}
static JSScript *
ValueToScript(JSContext *cx, jsval v, JSFunction **funp = NULL)
{
RootedFunction fun(cx, JS_ValueToFunction(cx, v));
if (!fun)
return NULL;
// 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, NULL, JSSMSG_SCRIPTS_ONLY);
return NULL;
}
JSScript *script = fun->getOrCreateScript(cx);
if (!script)
return NULL;
if (fun && funp)
*funp = fun;
return script;
}
static JSBool
SetDebug(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() == 0 || !args[0].isBoolean()) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_NOT_ENOUGH_ARGS, "setDebug");
return false;
}
/*
* Debug mode can only be set when there is no JS code executing on the
* stack. Unfortunately, that currently means that this call will fail
* unless debug mode is already set to what you're trying to set it to.
* In the future, this restriction may be lifted.
*/
bool ok = !!JS_SetDebugMode(cx, args[0].toBoolean());
if (ok)
args.rval().setBoolean(true);
return ok;
}
static JSBool
GetScriptAndPCArgs(JSContext *cx, unsigned argc, jsval *argv, MutableHandleScript scriptp,
int32_t *ip)
{
RootedScript script(cx, GetTopScript(cx));
*ip = 0;
if (argc != 0) {
jsval v = argv[0];
unsigned intarg = 0;
if (!JSVAL_IS_PRIMITIVE(v) &&
JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) {
script = ValueToScript(cx, v);
if (!script)
return false;
intarg++;
}
if (argc > intarg) {
if (!JS_ValueToInt32(cx, argv[intarg], ip))
return false;
if ((uint32_t)*ip >= script->length) {
JS_ReportError(cx, "Invalid PC");
return false;
}
}
}
scriptp.set(script);
return true;
}
static JSTrapStatus
TrapHandler(JSContext *cx, JSScript *, jsbytecode *pc, jsval *rvalArg,
jsval closure)
{
JSString *str = JSVAL_TO_STRING(closure);
RootedValue rval(cx, *rvalArg);
ScriptFrameIter iter(cx);
JS_ASSERT(!iter.done());
/* Debug-mode currently disables Ion compilation. */
JSAbstractFramePtr frame(Jsvalify(iter.abstractFramePtr()));
RootedScript script(cx, iter.script());
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return JSTRAP_ERROR;
if (!frame.evaluateUCInStackFrame(cx, chars, length,
script->filename(),
script->lineno,
&rval))
{
*rvalArg = rval;
return JSTRAP_ERROR;
}
*rvalArg = rval;
if (!rval.isUndefined())
return JSTRAP_RETURN;
return JSTRAP_CONTINUE;
}
static JSBool
Trap(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedScript script(cx);
int32_t i;
if (args.length() == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE);
return false;
}
argc = args.length() - 1;
RootedString str(cx, JS_ValueToString(cx, args[argc]));
if (!str)
return false;
args[argc] = STRING_TO_JSVAL(str);
if (!GetScriptAndPCArgs(cx, argc, args.array(), &script, &i))
return false;
if (uint32_t(i) >= script->length) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE);
return false;
}
args.rval().setUndefined();
return JS_SetTrap(cx, script, script->code + i, TrapHandler, STRING_TO_JSVAL(str));
}
static JSBool
Untrap(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedScript script(cx);
int32_t i;
if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i))
return false;
JS_ClearTrap(cx, script, script->code + i, NULL, NULL);
args.rval().setUndefined();
return true;
}
static JSTrapStatus
DebuggerAndThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval,
void *closure)
{
return TrapHandler(cx, script, pc, rval, STRING_TO_JSVAL((JSString *)closure));
}
static JSBool
SetDebuggerHandler(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_NOT_ENOUGH_ARGS, "setDebuggerHandler");
return false;
}
JSString *str = JS_ValueToString(cx, args[0]);
if (!str)
return false;
JS_SetDebuggerHandler(cx->runtime(), DebuggerAndThrowHandler, str);
args.rval().setUndefined();
return true;
}
static JSBool
SetThrowHook(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSString *str;
if (args.length() == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_NOT_ENOUGH_ARGS, "setThrowHook");
return false;
}
str = JS_ValueToString(cx, args[0]);
if (!str)
return false;
JS_SetThrowHook(cx->runtime(), DebuggerAndThrowHandler, str);
args.rval().setUndefined();
return true;
}
static JSBool
LineToPC(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedScript script(cx);
int32_t lineArg = 0;
uint32_t lineno;
jsbytecode *pc;
if (args.length() == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_LINE2PC_USAGE);
return false;
}
script = GetTopScript(cx);
jsval v = args[0];
if (!JSVAL_IS_PRIMITIVE(v) &&
JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_))
{
script = ValueToScript(cx, v);
if (!script)
return false;
lineArg++;
}
if (!JS_ValueToECMAUint32(cx, args[lineArg], &lineno))
return false;
pc = JS_LineNumberToPC(cx, script, lineno);
if (!pc)
return false;
args.rval().setInt32(pc - script->code);
return true;
}
static JSBool
PCToLine(JSContext *cx, unsigned argc, jsval *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 = JS_PCToLineNumber(cx, script, script->code + i);
if (!lineno)
return false;
args.rval().setInt32(lineno);
return true;
}
#if defined(DEBUG) || defined(STARBOARD_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->code + 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. */
JS_ASSERT(op == JSOP_CONDSWITCH);
return;
}
*start = (unsigned)(pc - script->code);
*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_HIDDEN:
case SRC_CATCH:
case SRC_XDELTA:
break;
case SRC_COLSPAN:
colspan = js_GetSrcNoteOffset(sn, 0);
if (colspan >= SN_COLSPAN_DOMAIN / 2)
colspan -= SN_COLSPAN_DOMAIN;
Sprint(sp, "%d", colspan);
break;
case SRC_SETLINE:
lineno = js_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(js_GetSrcNoteOffset(sn, 0)),
unsigned(js_GetSrcNoteOffset(sn, 1)),
unsigned(js_GetSrcNoteOffset(sn, 2)));
break;
case SRC_IF_ELSE:
Sprint(sp, " else %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
break;
case SRC_FOR_IN:
Sprint(sp, " closingjump %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
break;
case SRC_COND:
case SRC_WHILE:
case SRC_NEXTCASE:
Sprint(sp, " offset %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
break;
case SRC_TABLESWITCH: {
JSOp op = JSOp(script->code[offset]);
JS_ASSERT(op == JSOP_TABLESWITCH);
Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
UpdateSwitchTableBounds(cx, script, offset,
&switchTableStart, &switchTableEnd);
break;
}
case SRC_CONDSWITCH: {
JSOp op = JSOp(script->code[offset]);
JS_ASSERT(op == JSOP_CONDSWITCH);
Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
unsigned caseOff = (unsigned) js_GetSrcNoteOffset(sn, 1);
if (caseOff)
Sprint(sp, " first case offset %u", caseOff);
UpdateSwitchTableBounds(cx, script, offset,
&switchTableStart, &switchTableEnd);
break;
}
default:
JS_ASSERT(0);
break;
}
Sprint(sp, "\n");
}
}
static JSBool
Notes(JSContext *cx, unsigned argc, jsval *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_ITER == 2);
static const char* const TryNoteNames[] = { "catch", "finally", "iter", "loop" };
static JSBool
TryNotes(JSContext *cx, HandleScript script, Sprinter *sp)
{
JSTryNote *tn, *tnlimit;
if (!script->hasTrynotes())
return true;
tn = script->trynotes()->vector;
tnlimit = tn + script->trynotes()->length;
Sprint(sp, "\nException table:\nkind stack start end\n");
do {
JS_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
DisassembleScript(JSContext *cx, HandleScript script, HandleFunction fun, bool lines,
bool recursive, Sprinter *sp)
{
if (fun) {
Sprint(sp, "flags:");
if (fun->isLambda())
Sprint(sp, " LAMBDA");
if (fun->isHeavyweight())
Sprint(sp, " HEAVYWEIGHT");
if (fun->isExprClosure())
Sprint(sp, " EXPRESSION_CLOSURE");
if (fun->isFunctionPrototype())
Sprint(sp, " Function.prototype");
if (fun->isSelfHostedBuiltin())
Sprint(sp, " SELF_HOSTED");
if (fun->isSelfHostedConstructor())
Sprint(sp, " SELF_HOSTED_CTOR");
if (fun->isArrow())
Sprint(sp, " ARROW");
Sprint(sp, "\n");
}
if (!js_Disassemble(cx, script, lines, sp))
return false;
SrcNotes(cx, script, sp);
TryNotes(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 || !DisassembleScript(cx, script, fun, lines, recursive, sp))
return false;
} else {
Sprint(sp, "[native code]\n");
}
}
}
}
return true;
}
namespace {
struct DisassembleOptionParser {
unsigned argc;
jsval *argv;
bool lines;
bool recursive;
DisassembleOptionParser(unsigned argc, jsval *argv)
: argc(argc), argv(argv), lines(false), recursive(false) {}
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
break;
argv++, argc--;
}
return true;
}
};
} /* anonymous namespace */
static bool
DisassembleToSprinter(JSContext *cx, unsigned argc, jsval *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) {
if (!js_Disassemble(cx, script, p.lines, sprinter))
return false;
SrcNotes(cx, script, sprinter);
TryNotes(cx, script, sprinter);
}
} else {
for (unsigned i = 0; i < p.argc; i++) {
RootedFunction fun(cx);
RootedScript script (cx, ValueToScript(cx, p.argv[i], fun.address()));
if (!script)
return false;
if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, sprinter))
return false;
}
}
return true;
}
static JSBool
DisassembleToString(JSContext *cx, unsigned argc, jsval *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 JSBool
Disassemble(JSContext *cx, unsigned argc, jsval *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 JSBool
DisassFile(JSContext *cx, unsigned argc, jsval *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;
}
RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp));
if (!thisobj)
return false;
JSString *str = JS_ValueToString(cx, p.argv[0]);
if (!str)
return false;
JSAutoByteString filename(cx, str);
if (!filename)
return false;
uint32_t oldopts = JS_GetOptions(cx);
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
CompileOptions options(cx);
options.setUTF8(true)
.setFileAndLine(filename.ptr(), 1);
RootedScript script (cx, JS::Compile(cx, thisobj, options, filename.ptr()));
JS_SetOptions(cx, oldopts);
if (!script)
return false;
Sprinter sprinter(cx);
if (!sprinter.init())
return false;
bool ok = DisassembleScript(cx, script, NullPtr(), p.lines, p.recursive, &sprinter);
if (ok)
fprintf(stdout, "%s\n", sprinter.string());
if (!ok)
return false;
args.rval().setUndefined();
return true;
}
static JSBool
DisassWithSrc(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
#define LINE_BUF_LEN 512
unsigned len, line1, line2, bupline;
FILE *file;
char linebuf[LINE_BUF_LEN];
jsbytecode *pc, *end;
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, NULL,
JSSMSG_FILE_SCRIPTS_ONLY);
return false;
}
file = fopen(script->filename(), "r");
if (!file) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_CANT_OPEN, script->filename(),
strerror(errno));
return false;
}
pc = script->code;
end = pc + script->length;
Sprinter sprinter(cx);
if (!sprinter.init()) {
ok = false;
goto bail;
}
/* burn the leading lines */
line2 = JS_PCToLineNumber(cx, 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 = JS_PCToLineNumber(cx, 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, NULL,
JSSMSG_UNEXPECTED_EOF,
script->filename());
ok = false;
goto bail;
}
line1++;
Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf);
}
}
len = js_Disassemble1(cx, script, pc, pc - script->code, true, &sprinter);
if (!len) {
ok = false;
goto bail;
}
pc += len;
}
bail:
fclose(file);
}
args.rval().setUndefined();
return ok;
#undef LINE_BUF_LEN
}
static JSBool
DumpHeap(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
jsval v;
void* startThing;
JSGCTraceKind startTraceKind;
const char *badTraceArg;
void *thingToFind;
size_t maxDepth;
void *thingToIgnore;
FILE *dumpFile;
bool ok;
const char *fileName = NULL;
JSAutoByteString fileNameBytes;
if (args.length() > 0) {
v = args[0];
if (!v.isNull()) {
JSString *str;
str = JS_ValueToString(cx, v);
if (!str)
return false;
args[0] = STRING_TO_JSVAL(str);
if (!fileNameBytes.encodeLatin1(cx, str))
return false;
fileName = fileNameBytes.ptr();
}
}
// Grab the depth param first, because JS_ValueToECMAUint32 can GC, and
// there's no easy way to root the traceable void* parameters below.
maxDepth = (size_t)-1;
if (args.length() > 3) {
v = args[3];
if (!v.isNull()) {
uint32_t depth;
if (!JS_ValueToECMAUint32(cx, v, &depth))
return false;
maxDepth = depth;
}
}
startThing = NULL;
startTraceKind = JSTRACE_OBJECT;
if (args.length() > 1) {
v = args[1];
if (v.isMarkable()) {
startThing = JSVAL_TO_TRACEABLE(v);
startTraceKind = v.gcKind();
} else if (!v.isNull()) {
badTraceArg = "start";
goto not_traceable_arg;
}
}
thingToFind = NULL;
if (args.length() > 2) {
v = args[2];
if (v.isMarkable()) {
thingToFind = JSVAL_TO_TRACEABLE(v);
} else if (!v.isNull()) {
badTraceArg = "toFind";
goto not_traceable_arg;
}
}
thingToIgnore = NULL;
if (args.length() > 4) {
v = args[4];
if (v.isMarkable()) {
thingToIgnore = JSVAL_TO_TRACEABLE(v);
} else if (!v.isNull()) {
badTraceArg = "toIgnore";
goto not_traceable_arg;
}
}
if (!fileName) {
dumpFile = stdout;
} else {
dumpFile = fopen(fileName, "w");
if (!dumpFile) {
JS_ReportError(cx, "can't open %s: %s", fileName, strerror(errno));
return false;
}
}
ok = JS_DumpHeap(JS_GetRuntime(cx), dumpFile, startThing, startTraceKind, thingToFind,
maxDepth, thingToIgnore);
if (dumpFile != stdout)
fclose(dumpFile);
if (!ok) {
JS_ReportOutOfMemory(cx);
return false;
}
args.rval().setUndefined();
return true;
not_traceable_arg:
JS_ReportError(cx, "argument '%s' is not null or a heap-allocated thing",
badTraceArg);
return false;
}
static JSBool
DumpObject(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject arg0(cx);
if (!JS_ConvertArguments(cx, args.length(), args.array(), "o", arg0.address()))
return false;
js_DumpObject(arg0);
args.rval().setUndefined();
return true;
}
#endif /* DEBUG */
static JSBool
BuildDate(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__);
args.rval().setUndefined();
return true;
}
static JSBool
Intern(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSString *str = JS_ValueToString(cx, args.length() == 0 ? UndefinedValue() : args[0]);
if (!str)
return false;
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return false;
if (!JS_InternUCStringN(cx, chars, length))
return false;
args.rval().setUndefined();
return true;
}
static JSBool
Clone(JSContext *cx, unsigned argc, jsval *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, JSVAL_IS_PRIMITIVE(args[0]) ? NULL : &args[0].toObject());
if (obj && IsCrossCompartmentWrapper(obj)) {
obj = UncheckedUnwrap(obj);
ac.construct(cx, obj);
args[0] = ObjectValue(*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 (funobj->compartment() != cx->compartment()) {
JSFunction *fun = &funobj->as<JSFunction>();
if (fun->hasScript() && fun->nonLazyScript()->compileAndGo) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
"function", "compile-and-go");
return false;
}
}
if (argc > 1) {
if (!JS_ValueToObject(cx, args[1], parent.address()))
return false;
} else {
parent = JS_GetParent(JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));
}
JSObject *clone = JS_CloneFunctionObject(cx, funobj, parent);
if (!clone)
return false;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(clone));
return true;
}
static JSBool
GetPDA(JSContext *cx, unsigned argc, jsval *vp)
{
RootedObject vobj(cx);
bool ok;
JSPropertyDescArray pda;
JSPropertyDesc *pd;
jsval v;
if (!JS_ValueToObject(cx, argc == 0 ? UndefinedValue() : vp[2], vobj.address()))
return false;
if (!vobj) {
JS_SET_RVAL(cx, vp, UndefinedValue());
return true;
}
RootedObject aobj(cx, JS_NewArrayObject(cx, 0, NULL));
if (!aobj)
return false;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(aobj));
ok = !!JS_GetPropertyDescArray(cx, vobj, &pda);
if (!ok)
return false;
pd = pda.array;
RootedObject pdobj(cx);
for (uint32_t i = 0; i < pda.length; i++, pd++) {
pdobj = JS_NewObject(cx, NULL, NULL, NULL);
if (!pdobj) {
ok = false;
break;
}
/* Protect pdobj from GC by setting it as an element of aobj now */
v = OBJECT_TO_JSVAL(pdobj);
ok = !!JS_SetElement(cx, aobj, i, &v);
if (!ok)
break;
ok = JS_SetProperty(cx, pdobj, "id", &pd->id) &&
JS_SetProperty(cx, pdobj, "value", &pd->value) &&
(v = INT_TO_JSVAL(pd->flags),
JS_SetProperty(cx, pdobj, "flags", &v)) &&
JS_SetProperty(cx, pdobj, "alias", &pd->alias);
if (!ok)
break;
}
JS_PutPropertyDescArray(cx, &pda);
return ok;
}
static JSBool
GetSLX(JSContext *cx, unsigned argc, jsval *vp)
{
RootedScript script(cx);
script = ValueToScript(cx, argc == 0 ? UndefinedValue() : vp[2]);
if (!script)
return false;
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(js_GetScriptLineExtent(script)));
return true;
}
static JSBool
ToInt32(JSContext *cx, unsigned argc, jsval *vp)
{
int32_t i;
if (!JS_ValueToInt32(cx, argc == 0 ? UndefinedValue() : vp[2], &i))
return false;
JS_SET_RVAL(cx, vp, JS_NumberValue(i));
return true;
}
static JSBool
ThrowError(JSContext *cx, unsigned argc, jsval *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 JSBool
sandbox_enumerate(JSContext *cx, HandleObject obj)
{
RootedValue v(cx);
JSBool b;
if (!JS_GetProperty(cx, obj, "lazy", v.address()))
return false;
JS_ValueToBoolean(cx, v, &b);
return !b || JS_EnumerateStandardClasses(cx, obj);
}
static JSBool
sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
MutableHandleObject objp)
{
RootedValue v(cx);
JSBool b, resolved;
if (!JS_GetProperty(cx, obj, "lazy", v.address()))
return false;
JS_ValueToBoolean(cx, v, &b);
if (b) {
if (!JS_ResolveStandardClass(cx, obj, id, &resolved))
return false;
if (resolved) {
objp.set(obj);
return true;
}
}
objp.set(NULL);
return true;
}
static JSClass sandbox_class = {
"sandbox",
JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_DeletePropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
sandbox_enumerate, (JSResolveOp)sandbox_resolve,
JS_ConvertStub
};
static JSObject *
NewSandbox(JSContext *cx, bool lazy)
{
RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, NULL));
if (!obj)
return NULL;
{
JSAutoCompartment ac(cx, obj);
if (!lazy && !JS_InitStandardClasses(cx, obj))
return NULL;
RootedValue value(cx, BooleanValue(lazy));
if (!JS_SetProperty(cx, obj, "lazy", value.address()))
return NULL;
}
if (!cx->compartment()->wrap(cx, obj.address()))
return NULL;
return obj;
}
static JSBool
EvalInContext(JSContext *cx, unsigned argc, jsval *vp)
{
RootedString str(cx);
RootedObject sobj(cx);
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "S / o", str.address(), sobj.address()))
return false;
size_t srclen;
const jschar *src = JS_GetStringCharsAndLength(cx, str, &srclen);
if (!src)
return false;
SkipRoot skip(cx, &src);
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) {
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(sobj));
return true;
}
RootedScript script(cx);
unsigned lineno;
JS_DescribeScriptedCaller(cx, script.address(), &lineno);
RootedValue rval(cx);
{
Maybe<JSAutoCompartment> ac;
unsigned flags;
JSObject *unwrapped = UncheckedUnwrap(sobj, true, &flags);
if (flags & Wrapper::CROSS_COMPARTMENT) {
sobj = unwrapped;
ac.construct(cx, sobj);
}
sobj = GetInnerObject(cx, sobj);
if (!sobj)
return false;
if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) {
JS_ReportError(cx, "Invalid scope argument to evalcx");
return false;
}
if (!JS_EvaluateUCScript(cx, sobj, src, srclen,
script->filename(),
lineno,
rval.address())) {
return false;
}
}
if (!cx->compartment()->wrap(cx, &rval))
return false;
JS_SET_RVAL(cx, vp, rval);
return true;
}
static JSBool
EvalInFrame(JSContext *cx, unsigned argc, jsval *vp)
{
jsval *argv = JS_ARGV(cx, vp);
if (argc < 2 ||
!JSVAL_IS_INT(argv[0]) ||
!JSVAL_IS_STRING(argv[1])) {
JS_ReportError(cx, "Invalid arguments to evalInFrame");
return false;
}
uint32_t upCount = JSVAL_TO_INT(argv[0]);
RootedString str(cx, JSVAL_TO_STRING(argv[1]));
bool saveCurrent = (argc >= 3 && JSVAL_IS_BOOLEAN(argv[2]))
? !!(JSVAL_TO_BOOLEAN(argv[2]))
: false;
/* This is a copy of CheckDebugMode. */
if (!JS_GetDebugMode(cx)) {
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
NULL, JSMSG_NEED_DEBUG_MODE);
return false;
}
/* Debug-mode currently disables Ion compilation. */
ScriptFrameIter fi(cx);
for (uint32_t i = 0; i < upCount; ++i, ++fi) {
ScriptFrameIter next(fi);
++next;
if (next.done())
break;
}
AutoSaveFrameChain sfc(cx);
mozilla::Maybe<AutoCompartment> ac;
if (saveCurrent) {
if (!sfc.save())
return false;
ac.construct(cx, GetDefaultGlobalForContext(cx));
}
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return false;
JSAbstractFramePtr frame(Jsvalify(fi.abstractFramePtr()));
RootedScript fpscript(cx, frame.script());
bool ok = !!frame.evaluateUCInStackFrame(cx, chars, length,
fpscript->filename(),
JS_PCToLineNumber(cx, fpscript,
fi.pc()),
MutableHandleValue::fromMarkedLocation(vp));
return ok;
}
static JSBool
ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp)
{
JS::Value v;
if (argc < 1 || !((v = JS_ARGV(cx, vp)[0]).isObject())) {
JS_ReportError(cx, "shapeOf: object expected");
return false;
}
JSObject *obj = &v.toObject();
JS_SET_RVAL(cx, vp, JS_NumberValue((double) ((uintptr_t)obj->lastProperty() >> 3)));
return true;
}
/*
* If referent has an own property named id, copy that property to obj[id].
* Since obj is native, this isn't totally transparent; properties of a
* non-native referent may be simplified to data properties.
*/
static JSBool
CopyProperty(JSContext *cx, HandleObject obj, HandleObject referent, HandleId id,
unsigned lookupFlags, MutableHandleObject objp)
{
RootedShape shape(cx);
AutoPropertyDescriptorRooter desc(cx);
unsigned propFlags = 0;
RootedObject obj2(cx);
objp.set(NULL);
if (referent->isNative()) {
if (!LookupPropertyWithFlags(cx, referent, id, lookupFlags, &obj2, &shape))
return false;
if (obj2 != referent)
return true;
if (shape->hasSlot()) {
desc.value = referent->nativeGetSlot(shape->slot());
} else {
desc.value.setUndefined();
}
desc.attrs = shape->attributes();
desc.getter = shape->getter();
if (!desc.getter && !(desc.attrs & JSPROP_GETTER))
desc.getter = JS_PropertyStub;
desc.setter = shape->setter();
if (!desc.setter && !(desc.attrs & JSPROP_SETTER))
desc.setter = JS_StrictPropertyStub;
desc.shortid = shape->shortid();
propFlags = shape->getFlags();
} else if (IsProxy(referent)) {
if (!Proxy::getOwnPropertyDescriptor(cx, referent, id, &desc, 0))
return false;
if (!desc.obj)
return true;
} else {
if (!JSObject::lookupGeneric(cx, referent, id, objp, &shape))
return false;
if (objp != referent)
return true;
RootedValue value(cx);
if (!JSObject::getGeneric(cx, referent, referent, id, &value) ||
!JSObject::getGenericAttributes(cx, referent, id, &desc.attrs))
{
return false;
}
desc.value = value;
desc.attrs &= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
desc.getter = JS_PropertyStub;
desc.setter = JS_StrictPropertyStub;
desc.shortid = 0;
}
RootedValue value(cx, desc.value);
objp.set(obj);
return DefineNativeProperty(cx, obj, id, value, desc.getter, desc.setter,
desc.attrs, propFlags, desc.shortid);
}
static JSBool
resolver_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
MutableHandleObject objp)
{
jsval v = JS_GetReservedSlot(obj, 0);
Rooted<JSObject*> vobj(cx, &v.toObject());
return CopyProperty(cx, obj, vobj, id, flags, objp);
}
static JSBool
resolver_enumerate(JSContext *cx, HandleObject obj)
{
jsval v = JS_GetReservedSlot(obj, 0);
RootedObject referent(cx, JSVAL_TO_OBJECT(v));
AutoIdArray ida(cx, JS_Enumerate(cx, referent));
bool ok = !!ida;
RootedObject ignore(cx);
for (size_t i = 0; ok && i < ida.length(); i++) {
Rooted<jsid> id(cx, ida[i]);
ok = CopyProperty(cx, obj, referent, id, 0, &ignore);
}
return ok;
}
static JSClass resolver_class = {
"resolver",
JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1),
JS_PropertyStub, JS_DeletePropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
resolver_enumerate, (JSResolveOp)resolver_resolve,
JS_ConvertStub
};
static JSBool
Resolver(JSContext *cx, unsigned argc, jsval *vp)
{
RootedObject referent(cx, NULL);
RootedObject proto(cx, NULL);
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "o/o", &referent, &proto))
return false;
RootedObject parent(cx, JS_GetParent(referent));
JSObject *result = (argc > 1
? JS_NewObjectWithGivenProto
: JS_NewObject)(cx, &resolver_class, proto, parent);
if (!result)
return false;
JS_SetReservedSlot(result, 0, OBJECT_TO_JSVAL(referent));
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result));
return true;
}
#ifdef JS_THREADSAFE
/*
* 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 JSBo