gecko/js/src/shell/js.cpp
2011-03-29 09:05:25 -07:00

5935 lines
166 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#define __STDC_LIMIT_MACROS
/*
* JS shell.
*/
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <locale.h>
#include "jstypes.h"
#include "jsstdint.h"
#include "jsarena.h"
#include "jsutil.h"
#include "jsprf.h"
#include "jswrapper.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jsbuiltins.h"
#include "jscntxt.h"
#include "jsdate.h"
#include "jsdbgapi.h"
#include "jsemit.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsiter.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsparse.h"
#include "jsreflect.h"
#include "jsscope.h"
#include "jsscript.h"
#include "jstypedarray.h"
#include "jsxml.h"
#include "jsperf.h"
#include "prmjtime.h"
#ifdef JSDEBUGGER
#include "jsdebug.h"
#ifdef JSDEBUGGER_JAVA_UI
#include "jsdjava.h"
#endif /* JSDEBUGGER_JAVA_UI */
#ifdef JSDEBUGGER_C_UI
#include "jsdb.h"
#endif /* JSDEBUGGER_C_UI */
#endif /* JSDEBUGGER */
#include "jsworkers.h"
#include "jsinterpinlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.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 "jswin.h"
#endif
using namespace js;
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;
/* Assume that we can not use more than 5e5 bytes of C stack by default. */
#if (defined(DEBUG) && defined(__SUNPRO_CC)) || defined(JS_CPU_SPARC)
/* Sun compiler uses larger stack space for js_Interpret() with debug
Use a bigger gMaxStackSize to make "make check" happy. */
#define DEFAULT_MAX_STACK_SIZE 5000000
#else
#define DEFAULT_MAX_STACK_SIZE 500000
#endif
size_t gMaxStackSize = DEFAULT_MAX_STACK_SIZE;
#ifdef JS_THREADSAFE
static PRUintn gStackBaseThreadIndex;
#else
static jsuword gStackBase;
#endif
static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
/*
* Limit the timeout to 30 minutes to prevent an overflow on platfoms
* that represent the time internally in microseconds using 32-bit int.
*/
static jsdouble MAX_TIMEOUT_INTERVAL = 1800.0;
static jsdouble gTimeoutInterval = -1.0;
static volatile bool gCanceled = false;
static bool enableTraceJit = false;
static bool enableMethodJit = false;
static bool enableProfiling = false;
static bool printTiming = false;
static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t);
static bool
InitWatchdog(JSRuntime *rt);
static void
KillWatchdog();
static bool
ScheduleWatchdog(JSRuntime *rt, jsdouble 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 PRIntervalTime gWatchdogTimeout = 0;
static PRCondVar *gSleepWakeup = NULL;
#else
static JSRuntime *gRuntime = NULL;
#endif
int gExitCode = 0;
JSBool gQuitting = JS_FALSE;
FILE *gErrFile = NULL;
FILE *gOutFile = NULL;
#ifdef JS_THREADSAFE
JSObject *gWorkers = NULL;
js::workers::ThreadPool *gWorkerThreadPool = NULL;
#endif
static JSBool reportWarnings = JS_TRUE;
static JSBool compileOnly = JS_FALSE;
#ifdef DEBUG
static JSBool OOM_printAllocationCount = JS_FALSE;
#endif
typedef enum JSShellErrNum {
#define MSG_DEF(name, number, count, exception, format) \
name = number,
#include "jsshell.msg"
#undef MSG_DEF
JSShellErr_Limit
#undef MSGDEF
} JSShellErrNum;
static JSContext *
NewContext(JSRuntime *rt);
static void
DestroyContext(JSContext *cx, bool withGC);
static const JSErrorFormatString *
my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber);
static JSObject *
split_setup(JSContext *cx, JSBool evalcx);
#ifdef EDITLINE
JS_BEGIN_EXTERN_C
JS_EXTERN_API(char) *readline(const char *prompt);
JS_EXTERN_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 ToString {
public:
ToString(JSContext *aCx, jsval v, JSBool aThrow = JS_FALSE)
: cx(aCx), mThrow(aThrow)
{
mStr = JS_ValueToString(cx, v);
if (!aThrow && !mStr)
ReportException(cx);
JS_AddNamedStringRoot(cx, &mStr, "Value ToString helper");
}
~ToString() {
JS_RemoveStringRoot(cx, &mStr);
}
JSBool threw() { return !mStr; }
jsval getJSVal() { return STRING_TO_JSVAL(mStr); }
const char *getBytes() {
if (mStr && (mBytes.ptr() || mBytes.encode(cx, mStr)))
return mBytes.ptr();
return "(error converting value)";
}
private:
JSContext *cx;
JSString *mStr;
JSBool mThrow;
JSAutoByteString mBytes;
};
class IdToString : public ToString {
public:
IdToString(JSContext *cx, jsid id, JSBool aThrow = JS_FALSE)
: ToString(cx, IdToJsval(id), aThrow)
{ }
};
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 *) 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 *) realloc(buffer, size);
if (!tmp) {
free(buffer);
return NULL;
}
buffer = tmp;
}
current = buffer + len;
}
if (len && !ferror(file))
return buffer;
free(buffer);
return NULL;
}
/*
* 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 JSIntervalTime startTime;
};
static JSShellContextData *
NewContextData()
{
/* Prevent creation of new contexts after we have been canceled. */
if (gCanceled)
return NULL;
JSShellContextData *data = (JSShellContextData *)
calloc(sizeof(JSShellContextData), 1);
if (!data)
return NULL;
data->startTime = js_IntervalNow();
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 (!gCanceled)
return JS_TRUE;
JS_ClearPendingException(cx);
return JS_FALSE;
}
static void
SetContextOptions(JSContext *cx)
{
JS_SetNativeStackQuota(cx, gMaxStackSize);
JS_SetScriptStackQuota(cx, gScriptStackQuota);
JS_SetOperationCallback(cx, ShellOperationCallback);
}
#ifdef WINCE
int errno;
#endif
static void
Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY)
{
JSBool ok, hitEOF;
JSObject *scriptObj;
jsval result;
JSString *str;
char *buffer;
size_t size;
int lineno;
int startline;
FILE *file;
uint32 oldopts;
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;
}
}
SetContextOptions(cx);
#ifndef WINCE
/* windows mobile (and possibly other os's) does not have a TTY */
if (!forceTTY && !isatty(fileno(file)))
#endif
{
/*
* It's not interactive - just execute it.
*
* Support the UNIX #! shell hack; gobble the first line if it starts
* with '#'. TODO - this isn't quite compatible with sharp variables,
* as a legal js program (using sharp variables) might start with '#'.
* But that would require multi-character lookahead.
*/
int ch = fgetc(file);
if (ch == '#') {
while((ch = fgetc(file)) != EOF) {
if (ch == '\n' || ch == '\r')
break;
}
}
ungetc(ch, file);
int64 t1 = PRMJ_Now();
oldopts = JS_GetOptions(cx);
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
scriptObj = JS_CompileFileHandle(cx, obj, filename, file);
JS_SetOptions(cx, oldopts);
if (scriptObj && !compileOnly) {
(void) JS_ExecuteScript(cx, obj, scriptObj, NULL);
int64 t2 = PRMJ_Now() - t1;
if (printTiming)
printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
}
goto cleanup;
}
/* It's an interactive filehandle; drop into read-eval-print loop. */
lineno = 1;
hitEOF = JS_FALSE;
buffer = NULL;
size = 0; /* assign here to avoid warnings */
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.
*/
startline = lineno;
size_t len = 0; /* initialize to avoid warnings */
do {
ScheduleWatchdog(cx->runtime, -1);
gCanceled = false;
errno = 0;
char *line;
{
JSAutoSuspendRequest suspended(cx);
line = GetLine(file, startline == lineno ? "js> " : "");
}
if (!line) {
if (errno) {
JS_ReportError(cx, strerror(errno));
free(buffer);
goto cleanup;
}
hitEOF = JS_TRUE;
break;
}
if (!buffer) {
buffer = line;
len = strlen(buffer);
size = len + 1;
} else {
/*
* len + 1 is required to store '\n' in the end of line.
*/
size_t newlen = strlen(line) + (len ? len + 1 : 0);
if (newlen + 1 > size) {
size = newlen + 1 > size * 2 ? newlen + 1 : size * 2;
char *newBuf = (char *) realloc(buffer, size);
if (!newBuf) {
free(buffer);
free(line);
JS_ReportOutOfMemory(cx);
goto cleanup;
}
buffer = newBuf;
}
char *current = buffer + len;
if (startline != lineno)
*current++ = '\n';
strcpy(current, line);
len = newlen;
free(line);
}
lineno++;
if (!ScheduleWatchdog(cx->runtime, gTimeoutInterval)) {
hitEOF = JS_TRUE;
break;
}
} while (!JS_BufferIsCompilableUnit(cx, obj, buffer, len));
if (hitEOF && !buffer)
break;
/* Clear any pending exception from previous failed compiles. */
JS_ClearPendingException(cx);
/* Even though we're interactive, we have a compile-n-go opportunity. */
oldopts = JS_GetOptions(cx);
if (!compileOnly)
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO);
scriptObj = JS_CompileScript(cx, obj, buffer, len, "typein",
startline);
if (!compileOnly)
JS_SetOptions(cx, oldopts);
if (scriptObj && !compileOnly) {
ok = JS_ExecuteScript(cx, obj, scriptObj, &result);
if (ok && !JSVAL_IS_VOID(result)) {
str = JS_ValueToSource(cx, result);
ok = !!str;
if (ok) {
JSAutoByteString bytes(cx, str);
ok = !!bytes;
if (ok)
fprintf(gOutFile, "%s\n", bytes.ptr());
}
}
}
*buffer = '\0';
} while (!hitEOF && !gQuitting);
free(buffer);
fprintf(gOutFile, "\n");
cleanup:
if (file != stdin)
fclose(file);
return;
}
static int
usage(void)
{
fprintf(gErrFile, "%s\n", JS_GetImplementationVersion());
fprintf(gErrFile, "usage: js [options] [scriptfile] [scriptarg...]\n"
"Options:\n"
" -h Display this information\n"
" -z Create a split global object\n"
" Warning: this option is probably not useful\n"
" -P Deeply freeze the global object prototype\n"
" -s Toggle JSOPTION_STRICT flag\n"
" -w Report strict warnings\n"
" -W Do not report strict warnings\n"
" -x Toggle JSOPTION_XML flag\n"
" -C Compile-only; do not execute\n"
" -i Enable interactive read-eval-print loop\n"
" -j Enable the TraceMonkey tracing JIT\n"
" -m Enable the JaegerMonkey method JIT\n"
" -a Always method JIT, ignore internal tuning\n"
" This only has effect with -m\n"
" -p Enable loop profiling for TraceMonkey\n"
" -d Enable debug mode\n"
" -b Print timing statistics\n"
" -t <timeout> Interrupt long-running execution after <timeout> seconds, where\n"
" <timeout> <= 1800.0. Negative values indicate no timeout (default).\n"
" -c <size> Suggest stack chunk size of <size> bytes. Default is 8192.\n"
" Warning: this option is currently ignored.\n"
" -o <option> Enable a context option flag by name\n"
" Possible values:\n"
" anonfunfix: JSOPTION_ANONFUNFIX\n"
" atline: JSOPTION_ATLINE\n"
" tracejit: JSOPTION_JIT\n"
" methodjit: JSOPTION_METHODJIT\n"
" relimit: JSOPTION_RELIMIT\n"
" strict: JSOPTION_STRICT\n"
" werror: JSOPTION_WERROR\n"
" xml: JSOPTION_XML\n"
" -v <version> Set the JavaScript language version\n"
" Possible values:\n"
" 150: JavaScript 1.5\n"
" 160: JavaScript 1.6\n"
" 170: JavaScript 1.7\n"
" 180: JavaScript 1.8\n"
" 185: JavaScript 1.8.5 (default)\n"
" -f <file> Load and execute JavaScript source <file>\n"
" Note: this option switches to non-interactive mode.\n"
" -e <source> Execute JavaScript <source>\n"
" Note: this option switches to non-interactive mode.\n"
" -S <size> Set the maximum size of the stack to <size> bytes\n"
" Default is %u.\n", DEFAULT_MAX_STACK_SIZE);
#ifdef DEBUG
fprintf(gErrFile, " -A <max> After <max> memory allocations, act like we're OOM.\n");
fprintf(gErrFile, " -O <max> At exit, print the number of memory allocations in \n"
" the program.\n");
#endif
#ifdef JS_THREADSAFE
fprintf(gErrFile, " -g <n> Sleep for <n> seconds before starting (default: 0)\n");
#endif
#ifdef JS_GC_ZEAL
fprintf(gErrFile, " -Z <n> Toggle GC zeal: low if <n> is 0 (default), high if non-zero\n");
#endif
#ifdef MOZ_TRACEVIS
fprintf(gErrFile, " -T Start TraceVis\n");
#endif
return 2;
}
/*
* JSContext option name to flag map. The option names are in alphabetical
* order for better reporting.
*/
static const struct {
const char *name;
uint32 flag;
} js_options[] = {
{"anonfunfix", JSOPTION_ANONFUNFIX},
{"atline", JSOPTION_ATLINE},
{"jitprofiling", JSOPTION_PROFILING},
{"tracejit", JSOPTION_JIT},
{"methodjit", JSOPTION_METHODJIT},
{"methodjit_always",JSOPTION_METHODJIT_ALWAYS},
{"relimit", JSOPTION_RELIMIT},
{"strict", JSOPTION_STRICT},
{"werror", JSOPTION_WERROR},
{"xml", JSOPTION_XML},
};
static uint32
MapContextOptionNameToFlag(JSContext* cx, const char* name)
{
for (size_t i = 0; i != JS_ARRAY_LENGTH(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 != JS_ARRAY_LENGTH(js_options); ++i) {
if (!msg)
break;
msg = JS_sprintf_append(msg, "%s%s", js_options[i].name,
(i + 2 < JS_ARRAY_LENGTH(js_options)
? ", "
: i + 2 == JS_ARRAY_LENGTH(js_options)
? " and "
: "."));
}
if (!msg) {
JS_ReportOutOfMemory(cx);
} else {
JS_ReportError(cx, msg);
free(msg);
}
return 0;
}
extern JSClass global_class;
#if defined(JS_TRACER) && defined(DEBUG)
namespace js {
extern struct JSClass jitstats_class;
void InitJITStatsClass(JSContext *cx, JSObject *glob);
}
#endif
static int
ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc)
{
int i, j, length;
JSObject *argsObj;
char *filename = NULL;
JSBool isInteractive = JS_TRUE;
JSBool forceTTY = JS_FALSE;
/*
* Scan past all optional arguments so we can create the arguments object
* before processing any -f options, which must interleave properly with
* -v and -w options. This requires two passes, and without getopt, we'll
* have to keep the option logic here and in the second for loop in sync.
*/
for (i = 0; i < argc; i++) {
if (argv[i][0] != '-' || argv[i][1] == '\0') {
++i;
break;
}
switch (argv[i][1]) {
case 'c':
case 'f':
case 'e':
case 'v':
case 'S':
case 't':
#ifdef JS_GC_ZEAL
case 'Z':
#endif
#ifdef MOZ_TRACEVIS
case 'T':
#endif
case 'g':
case 'A':
++i;
break;
default:;
}
}
/*
* Create arguments early and define it to root it, so it's safe from any
* GC calls nested below, and so it is available to -f <file> arguments.
*/
argsObj = JS_NewArrayObject(cx, 0, NULL);
if (!argsObj)
return 1;
if (!JS_DefineProperty(cx, obj, "arguments", OBJECT_TO_JSVAL(argsObj),
NULL, NULL, 0)) {
return 1;
}
length = argc - i;
for (j = 0; j < length; j++) {
JSString *str = JS_NewStringCopyZ(cx, argv[i++]);
if (!str)
return 1;
if (!JS_DefineElement(cx, argsObj, j, STRING_TO_JSVAL(str),
NULL, NULL, JSPROP_ENUMERATE)) {
return 1;
}
}
for (i = 0; i < argc; i++) {
if (argv[i][0] != '-' || argv[i][1] == '\0') {
filename = argv[i++];
isInteractive = JS_FALSE;
break;
}
switch (argv[i][1]) {
case 'v':
if (++i == argc)
return usage();
JS_SetVersion(cx, (JSVersion) atoi(argv[i]));
break;
#ifdef JS_GC_ZEAL
case 'Z':
if (++i == argc)
return usage();
JS_SetGCZeal(cx, !!(atoi(argv[i])));
break;
#endif
#ifdef DEBUG
case 'A':
/* Handled at the very start of main(). */
++i; /* skip the argument */
break;
case 'O':
OOM_printAllocationCount = JS_TRUE;
break;
#endif
case 'w':
reportWarnings = JS_TRUE;
break;
case 'W':
reportWarnings = JS_FALSE;
break;
case 's':
JS_ToggleOptions(cx, JSOPTION_STRICT);
break;
case 'E':
JS_ToggleOptions(cx, JSOPTION_RELIMIT);
break;
case 'x':
JS_ToggleOptions(cx, JSOPTION_XML);
break;
case 'b':
printTiming = true;
break;
case 'j':
enableTraceJit = !enableTraceJit;
JS_ToggleOptions(cx, JSOPTION_JIT);
#if defined(JS_TRACER) && defined(DEBUG)
js::InitJITStatsClass(cx, JS_GetGlobalObject(cx));
JS_DefineObject(cx, JS_GetGlobalObject(cx), "tracemonkey",
&js::jitstats_class, NULL, 0);
#endif
break;
case 'm':
enableMethodJit = !enableMethodJit;
JS_ToggleOptions(cx, JSOPTION_METHODJIT);
break;
case 'a':
JS_ToggleOptions(cx, JSOPTION_METHODJIT_ALWAYS);
break;
case 'p':
enableProfiling = !enableProfiling;
JS_ToggleOptions(cx, JSOPTION_PROFILING);
break;
case 'o':
{
if (++i == argc)
return usage();
uint32 flag = MapContextOptionNameToFlag(cx, argv[i]);
if (flag == 0)
return gExitCode;
JS_ToggleOptions(cx, flag);
break;
}
case 'P':
if (JS_GET_CLASS(cx, JS_GetPrototype(cx, obj)) != &global_class) {
JSObject *gobj;
if (!JS_DeepFreezeObject(cx, obj))
return JS_FALSE;
gobj = JS_NewGlobalObject(cx, &global_class);
if (!gobj)
return JS_FALSE;
if (!JS_SetPrototype(cx, gobj, obj))
return JS_FALSE;
JS_SetParent(cx, gobj, NULL);
JS_SetGlobalObject(cx, gobj);
obj = gobj;
}
break;
case 't':
if (++i == argc)
return usage();
if (!SetTimeoutValue(cx, atof(argv[i])))
return JS_FALSE;
break;
case 'c':
/* set stack chunk size */
gStackChunkSize = atoi(argv[++i]);
break;
case 'f':
if (++i == argc)
return usage();
Process(cx, obj, argv[i], JS_FALSE);
if (gExitCode != 0)
return gExitCode;
/*
* XXX: js -f foo.js should interpret foo.js and then
* drop into interactive mode, but that breaks the test
* harness. Just execute foo.js for now.
*/
isInteractive = JS_FALSE;
break;
case 'e':
{
jsval rval;
if (++i == argc)
return usage();
/* Pass a filename of -e to imitate PERL */
JS_EvaluateScript(cx, obj, argv[i], strlen(argv[i]),
"-e", 1, &rval);
isInteractive = JS_FALSE;
break;
}
case 'C':
compileOnly = JS_TRUE;
isInteractive = JS_FALSE;
break;
case 'i':
isInteractive = forceTTY = JS_TRUE;
break;
case 'S':
if (++i == argc)
return usage();
/* Set maximum stack size. */
gMaxStackSize = atoi(argv[i]);
break;
case 'd':
JS_SetRuntimeDebugMode(JS_GetRuntime(cx), JS_TRUE);
JS_SetDebugMode(cx, JS_TRUE);
break;
case 'z':
obj = split_setup(cx, JS_FALSE);
if (!obj)
return gExitCode;
break;
#ifdef MOZ_TRACEVIS
case 'T':
if (++i == argc)
return usage();
StartTraceVis(argv[i]);
break;
#endif
#ifdef JS_THREADSAFE
case 'g':
if (++i == argc)
return usage();
PR_Sleep(PR_SecondsToInterval(atoi(argv[i])));
break;
#endif
default:
return usage();
}
}
if (filename || isInteractive)
Process(cx, obj, filename, forceTTY);
return gExitCode;
}
static JSBool
Version(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv = JS_ARGV(cx, vp);
if (argc > 0 && JSVAL_IS_INT(argv[0]))
*vp = INT_TO_JSVAL(JS_SetVersion(cx, (JSVersion) JSVAL_TO_INT(argv[0])));
else
*vp = INT_TO_JSVAL(JS_GetVersion(cx));
return JS_TRUE;
}
static JSBool
RevertVersion(JSContext *cx, uintN argc, jsval *vp)
{
js_RevertVersion(cx);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
Options(JSContext *cx, uintN argc, jsval *vp)
{
uint32 optset, flag;
JSString *str;
char *names;
JSBool found;
optset = 0;
jsval *argv = JS_ARGV(cx, vp);
for (uintN i = 0; i < argc; i++) {
str = JS_ValueToString(cx, argv[i]);
if (!str)
return JS_FALSE;
argv[i] = STRING_TO_JSVAL(str);
JSAutoByteString opt(cx, str);
if (!opt)
return JS_FALSE;
flag = MapContextOptionNameToFlag(cx, opt.ptr());
if (!flag)
return JS_FALSE;
optset |= flag;
}
optset = JS_ToggleOptions(cx, optset);
names = NULL;
found = JS_FALSE;
for (size_t i = 0; i != JS_ARRAY_LENGTH(js_options); i++) {
if (js_options[i].flag & optset) {
found = JS_TRUE;
names = JS_sprintf_append(names, "%s%s",
names ? "," : "", js_options[i].name);
if (!names)
break;
}
}
if (!found)
names = strdup("");
if (!names) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
str = JS_NewStringCopyZ(cx, names);
free(names);
if (!str)
return JS_FALSE;
*vp = STRING_TO_JSVAL(str);
return JS_TRUE;
}
static JSBool
Load(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *thisobj = JS_THIS_OBJECT(cx, vp);
if (!thisobj)
return JS_FALSE;
jsval *argv = JS_ARGV(cx, vp);
for (uintN i = 0; i < argc; i++) {
JSString *str = JS_ValueToString(cx, argv[i]);
if (!str)
return false;
argv[i] = STRING_TO_JSVAL(str);
JSAutoByteString filename(cx, str);
if (!filename)
return JS_FALSE;
errno = 0;
uint32 oldopts = JS_GetOptions(cx);
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
JSObject *scriptObj = JS_CompileFile(cx, thisobj, filename.ptr());
JS_SetOptions(cx, oldopts);
if (!scriptObj)
return false;
if (!compileOnly && !JS_ExecuteScript(cx, thisobj, scriptObj, NULL))
return false;
}
return true;
}
static JSBool
Evaluate(JSContext *cx, uintN argc, jsval *vp)
{
if (argc != 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
(argc != 1) ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_INVALID_ARGS,
"evaluate");
return false;
}
JSString *code = JSVAL_TO_STRING(JS_ARGV(cx, vp)[0]);
size_t codeLength;
const jschar *codeChars = JS_GetStringCharsAndLength(cx, code, &codeLength);
if (!codeChars)
return false;
JSObject *thisobj = JS_THIS_OBJECT(cx, vp);
if (!thisobj)
return false;
if ((JS_GET_CLASS(cx, thisobj)->flags & JSCLASS_IS_GLOBAL) != JSCLASS_IS_GLOBAL) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE,
"this-value passed to evaluate()", "not a global object");
return false;
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_EvaluateUCScript(cx, thisobj, codeChars, codeLength, "@evaluate", 0, NULL);
}
static JSString *
FileAsString(JSContext *cx, const char *pathname)
{
FILE *file;
JSString *str = NULL;
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) == EOF) {
JS_ReportError(cx, "can't seek end of %s", pathname);
} else {
len = ftell(file);
if (fseek(file, 0, SEEK_SET) == EOF) {
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 {
len = (size_t)cc;
str = JS_NewStringCopyN(cx, buf, len);
}
JS_free(cx, buf);
}
}
}
fclose(file);
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 JSBool
Run(JSContext *cx, uintN argc, jsval *vp)
{
if (argc != 1) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "run");
return false;
}
JSObject *thisobj = JS_THIS_OBJECT(cx, vp);
if (!thisobj)
return false;
jsval *argv = JS_ARGV(cx, vp);
JSString *str = JS_ValueToString(cx, argv[0]);
if (!str)
return false;
argv[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 oldopts = JS_GetOptions(cx);
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
int64 startClock = PRMJ_Now();
JSObject *scriptObj = JS_CompileUCScript(cx, thisobj, ucbuf, buflen, filename.ptr(), 1);
JS_SetOptions(cx, oldopts);
if (!scriptObj || !JS_ExecuteScript(cx, thisobj, scriptObj, NULL))
return false;
int64 endClock = PRMJ_Now();
JS_SET_RVAL(cx, vp, DOUBLE_TO_JSVAL((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, uintN argc, jsval *vp)
{
#define BUFSIZE 256
FILE *from;
char *buf, *tmp;
size_t bufsize, buflength, gotlength;
JSBool sawNewline;
JSString *str;
from = stdin;
buflength = 0;
bufsize = BUFSIZE;
buf = (char *) JS_malloc(cx, bufsize);
if (!buf)
return JS_FALSE;
sawNewline = JS_FALSE;
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 = JS_TRUE;
break;
} else if (buflength < bufsize - 1) {
break;
}
/* Else, grow our buffer for another pass. */
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 JS_FALSE;
}
buf = tmp;
}
/* Treat the empty string specially. */
if (buflength == 0) {
*vp = feof(from) ? JSVAL_NULL : JS_GetEmptyStringValue(cx);
JS_free(cx, buf);
return JS_TRUE;
}
/* Shrink the buffer to the real size. */
tmp = (char *) JS_realloc(cx, buf, buflength);
if (!tmp) {
JS_free(cx, buf);
return JS_FALSE;
}
buf = tmp;
/*
* Turn buf into a JSString. Note that buflength includes the trailing null
* character.
*/
str = JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength);
JS_free(cx, buf);
if (!str)
return JS_FALSE;
*vp = STRING_TO_JSVAL(str);
return JS_TRUE;
}
static JSBool
PutStr(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv;
JSString *str;
char *bytes;
if (argc != 0) {
argv = JS_ARGV(cx, vp);
str = JS_ValueToString(cx, argv[0]);
if (!str)
return JS_FALSE;
bytes = JS_EncodeString(cx, str);
if (!bytes)
return JS_FALSE;
fputs(bytes, gOutFile);
JS_free(cx, bytes);
fflush(gOutFile);
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
Now(JSContext *cx, uintN argc, jsval *vp)
{
jsdouble now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC);
JS_SET_RVAL(cx, vp, DOUBLE_TO_JSVAL(now));
return true;
}
static JSBool
Print(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv;
uintN i;
JSString *str;
char *bytes;
argv = JS_ARGV(cx, vp);
for (i = 0; i < argc; i++) {
str = JS_ValueToString(cx, argv[i]);
if (!str)
return JS_FALSE;
bytes = JS_EncodeString(cx, str);
if (!bytes)
return JS_FALSE;
fprintf(gOutFile, "%s%s", i ? " " : "", bytes);
JS_free(cx, bytes);
}
fputc('\n', gOutFile);
fflush(gOutFile);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
Help(JSContext *cx, uintN argc, jsval *vp);
static JSBool
Quit(JSContext *cx, uintN argc, jsval *vp)
{
JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "/ i", &gExitCode);
gQuitting = JS_TRUE;
#ifdef JS_THREADSAFE
if (gWorkerThreadPool)
js::workers::terminateAll(JS_GetRuntime(cx), gWorkerThreadPool);
#endif
return JS_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->encode(cx, str))
return bytes->ptr();
}
JS_ClearPendingException(cx);
return "<<error converting value to string>>";
}
static JSBool
AssertEq(JSContext *cx, uintN argc, jsval *vp)
{
if (!(argc == 2 || (argc == 3 && JSVAL_IS_STRING(JS_ARGV(cx, vp)[2])))) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
(argc < 2)
? JSSMSG_NOT_ENOUGH_ARGS
: (argc == 3)
? JSSMSG_INVALID_ARGS
: JSSMSG_TOO_MANY_ARGS,
"assertEq");
return JS_FALSE;
}
jsval *argv = JS_ARGV(cx, vp);
JSBool same;
if (!JS_SameValue(cx, argv[0], argv[1], &same))
return JS_FALSE;
if (!same) {
JSAutoByteString bytes0, bytes1;
const char *actual = ToSource(cx, &argv[0], &bytes0);
const char *expected = ToSource(cx, &argv[1], &bytes1);
if (argc == 2) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_ASSERT_EQ_FAILED,
actual, expected);
} else {
JSAutoByteString bytes2(cx, JSVAL_TO_STRING(argv[2]));
if (!bytes2)
return JS_FALSE;
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_ASSERT_EQ_FAILED_MSG,
actual, expected, bytes2.ptr());
}
return JS_FALSE;
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
AssertJit(JSContext *cx, uintN argc, jsval *vp)
{
#ifdef JS_METHODJIT
if (JS_GetOptions(cx) & JSOPTION_METHODJIT) {
if (!cx->fp()->script()->getJIT(cx->fp()->isConstructing())) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_ASSERT_JIT_FAILED);
return JS_FALSE;
}
}
#endif
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
GC(JSContext *cx, uintN argc, jsval *vp)
{
size_t preBytes = cx->runtime->gcBytes;
JS_GC(cx);
char buf[256];
JS_snprintf(buf, sizeof(buf), "before %lu, after %lu, break %08lx\n",
(unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes,
#ifdef HAVE_SBRK
(unsigned long)sbrk(0)
#else
0
#endif
);
*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buf));
return true;
}
#ifdef JS_GCMETER
static JSBool
GCStats(JSContext *cx, uintN argc, jsval *vp)
{
js_DumpGCStats(cx->runtime, stdout);
*vp = JSVAL_VOID;
return true;
}
#endif
static JSBool
GCParameter(JSContext *cx, uintN argc, jsval *vp)
{
static const struct {
const char *name;
JSGCParamKey param;
} paramMap[] = {
{"maxBytes", JSGC_MAX_BYTES },
{"maxMallocBytes", JSGC_MAX_MALLOC_BYTES},
{"gcStackpoolLifespan", JSGC_STACKPOOL_LIFESPAN},
{"gcBytes", JSGC_BYTES},
{"gcNumber", JSGC_NUMBER},
{"gcTriggerFactor", JSGC_TRIGGER_FACTOR},
};
JSString *str;
if (argc == 0) {
str = JS_ValueToString(cx, JSVAL_VOID);
JS_ASSERT(str);
} else {
str = JS_ValueToString(cx, vp[2]);
if (!str)
return JS_FALSE;
vp[2] = STRING_TO_JSVAL(str);
}
JSFlatString *flatStr = JS_FlattenString(cx, str);
if (!flatStr)
return JS_FALSE;
size_t paramIndex = 0;
for (;; paramIndex++) {
if (paramIndex == JS_ARRAY_LENGTH(paramMap)) {
JS_ReportError(cx,
"the first argument argument must be maxBytes, "
"maxMallocBytes, gcStackpoolLifespan, gcBytes, "
"gcNumber or gcTriggerFactor");
return JS_FALSE;
}
if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name))
break;
}
JSGCParamKey param = paramMap[paramIndex].param;
if (argc == 1) {
uint32 value = JS_GetGCParameter(cx->runtime, param);
return JS_NewNumberValue(cx, value, &vp[0]);
}
if (param == JSGC_NUMBER ||
param == JSGC_BYTES) {
JS_ReportError(cx, "Attempt to change read-only parameter %s",
paramMap[paramIndex].name);
return JS_FALSE;
}
uint32 value;
if (!JS_ValueToECMAUint32(cx, vp[3], &value)) {
JS_ReportError(cx,
"the second argument must be convertable to uint32 "
"with non-zero value");
return JS_FALSE;
}
if (param == JSGC_TRIGGER_FACTOR && value < 100) {
JS_ReportError(cx,
"the gcTriggerFactor value must be >= 100");
return JS_FALSE;
}
JS_SetGCParameter(cx->runtime, param, value);
*vp = JSVAL_VOID;
return JS_TRUE;
}
#ifdef JS_GC_ZEAL
static JSBool
GCZeal(JSContext *cx, uintN argc, jsval *vp)
{
uint32 zeal;
if (!JS_ValueToECMAUint32(cx, argc == 0 ? JSVAL_VOID : vp[2], &zeal))
return JS_FALSE;
JS_SetGCZeal(cx, (uint8)zeal);
*vp = JSVAL_VOID;
return JS_TRUE;
}
#endif /* JS_GC_ZEAL */
typedef struct JSCountHeapNode JSCountHeapNode;
struct JSCountHeapNode {
void *thing;
int32 kind;
JSCountHeapNode *next;
};
typedef struct JSCountHeapTracer {
JSTracer base;
JSDHashTable visited;
JSBool ok;
JSCountHeapNode *traceList;
JSCountHeapNode *recycleList;
} JSCountHeapTracer;
static void
CountHeapNotify(JSTracer *trc, void *thing, uint32 kind)
{
JSCountHeapTracer *countTracer;
JSDHashEntryStub *entry;
JSCountHeapNode *node;
JS_ASSERT(trc->callback == CountHeapNotify);
countTracer = (JSCountHeapTracer *)trc;
if (!countTracer->ok)
return;
entry = (JSDHashEntryStub *)
JS_DHashTableOperate(&countTracer->visited, thing, JS_DHASH_ADD);
if (!entry) {
JS_ReportOutOfMemory(trc->context);
countTracer->ok = JS_FALSE;
return;
}
if (entry->key)
return;
entry->key = thing;
node = countTracer->recycleList;
if (node) {
countTracer->recycleList = node->next;
} else {
node = (JSCountHeapNode *) JS_malloc(trc->context, sizeof *node);
if (!node) {
countTracer->ok = JS_FALSE;
return;
}
}
node->thing = thing;
node->kind = kind;
node->next = countTracer->traceList;
countTracer->traceList = node;
}
static JSBool
CountHeap(JSContext *cx, uintN argc, jsval *vp)
{
void* startThing;
int32 startTraceKind;
jsval v;
int32 traceKind, i;
JSString *str;
JSCountHeapTracer countTracer;
JSCountHeapNode *node;
size_t counter;
static const struct {
const char *name;
int32 kind;
} traceKindNames[] = {
{ "all", -1 },
{ "object", JSTRACE_OBJECT },
{ "string", JSTRACE_STRING },
#if JS_HAS_XML_SUPPORT
{ "xml", JSTRACE_XML },
#endif
};
startThing = NULL;
startTraceKind = 0;
if (argc > 0) {
v = JS_ARGV(cx, vp)[0];
if (JSVAL_IS_TRACEABLE(v)) {
startThing = JSVAL_TO_TRACEABLE(v);
startTraceKind = JSVAL_TRACE_KIND(v);
} else if (!JSVAL_IS_NULL(v)) {
JS_ReportError(cx,
"the first argument is not null or a heap-allocated "
"thing");
return JS_FALSE;
}
}
traceKind = -1;
if (argc > 1) {
str = JS_ValueToString(cx, JS_ARGV(cx, vp)[1]);
if (!str)
return JS_FALSE;
JSFlatString *flatStr = JS_FlattenString(cx, str);
if (!flatStr)
return JS_FALSE;
for (i = 0; ;) {
if (JS_FlatStringEqualsAscii(flatStr, traceKindNames[i].name)) {
traceKind = traceKindNames[i].kind;
break;
}
if (++i == JS_ARRAY_LENGTH(traceKindNames)) {
JSAutoByteString bytes(cx, str);
if (!!bytes)
JS_ReportError(cx, "trace kind name '%s' is unknown", bytes.ptr());
return JS_FALSE;
}
}
}
JS_TRACER_INIT(&countTracer.base, cx, CountHeapNotify);
if (!JS_DHashTableInit(&countTracer.visited, JS_DHashGetStubOps(),
NULL, sizeof(JSDHashEntryStub),
JS_DHASH_DEFAULT_CAPACITY(100))) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
countTracer.ok = JS_TRUE;
countTracer.traceList = NULL;
countTracer.recycleList = NULL;
if (!startThing) {
JS_TraceRuntime(&countTracer.base);
} else {
JS_SET_TRACING_NAME(&countTracer.base, "root");
JS_CallTracer(&countTracer.base, startThing, startTraceKind);
}
counter = 0;
while ((node = countTracer.traceList) != NULL) {
if (traceKind == -1 || node->kind == traceKind)
counter++;
countTracer.traceList = node->next;
node->next = countTracer.recycleList;
countTracer.recycleList = node;
JS_TraceChildren(&countTracer.base, node->thing, node->kind);
}
while ((node = countTracer.recycleList) != NULL) {
countTracer.recycleList = node->next;
JS_free(cx, node);
}
JS_DHashTableFinish(&countTracer.visited);
return countTracer.ok && JS_NewNumberValue(cx, (jsdouble) counter, vp);
}
static jsrefcount finalizeCount = 0;
static void
finalize_counter_finalize(JSContext *cx, JSObject *obj)
{
JS_ATOMIC_INCREMENT(&finalizeCount);
}
static JSClass FinalizeCounterClass = {
"FinalizeCounter", JSCLASS_IS_ANONYMOUS,
JS_PropertyStub, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
finalize_counter_finalize
};
static JSBool
MakeFinalizeObserver(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, NULL,
JS_GetGlobalObject(cx));
if (!obj)
return false;
*vp = OBJECT_TO_JSVAL(obj);
return true;
}
static JSBool
FinalizeCount(JSContext *cx, uintN argc, jsval *vp)
{
*vp = INT_TO_JSVAL(finalizeCount);
return true;
}
static JSScript *
ValueToScript(JSContext *cx, jsval v)
{
JSScript *script = NULL;
JSFunction *fun;
if (!JSVAL_IS_PRIMITIVE(v)) {
JSObject *obj = JSVAL_TO_OBJECT(v);
JSClass *clasp = JS_GET_CLASS(cx, obj);
if (clasp == Jsvalify(&js_ScriptClass)) {
script = (JSScript *) JS_GetPrivate(cx, obj);
} else if (clasp == Jsvalify(&js_GeneratorClass)) {
JSGenerator *gen = (JSGenerator *) JS_GetPrivate(cx, obj);
fun = gen->floatingFrame()->fun();
script = FUN_SCRIPT(fun);
}
}
if (!script) {
fun = JS_ValueToFunction(cx, v);
if (!fun)
return NULL;
script = FUN_SCRIPT(fun);
if (!script) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_SCRIPTS_ONLY);
}
}
return script;
}
static JSBool
SetDebug(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv = JS_ARGV(cx, vp);
if (argc == 0 || !JSVAL_IS_BOOLEAN(argv[0])) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_NOT_ENOUGH_ARGS, "setDebug");
return JS_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.
*/
JSBool rv = JS_SetDebugMode(cx, JSVAL_TO_BOOLEAN(argv[0]));
JS_SET_RVAL(cx, vp, rv ? JSVAL_TRUE : JSVAL_FALSE);
return JS_TRUE;
}
static JSBool
GetTrapArgs(JSContext *cx, uintN argc, jsval *argv, JSScript **scriptp,
int32 *ip)
{
jsval v;
uintN intarg;
JSScript *script;
*scriptp = JS_GetScriptedCaller(cx, NULL)->script();
*ip = 0;
if (argc != 0) {
v = argv[0];
intarg = 0;
if (!JSVAL_IS_PRIMITIVE(v) &&
(JS_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == Jsvalify(&js_FunctionClass) ||
JS_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == Jsvalify(&js_ScriptClass))) {
script = ValueToScript(cx, v);
if (!script)
return JS_FALSE;
*scriptp = script;
intarg++;
}
if (argc > intarg) {
if (!JS_ValueToInt32(cx, argv[intarg], ip))
return JS_FALSE;
}
}
return JS_TRUE;
}
static JSTrapStatus
TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval,
jsval closure)
{
JSString *str = JSVAL_TO_STRING(closure);
JSStackFrame *caller = JS_GetScriptedCaller(cx, NULL);
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return JSTRAP_ERROR;
if (!JS_EvaluateUCInStackFrame(cx, caller, chars, length,
caller->script()->filename,
caller->script()->lineno,
rval)) {
return JSTRAP_ERROR;
}
if (!JSVAL_IS_VOID(*rval))
return JSTRAP_RETURN;
return JSTRAP_CONTINUE;
}
static JSBool
Trap(JSContext *cx, uintN argc, jsval *vp)
{
JSString *str;
JSScript *script;
int32 i;
jsval *argv = JS_ARGV(cx, vp);
if (argc == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TRAP_USAGE);
return JS_FALSE;
}
argc--;
str = JS_ValueToString(cx, argv[argc]);
if (!str)
return JS_FALSE;
argv[argc] = STRING_TO_JSVAL(str);
if (!GetTrapArgs(cx, argc, argv, &script, &i))
return JS_FALSE;
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_SetTrap(cx, script, script->code + i, TrapHandler, STRING_TO_JSVAL(str));
}
static JSBool
Untrap(JSContext *cx, uintN argc, jsval *vp)
{
JSScript *script;
int32 i;
if (!GetTrapArgs(cx, argc, JS_ARGV(cx, vp), &script, &i))
return JS_FALSE;
JS_ClearTrap(cx, script, script->code + i, NULL, NULL);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_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, uintN argc, jsval *vp)
{
JSString *str;
if (argc == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_NOT_ENOUGH_ARGS, "setDebuggerHandler");
return JS_FALSE;
}
str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]);
if (!str)
return JS_FALSE;
JS_SetDebuggerHandler(cx->runtime, DebuggerAndThrowHandler, str);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
SetThrowHook(JSContext *cx, uintN argc, jsval *vp)
{
JSString *str;
if (argc == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_NOT_ENOUGH_ARGS, "setThrowHook");
return JS_FALSE;
}
str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]);
if (!str)
return JS_FALSE;
JS_SetThrowHook(cx->runtime, DebuggerAndThrowHandler, str);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
LineToPC(JSContext *cx, uintN argc, jsval *vp)
{
JSScript *script;
int32 i;
uintN lineno;
jsbytecode *pc;
if (argc == 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_LINE2PC_USAGE);
return JS_FALSE;
}
script = JS_GetScriptedCaller(cx, NULL)->script();
if (!GetTrapArgs(cx, argc, JS_ARGV(cx, vp), &script, &i))
return JS_FALSE;
lineno = (i == 0) ? script->lineno : (uintN)i;
pc = JS_LineNumberToPC(cx, script, lineno);
if (!pc)
return JS_FALSE;
*vp = INT_TO_JSVAL(pc - script->code);
return JS_TRUE;
}
static JSBool
PCToLine(JSContext *cx, uintN argc, jsval *vp)
{
JSScript *script;
int32 i;
uintN lineno;
if (!GetTrapArgs(cx, argc, JS_ARGV(cx, vp), &script, &i))
return JS_FALSE;
lineno = JS_PCToLineNumber(cx, script, script->code + i);
if (!lineno)
return JS_FALSE;
*vp = INT_TO_JSVAL(lineno);
return JS_TRUE;
}
#ifdef DEBUG
static void
UpdateSwitchTableBounds(JSContext *cx, JSScript *script, uintN offset,
uintN *start, uintN *end)
{
jsbytecode *pc;
JSOp op;
ptrdiff_t jmplen;
jsint low, high, n;
pc = script->code + offset;
op = js_GetOpcode(cx, script, pc);
switch (op) {
case JSOP_TABLESWITCHX:
jmplen = JUMPX_OFFSET_LEN;
goto jump_table;
case JSOP_TABLESWITCH:
jmplen = JUMP_OFFSET_LEN;
jump_table:
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;
case JSOP_LOOKUPSWITCHX:
jmplen = JUMPX_OFFSET_LEN;
goto lookup_table;
case JSOP_LOOKUPSWITCH:
jmplen = JUMP_OFFSET_LEN;
lookup_table:
pc += jmplen;
n = GET_INDEX(pc);
pc += INDEX_LEN;
jmplen += JUMP_OFFSET_LEN;
break;
default:
/* [condswitch] switch does not have any jump or lookup tables. */
JS_ASSERT(op == JSOP_CONDSWITCH);
return;
}
*start = (uintN)(pc - script->code);
*end = *start + (uintN)(n * jmplen);
}
static void
SrcNotes(JSContext *cx, JSScript *script, Sprinter *sp)
{
uintN offset, lineno, delta, caseOff, switchTableStart, switchTableEnd;
jssrcnote *notes, *sn;
JSSrcNoteType type;
const char *name;
uint32 index;
JSAtom *atom;
JSString *str;
Sprint(sp, "\nSource notes:\n");
Sprint(sp, "%4s %4s %5s %6s %-8s %s\n",
"ofs", "line", "pc", "delta", "desc", "args");
Sprint(sp, "---- ---- ----- ------ -------- ------\n");
offset = 0;
lineno = script->lineno;
notes = script->notes();
switchTableEnd = switchTableStart = 0;
for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
delta = SN_DELTA(sn);
offset += delta;
type = (JSSrcNoteType) SN_TYPE(sn);
name = js_SrcNoteSpec[type].name;
if (type == SRC_LABEL) {
/* Check if the source note is for a switch case. */
if (switchTableStart <= offset && offset < switchTableEnd) {
name = "case";
} else {
JS_ASSERT(js_GetOpcode(cx, script, script->code + offset) == JSOP_NOP);
}
}
Sprint(sp, "%3u: %4u %5u [%4u] %-8s", uintN(sn - notes), lineno, offset, delta, name);
switch (type) {
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",
uintN(js_GetSrcNoteOffset(sn, 0)),
uintN(js_GetSrcNoteOffset(sn, 1)),
uintN(js_GetSrcNoteOffset(sn, 2)));
break;
case SRC_IF_ELSE:
Sprint(sp, " else %u elseif %u",
uintN(js_GetSrcNoteOffset(sn, 0)),
uintN(js_GetSrcNoteOffset(sn, 1)));
break;
case SRC_COND:
case SRC_WHILE:
case SRC_PCBASE:
case SRC_PCDELTA:
case SRC_DECL:
case SRC_BRACE:
Sprint(sp, " offset %u", uintN(js_GetSrcNoteOffset(sn, 0)));
break;
case SRC_LABEL:
case SRC_LABELBRACE:
case SRC_BREAK2LABEL:
case SRC_CONT2LABEL:
index = js_GetSrcNoteOffset(sn, 0);
atom = script->getAtom(index);
Sprint(sp, " atom %u (", index);
{
size_t len = PutEscapedString(NULL, 0, atom, '\0');
if (char *buf = SprintReserveAmount(sp, len)) {
PutEscapedString(buf, len, atom, 0);
buf[len] = '\0';
}
}
Sprint(sp, ")");
break;
case SRC_FUNCDEF: {
index = js_GetSrcNoteOffset(sn, 0);
JSObject *obj = script->getObject(index);
JSFunction *fun = (JSFunction *) JS_GetPrivate(cx, obj);
str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
JSAutoByteString bytes;
if (!str || !bytes.encode(cx, str))
ReportException(cx);
Sprint(sp, " function %u (%s)", index, !!bytes ? bytes.ptr() : "N/A");
break;
}
case SRC_SWITCH:
Sprint(sp, " length %u", uintN(js_GetSrcNoteOffset(sn, 0)));
caseOff = (uintN) js_GetSrcNoteOffset(sn, 1);
if (caseOff)
Sprint(sp, " first case offset %u", caseOff);
UpdateSwitchTableBounds(cx, script, offset,
&switchTableStart, &switchTableEnd);
break;
case SRC_CATCH:
delta = (uintN) js_GetSrcNoteOffset(sn, 0);
if (delta) {
if (script->main[offset] == JSOP_LEAVEBLOCK)
Sprint(sp, " stack depth %u", delta);
else
Sprint(sp, " guard delta %u", delta);
}
break;
default:;
}
Sprint(sp, "\n");
}
}
static JSBool
Notes(JSContext *cx, uintN argc, jsval *vp)
{
uintN i;
JSScript *script;
void *mark = JS_ARENA_MARK(&cx->tempPool);
Sprinter sprinter;
INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
jsval *argv = JS_ARGV(cx, vp);
for (i = 0; i < argc; i++) {
script = ValueToScript(cx, argv[i]);
if (!script)
continue;
SrcNotes(cx, script, &sprinter);
}
JSString *str = JS_NewStringCopyZ(cx, sprinter.base);
JS_ARENA_RELEASE(&cx->tempPool, mark);
if (!str)
return JS_FALSE;
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str));
return JS_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" };
static JSBool
TryNotes(JSContext *cx, JSScript *script, Sprinter *sp)
{
JSTryNote *tn, *tnlimit;
if (!JSScript::isValidOffset(script->trynotesOffset))
return JS_TRUE;
tn = script->trynotes()->vector;
tnlimit = tn + script->trynotes()->length;
Sprint(sp, "\nException table:\nkind stack start end\n");
do {
JS_ASSERT(tn->kind < JS_ARRAY_LENGTH(TryNoteNames));
Sprint(sp, " %-7s %6u %8u %8u\n",
TryNoteNames[tn->kind], tn->stackDepth,
tn->start, tn->start + tn->length);
} while (++tn != tnlimit);
return JS_TRUE;
}
static bool
DisassembleValue(JSContext *cx, jsval v, bool lines, bool recursive, Sprinter *sp)
{
JSScript *script = ValueToScript(cx, v);
if (!script)
return false;
if (VALUE_IS_FUNCTION(cx, v)) {
JSFunction *fun = JS_ValueToFunction(cx, v);
if (fun && (fun->flags & ~7U)) {
uint16 flags = fun->flags;
Sprint(sp, "flags:");
#define SHOW_FLAG(flag) if (flags & JSFUN_##flag) Sprint(sp, " " #flag);
SHOW_FLAG(LAMBDA);
SHOW_FLAG(HEAVYWEIGHT);
SHOW_FLAG(EXPR_CLOSURE);
SHOW_FLAG(TRCINFO);
#undef SHOW_FLAG
if (FUN_INTERPRETED(fun)) {
if (FUN_NULL_CLOSURE(fun))
Sprint(sp, " NULL_CLOSURE");
else if (FUN_FLAT_CLOSURE(fun))
Sprint(sp, " FLAT_CLOSURE");
JSScript *script = fun->script();
if (script->bindings.hasUpvars()) {
Sprint(sp, "\nupvars: {\n");
void *mark = JS_ARENA_MARK(&cx->tempPool);
jsuword *localNames = script->bindings.getLocalNameArray(cx, &cx->tempPool);
if (!localNames)
return false;
JSUpvarArray *uva = script->upvars();
uintN upvar_base = script->bindings.countArgsAndVars();
for (uint32 i = 0, n = uva->length; i < n; i++) {
JSAtom *atom = JS_LOCAL_NAME_TO_ATOM(localNames[upvar_base + i]);
UpvarCookie cookie = uva->vector[i];
JSAutoByteString printable;
if (js_AtomToPrintableString(cx, atom, &printable)) {
Sprint(sp, " %s: {skip:%u, slot:%u},\n",
printable.ptr(), cookie.level(), cookie.slot());
}
}
JS_ARENA_RELEASE(&cx->tempPool, mark);
Sprint(sp, "}");
}
}
Sprint(sp, "\n");
}
}
if (!js_Disassemble(cx, script, lines, sp))
return false;
SrcNotes(cx, script, sp);
TryNotes(cx, script, sp);
if (recursive && JSScript::isValidOffset(script->objectsOffset)) {
JSObjectArray *objects = script->objects();
for (uintN i = 0; i != objects->length; ++i) {
JSObject *obj = objects->vector[i];
if (obj->isFunction()) {
Sprint(sp, "\n");
if (!DisassembleValue(cx, OBJECT_TO_JSVAL(obj), lines, recursive, sp))
return false;
}
}
}
return true;
}
static JSBool
DisassembleToString(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv = JS_ARGV(cx, vp);
/* Read options off early arguments */
bool lines = false, recursive = false;
while (argc > 0 && JSVAL_IS_STRING(argv[0])) {
JSString *str = JSVAL_TO_STRING(argv[0]);
JSFlatString *flatStr = JS_FlattenString(cx, str);
if (!flatStr)
return JS_FALSE;
lines |= !!JS_FlatStringEqualsAscii(flatStr, "-l");
recursive |= !!JS_FlatStringEqualsAscii(flatStr, "-r");
if (!lines && !recursive)
break;
argv++, argc--;
}
void *mark = JS_ARENA_MARK(&cx->tempPool);
Sprinter sprinter;
INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
Sprinter *sp = &sprinter;
bool ok = true;
if (argc == 0) {
/* Without arguments, disassemble the current script. */
if (JSStackFrame *frame = JS_GetScriptedCaller(cx, NULL)) {
JSScript *script = JS_GetFrameScript(cx, frame);
if (js_Disassemble(cx, script, lines, sp)) {
SrcNotes(cx, script, sp);
TryNotes(cx, script, sp);
} else {
ok = false;
}
}
} else {
for (uintN i = 0; i < argc; i++)
ok = ok && DisassembleValue(cx, argv[i], lines, recursive, sp);
}
JSString *str = ok ? JS_NewStringCopyZ(cx, sprinter.base) : NULL;
JS_ARENA_RELEASE(&cx->tempPool, mark);
if (!str)
return false;
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(str));
return true;
}
static JSBool
Disassemble(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv = JS_ARGV(cx, vp);
/* Read options off early arguments */
bool lines = false, recursive = false;
while (argc > 0 && JSVAL_IS_STRING(argv[0])) {
JSString *str = JSVAL_TO_STRING(argv[0]);
JSFlatString *flatStr = JS_FlattenString(cx, str);
if (!flatStr)
return JS_FALSE;
lines |= !!JS_FlatStringEqualsAscii(flatStr, "-l");
recursive |= !!JS_FlatStringEqualsAscii(flatStr, "-r");
if (!lines && !recursive)
break;
argv++, argc--;
}
void *mark = JS_ARENA_MARK(&cx->tempPool);
Sprinter sprinter;
INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
Sprinter *sp = &sprinter;
bool ok = true;
if (argc == 0) {
/* Without arguments, disassemble the current script. */
if (JSStackFrame *frame = JS_GetScriptedCaller(cx, NULL)) {
JSScript *script = JS_GetFrameScript(cx, frame);
if (js_Disassemble(cx, script, lines, sp)) {
SrcNotes(cx, script, sp);
TryNotes(cx, script, sp);
} else {
ok = false;
}
}
} else {
for (uintN i = 0; i < argc; i++)
ok = ok && DisassembleValue(cx, argv[i], lines, recursive, sp);
}
if (ok)
fprintf(stdout, "%s\n", sprinter.base);
JS_ARENA_RELEASE(&cx->tempPool, mark);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return ok;
}
static JSBool
DisassFile(JSContext *cx, uintN argc, jsval *vp)
{
jsval *argv = JS_ARGV(cx, vp);
if (!argc) {
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
/* Support extra options at the start, just like Dissassemble. */
uintN _argc = argc;
argv += argc-1;
argc = 1;
JSObject *thisobj = JS_THIS_OBJECT(cx, vp);
if (!thisobj)
return JS_FALSE;
JSString *str = JS_ValueToString(cx, argv[0]);
if (!str)
return JS_FALSE;
JSAutoByteString filename(cx, str);
if (!filename)
return JS_FALSE;
uint32 oldopts = JS_GetOptions(cx);
JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL);
JSObject *scriptObj = JS_CompileFile(cx, thisobj, filename.ptr());
JS_SetOptions(cx, oldopts);
if (!scriptObj)
return false;
argv[0] = OBJECT_TO_JSVAL(scriptObj);
JSBool ok = Disassemble(cx, _argc, vp); /* gross, but works! */
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return ok;
}
static JSBool
DisassWithSrc(JSContext *cx, uintN argc, jsval *vp)
{
#define LINE_BUF_LEN 512
uintN i, len, line1, line2, bupline;
JSScript *script;
FILE *file;
char linebuf[LINE_BUF_LEN];
jsbytecode *pc, *end;
JSBool ok;
static char sep[] = ";-------------------------";
ok = JS_TRUE;
jsval *argv = JS_ARGV(cx, vp);
for (i = 0; ok && i < argc; i++) {
script = ValueToScript(cx, argv[i]);
if (!script)
return JS_FALSE;
if (!script->filename) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_FILE_SCRIPTS_ONLY);
return JS_FALSE;
}
file = fopen(script->filename, "r");
if (!file) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
JSSMSG_CANT_OPEN, script->filename,
strerror(errno));
return JS_FALSE;
}
pc = script->code;
end = pc + script->length;
void *mark = JS_ARENA_MARK(&cx->tempPool);
Sprinter sprinter;
Sprinter *sp = &sprinter;
INIT_SPRINTER(cx, sp, &cx->tempPool, 0);
/* 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 = JS_FALSE;
goto bail;
}
}
bupline = 0;
while (pc < end) {
line2 = JS_PCToLineNumber(cx, script, pc);
if (line2 < line1) {
if (bupline != line2) {
bupline = line2;
Sprint(sp, "%s %3u: BACKUP\n", sep, line2);
}
} else {
if (bupline && line1 == line2)
Sprint(sp, "%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 = JS_FALSE;
goto bail;
}
line1++;
Sprint(sp, "%s %3u: %s", sep, line1, linebuf);
}
}
len = js_Disassemble1(cx, script, pc, pc - script->code, JS_TRUE, sp);
if (!len) {
ok = JS_FALSE;
goto bail;
}
pc += len;
}
bail:
JS_ARENA_RELEASE(&cx->tempPool, mark);
fclose(file);
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return ok;
#undef LINE_BUF_LEN
}
static JSBool
Tracing(JSContext *cx, uintN argc, jsval *vp)
{
FILE *file;
if (argc == 0) {
*vp = BOOLEAN_TO_JSVAL(cx->logfp != 0);
return JS_TRUE;
}
jsval *argv = JS_ARGV(cx, vp);
switch (JS_TypeOfValue(cx, argv[0])) {
case JSTYPE_NUMBER:
case JSTYPE_BOOLEAN: {
JSBool bval;
JS_ValueToBoolean(cx, argv[0], &bval);
file = bval ? stderr : NULL;
break;
}
case JSTYPE_STRING: {
JSAutoByteString name(cx, JSVAL_TO_STRING(argv[0]));
if (!name)
return JS_FALSE;
file = fopen(name.ptr(), "w");
if (!file) {
JS_ReportError(cx, "tracing: couldn't open output file %s: %s",
name.ptr(), strerror(errno));
return JS_FALSE;
}
break;
}
default:
goto bad_argument;
}
if (cx->logfp && cx->logfp != stderr)
fclose((FILE *)cx->logfp);
cx->logfp = file;
cx->logPrevPc = NULL;
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
bad_argument:
JSString *str = JS_ValueToString(cx, argv[0]);
if (!str)
return JS_FALSE;
JSAutoByteString bytes(cx, str);
if (!bytes)
return JS_FALSE;
JS_ReportError(cx, "tracing: illegal argument %s", bytes.ptr());
return JS_FALSE;
}
static void
DumpScope(JSContext *cx, JSObject *obj, FILE *fp)
{
uintN i = 0;
for (JSScopeProperty *sprop = NULL; JS_PropertyIterator(obj, &sprop);) {
fprintf(fp, "%3u %p ", i++, (void *) sprop);
((Shape *) sprop)->dump(cx, fp);
}
}
static JSBool
DumpStats(JSContext *cx, uintN argc, jsval *vp)
{
uintN i;
JSString *str;
jsid id;
JSObject *obj2;
JSProperty *prop;
Value value;
jsval *argv = JS_ARGV(cx, vp);
for (i = 0; i < argc; i++) {
str = JS_ValueToString(cx, argv[i]);
if (!str)
return JS_FALSE;
argv[i] = STRING_TO_JSVAL(str);
JSFlatString *flatStr = JS_FlattenString(cx, str);
if (!flatStr)
return JS_FALSE;
if (JS_FlatStringEqualsAscii(flatStr, "arena")) {
#ifdef JS_ARENAMETER
JS_DumpArenaStats(stdout);
#endif
} else if (JS_FlatStringEqualsAscii(flatStr, "atom")) {
js_DumpAtoms(cx, gOutFile);
} else if (JS_FlatStringEqualsAscii(flatStr, "global")) {
DumpScope(cx, cx->globalObject, stdout);
} else {
if (!JS_ValueToId(cx, STRING_TO_JSVAL(str), &id))
return JS_FALSE;
JSObject *obj;
if (!js_FindProperty(cx, id, &obj, &obj2, &prop))
return JS_FALSE;
if (prop) {
if (!obj->getProperty(cx, id, &value))
return JS_FALSE;
}
if (!prop || !value.isObjectOrNull()) {
fputs("js: invalid stats argument ", gErrFile);
JS_FileEscapedString(gErrFile, str, 0);
putc('\n', gErrFile);
continue;
}
obj = value.toObjectOrNull();
if (obj)
DumpScope(cx, obj, stdout);
}
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
DumpHeap(JSContext *cx, uintN argc, jsval *vp)
{
jsval v;
void* startThing;
uint32 startTraceKind;
const char *badTraceArg;
void *thingToFind;
size_t maxDepth;
void *thingToIgnore;
FILE *dumpFile;
JSBool ok;
const char *fileName = NULL;
JSAutoByteString fileNameBytes;
if (argc > 0) {
v = JS_ARGV(cx, vp)[0];
if (!JSVAL_IS_NULL(v)) {
JSString *str;
str = JS_ValueToString(cx, v);
if (!str)
return JS_FALSE;
JS_ARGV(cx, vp)[0] = STRING_TO_JSVAL(str);
if (!fileNameBytes.encode(cx, str))
return JS_FALSE;
fileName = fileNameBytes.ptr();
}
}
startThing = NULL;
startTraceKind = 0;
if (argc > 1) {
v = JS_ARGV(cx, vp)[1];
if (JSVAL_IS_TRACEABLE(v)) {
startThing = JSVAL_TO_TRACEABLE(v);
startTraceKind = JSVAL_TRACE_KIND(v);
} else if (!JSVAL_IS_NULL(v)) {
badTraceArg = "start";
goto not_traceable_arg;
}
}
thingToFind = NULL;
if (argc > 2) {
v = JS_ARGV(cx, vp)[2];
if (JSVAL_IS_TRACEABLE(v)) {
thingToFind = JSVAL_TO_TRACEABLE(v);
} else if (!JSVAL_IS_NULL(v)) {
badTraceArg = "toFind";
goto not_traceable_arg;
}
}
maxDepth = (size_t)-1;
if (argc > 3) {
v = JS_ARGV(cx, vp)[3];
if (!JSVAL_IS_NULL(v)) {
uint32 depth;
if (!JS_ValueToECMAUint32(cx, v, &depth))
return JS_FALSE;
maxDepth = depth;
}
}
thingToIgnore = NULL;
if (argc > 4) {
v = JS_ARGV(cx, vp)[4];
if (JSVAL_IS_TRACEABLE(v)) {
thingToIgnore = JSVAL_TO_TRACEABLE(v);
} else if (!JSVAL_IS_NULL(v)) {
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 JS_FALSE;
}
}
ok = JS_DumpHeap(cx, dumpFile, startThing, startTraceKind, thingToFind,
maxDepth, thingToIgnore);
if (dumpFile != stdout)
fclose(dumpFile);
return ok;
not_traceable_arg:
JS_ReportError(cx, "argument '%s' is not null or a heap-allocated thing",
badTraceArg);
return JS_FALSE;
}
JSBool
DumpObject(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *arg0 = NULL;
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "o", &arg0))
return JS_FALSE;
js_DumpObject(arg0);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
#endif /* DEBUG */
#ifdef TEST_CVTARGS
#include <ctype.h>
static const char *
EscapeWideString(jschar *w)
{
static char enuf[80];
static char hex[] = "0123456789abcdef";
jschar u;
unsigned char b, c;
int i, j;
if (!w)
return "";
for (i = j = 0; i < sizeof enuf - 1; i++, j++) {
u = w[j];
if (u == 0)
break;
b = (unsigned char)(u >> 8);
c = (unsigned char)(u);
if (b) {
if (i >= sizeof enuf - 6)
break;
enuf[i++] = '\\';
enuf[i++] = 'u';
enuf[i++] = hex[b >> 4];
enuf[i++] = hex[b & 15];
enuf[i++] = hex[c >> 4];
enuf[i] = hex[c & 15];
} else if (!isprint(c)) {
if (i >= sizeof enuf - 4)
break;
enuf[i++] = '\\';
enuf[i++] = 'x';
enuf[i++] = hex[c >> 4];
enuf[i] = hex[c & 15];
} else {
enuf[i] = (char)c;
}
}
enuf[i] = 0;
return enuf;
}
#include <stdarg.h>
static JSBool
ZZ_formatter(JSContext *cx, const char *format, JSBool fromJS, jsval **vpp,
va_list *app)
{
jsval *vp;
va_list ap;
jsdouble re, im;
printf("entering ZZ_formatter");
vp = *vpp;
ap = *app;
if (fromJS) {
if (!JS_ValueToNumber(cx, vp[0], &re))
return JS_FALSE;
if (!JS_ValueToNumber(cx, vp[1], &im))
return JS_FALSE;
*va_arg(ap, jsdouble *) = re;
*va_arg(ap, jsdouble *) = im;
} else {
re = va_arg(ap, jsdouble);
im = va_arg(ap, jsdouble);
if (!JS_NewNumberValue(cx, re, &vp[0]))
return JS_FALSE;
if (!JS_NewNumberValue(cx, im, &vp[1]))
return JS_FALSE;
}
*vpp = vp + 2;
*app = ap;
printf("leaving ZZ_formatter");
return JS_TRUE;
}
static JSBool
ConvertArgs(JSContext *cx, uintN argc, jsval *vp)
{
JSBool b = JS_FALSE;
jschar c = 0;
int32 i = 0, j = 0;
uint32 u = 0;
jsdouble d = 0, I = 0, re = 0, im = 0;
JSString *str = NULL;
jschar *w = NULL;
JSObject *obj2 = NULL;
JSFunction *fun = NULL;
jsval v = JSVAL_VOID;
JSBool ok;
if (!JS_AddArgumentFormatter(cx, "ZZ", ZZ_formatter))
return JS_FALSE;
ok = JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "b/ciujdISWofvZZ*",
&b, &c, &i, &u, &j, &d, &I, &str, &w, &obj2,
&fun, &v, &re, &im);
JS_RemoveArgumentFormatter(cx, "ZZ");
if (!ok)
return JS_FALSE;
fprintf(gOutFile,
"b %u, c %x (%c), i %ld, u %lu, j %ld\n",
b, c, (char)c, i, u, j);
ToString obj2string(cx, obj2);
ToString valueString(cx, v);
JSAutoByteString strBytes;
if (str)
strBytes.encode(cx, str);
JSString *tmpstr = JS_DecompileFunction(cx, fun, 4);
JSAutoByteString func;
if (!tmpstr || !func.encode(cx, tmpstr));
ReportException(cx);
fprintf(gOutFile,
"d %g, I %g, S %s, W %s, obj %s, fun %s\n"
"v %s, re %g, im %g\n",
d, I, !!strBytes ? strBytes.ptr() : "", EscapeWideString(w),
obj2string.getBytes(),
fun ? (!!func ? func.ptr() : "error decompiling fun") : "",
valueString.getBytes(), re, im);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
#endif
static JSBool
BuildDate(JSContext *cx, uintN argc, jsval *vp)
{
char version[20] = "\n";
#if JS_VERSION < 150
sprintf(version, " for version %d\n", JS_VERSION);
#endif
fprintf(gOutFile, "built on %s at %s%s", __DATE__, __TIME__, version);
*vp = JSVAL_VOID;
return JS_TRUE;
}
static JSBool
Clear(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *obj;
if (argc != 0 && !JS_ValueToObject(cx, JS_ARGV(cx, vp)[0], &obj))
return JS_FALSE;
JS_ClearScope(cx, obj);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
Intern(JSContext *cx, uintN argc, jsval *vp)
{
JSString *str = JS_ValueToString(cx, argc == 0 ? JSVAL_VOID : vp[2]);
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;
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return true;
}
static JSBool
Clone(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *funobj, *parent, *clone;
if (!argc)
return JS_FALSE;
jsval *argv = JS_ARGV(cx, vp);
if (VALUE_IS_FUNCTION(cx, argv[0])) {
funobj = JSVAL_TO_OBJECT(argv[0]);
} else {
JSFunction *fun = JS_ValueToFunction(cx, argv[0]);
if (!fun)
return JS_FALSE;
funobj = JS_GetFunctionObject(fun);
}
if (argc > 1) {
if (!JS_ValueToObject(cx, argv[1], &parent))
return JS_FALSE;
} else {
parent = JS_GetParent(cx, funobj);
}
clone = JS_CloneFunctionObject(cx, funobj, parent);
if (!clone)
return JS_FALSE;
*vp = OBJECT_TO_JSVAL(clone);
return JS_TRUE;
}
static JSBool
GetPDA(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *vobj, *aobj, *pdobj;
JSBool ok;
JSPropertyDescArray pda;
JSPropertyDesc *pd;
uint32 i;
jsval v;
if (!JS_ValueToObject(cx, argc == 0 ? JSVAL_VOID : vp[2], &vobj))
return JS_FALSE;
if (!vobj) {
*vp = JSVAL_VOID;
return JS_TRUE;
}
aobj = JS_NewArrayObject(cx, 0, NULL);
if (!aobj)
return JS_FALSE;
*vp = OBJECT_TO_JSVAL(aobj);
ok = JS_GetPropertyDescArray(cx, vobj, &pda);
if (!ok)
return JS_FALSE;
pd = pda.array;
for (i = 0; i < pda.length; i++, pd++) {
pdobj = JS_NewObject(cx, NULL, NULL, NULL);
if (!pdobj) {
ok = JS_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)) &&
(v = INT_TO_JSVAL(pd->slot),
JS_SetProperty(cx, pdobj, "slot", &v)) &&
JS_SetProperty(cx, pdobj, "alias", &pd->alias);
if (!ok)
break;
}
JS_PutPropertyDescArray(cx, &pda);
return ok;
}
static JSBool
GetSLX(JSContext *cx, uintN argc, jsval *vp)
{
JSScript *script;
script = ValueToScript(cx, argc == 0 ? JSVAL_VOID : vp[2]);
if (!script)
return JS_FALSE;
*vp = INT_TO_JSVAL(js_GetScriptLineExtent(script));
return JS_TRUE;
}
static JSBool
ToInt32(JSContext *cx, uintN argc, jsval *vp)
{
int32 i;
if (!JS_ValueToInt32(cx, argc == 0 ? JSVAL_VOID : vp[2], &i))
return JS_FALSE;
return JS_NewNumberValue(cx, i, vp);
}
static JSBool
StringsAreUTF8(JSContext *cx, uintN argc, jsval *vp)
{
*vp = JS_CStringsAreUTF8() ? JSVAL_TRUE : JSVAL_FALSE;
return JS_TRUE;
}
static JSBool
StackQuota(JSContext *cx, uintN argc, jsval *vp)
{
uint32 n;
if (argc == 0)
return JS_NewNumberValue(cx, (double) gScriptStackQuota, vp);
if (!JS_ValueToECMAUint32(cx, JS_ARGV(cx, vp)[0], &n))
return JS_FALSE;
gScriptStackQuota = n;
JS_SetScriptStackQuota(cx, gScriptStackQuota);
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static const char* badUTF8 = "...\xC0...";
static const char* bigUTF8 = "...\xFB\xBF\xBF\xBF\xBF...";
static const jschar badSurrogate[] = { 'A', 'B', 'C', 0xDEEE, 'D', 'E', 0 };
static JSBool
TestUTF8(JSContext *cx, uintN argc, jsval *vp)
{
int32 mode = 1;
jschar chars[20];
size_t charsLength = 5;
char bytes[20];
size_t bytesLength = 20;
if (argc && !JS_ValueToInt32(cx, *JS_ARGV(cx, vp), &mode))
return JS_FALSE;
/* The following throw errors if compiled with UTF-8. */
switch (mode) {
/* mode 1: malformed UTF-8 string. */
case 1:
JS_NewStringCopyZ(cx, badUTF8);
break;
/* mode 2: big UTF-8 character. */
case 2:
JS_NewStringCopyZ(cx, bigUTF8);
break;
/* mode 3: bad surrogate character. */
case 3:
JS_EncodeCharacters(cx, badSurrogate, 6, bytes, &bytesLength);
break;
/* mode 4: use a too small buffer. */
case 4:
JS_DecodeBytes(cx, "1234567890", 10, chars, &charsLength);
break;
default:
JS_ReportError(cx, "invalid mode parameter");
return JS_FALSE;
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return !JS_IsExceptionPending (cx);
}
static JSBool
ThrowError(JSContext *cx, uintN argc, jsval *vp)
{
JS_ReportError(cx, "This is an error");
return JS_FALSE;
}
#define LAZY_STANDARD_CLASSES
/* A class for easily testing the inner/outer object callbacks. */
typedef struct ComplexObject {
JSBool isInner;
JSBool frozen;
JSObject *inner;
JSObject *outer;
} ComplexObject;
static JSObject *
split_create_outer(JSContext *cx);
static JSObject *
split_create_inner(JSContext *cx, JSObject *outer);
static ComplexObject *
split_get_private(JSContext *cx, JSObject *obj);
static JSBool
split_addProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
ComplexObject *cpx;
cpx = split_get_private(cx, obj);
if (!cpx)
return JS_TRUE;
if (!cpx->isInner && cpx->inner) {
/* Make sure to define this property on the inner object. */
return JS_DefinePropertyById(cx, cpx->inner, id, *vp, NULL, NULL, JSPROP_ENUMERATE);
}
return JS_TRUE;
}
static JSBool
split_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
ComplexObject *cpx;
cpx = split_get_private(cx, obj);
if (!cpx)
return JS_TRUE;
if (JSID_IS_ATOM(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "isInner")) {
*vp = BOOLEAN_TO_JSVAL(cpx->isInner);
return JS_TRUE;
}
if (!cpx->isInner && cpx->inner) {
if (JSID_IS_ATOM(id)) {
JSString *str = JSID_TO_STRING(id);
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return false;
return JS_GetUCProperty(cx, cpx->inner, chars, length, vp);
}
if (JSID_IS_INT(id))
return JS_GetElement(cx, cpx->inner, JSID_TO_INT(id), vp);
return JS_TRUE;
}
return JS_TRUE;
}
static JSBool
split_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
{
ComplexObject *cpx;
cpx = split_get_private(cx, obj);
if (!cpx)
return true;
if (!cpx->isInner && cpx->inner) {
if (JSID_IS_ATOM(id)) {
JSString *str = JSID_TO_STRING(id);
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return false;
return JS_SetUCProperty(cx, cpx->inner, chars, length, vp);
}
if (JSID_IS_INT(id))
return JS_SetElement(cx, cpx->inner, JSID_TO_INT(id), vp);
return true;
}
return true;
}
static JSBool
split_delProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
ComplexObject *cpx;
jsid asId;
cpx = split_get_private(cx, obj);
if (!cpx)
return JS_TRUE;
if (!cpx->isInner && cpx->inner) {
/* Make sure to define this property on the inner object. */
if (!JS_ValueToId(cx, *vp, &asId))
return JS_FALSE;
return cpx->inner->deleteProperty(cx, asId, Valueify(vp), true);
}
return JS_TRUE;
}
static JSBool
split_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
jsval *statep, jsid *idp)
{
ComplexObject *cpx;
JSObject *iterator;
switch (enum_op) {
case JSENUMERATE_INIT:
case JSENUMERATE_INIT_ALL:
cpx = (ComplexObject *) JS_GetPrivate(cx, obj);
if (!cpx->isInner && cpx->inner)
obj = cpx->inner;
iterator = JS_NewPropertyIterator(cx, obj);
if (!iterator)
return JS_FALSE;
*statep = OBJECT_TO_JSVAL(iterator);
if (idp)
*idp = INT_TO_JSID(0);
break;
case JSENUMERATE_NEXT:
iterator = (JSObject*)JSVAL_TO_OBJECT(*statep);
if (!JS_NextProperty(cx, iterator, idp))
return JS_FALSE;
if (!JSID_IS_VOID(*idp))
break;
/* Fall through. */
case JSENUMERATE_DESTROY:
/* Let GC at our iterator object. */
*statep = JSVAL_NULL;
break;
}
return JS_TRUE;
}
static JSBool
ResolveClass(JSContext *cx, JSObject *obj, jsid id, JSBool *resolved)
{
if (!JS_ResolveStandardClass(cx, obj, id, resolved))
return JS_FALSE;
if (!*resolved) {
if (JSID_IS_ATOM(id, CLASS_ATOM(cx, Reflect))) {
if (!js_InitReflectClass(cx, obj))
return JS_FALSE;
*resolved = JS_TRUE;
}
}
return JS_TRUE;
}
static JSBool
split_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSObject **objp)
{
ComplexObject *cpx;
if (JSID_IS_ATOM(id) && JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "isInner")) {
*objp = obj;
return JS_DefinePropertyById(cx, obj, id, JSVAL_VOID, NULL, NULL, JSPROP_SHARED);
}
cpx = split_get_private(cx, obj);
if (!cpx)
return JS_TRUE;
if (!cpx->isInner && cpx->inner) {
JSProperty *prop;
return cpx->inner->lookupProperty(cx, id, objp, &prop);
}
#ifdef LAZY_STANDARD_CLASSES
if (!(flags & JSRESOLVE_ASSIGNING)) {
JSBool resolved;
if (!ResolveClass(cx, obj, id, &resolved))
return JS_FALSE;
if (resolved) {
*objp = obj;
return JS_TRUE;
}
}
#endif
/* XXX For additional realism, let's resolve some random property here. */
return JS_TRUE;
}
static void
split_finalize(JSContext *cx, JSObject *obj)
{
JS_free(cx, JS_GetPrivate(cx, obj));
}
static void
split_trace(JSTracer *trc, JSObject *obj)
{
ComplexObject *cpx;
cpx = (ComplexObject *) JS_GetPrivate(trc->context, obj);
if (!cpx)
return; /* The object is not fully constructed. */
if (!cpx->isInner && cpx->inner) {
/* Mark the inner object. */
JS_CALL_TRACER(trc, cpx->inner, JSTRACE_OBJECT, "ComplexObject.inner");
}
if (cpx->isInner && cpx->outer) {
/* Mark the inner object. */
JS_CALL_TRACER(trc, cpx->outer, JSTRACE_OBJECT, "ComplexObject.outer");
}
}
static JSObject *
split_outerObject(JSContext *cx, JSObject *obj)
{
ComplexObject *cpx;
cpx = (ComplexObject *) JS_GetPrivate(cx, obj);
return cpx->isInner ? cpx->outer : obj;
}
static JSObject *
split_thisObject(JSContext *cx, JSObject *obj)
{
OBJ_TO_OUTER_OBJECT(cx, obj);
if (!obj)
return NULL;
return obj;
}
static JSBool
split_equality(JSContext *cx, JSObject *obj, const jsval *v, JSBool *bp);
static JSObject *
split_innerObject(JSContext *cx, JSObject *obj)
{
ComplexObject *cpx;
cpx = (ComplexObject *) JS_GetPrivate(cx, obj);
if (cpx->frozen) {
JS_ASSERT(!cpx->isInner);
return obj;
}
return !cpx->isInner ? cpx->inner : obj;
}
static Class split_global_class = {
"split_global",
JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE | JSCLASS_HAS_PRIVATE | JSCLASS_GLOBAL_FLAGS,
Valueify(split_addProperty),
Valueify(split_delProperty),
Valueify(split_getProperty),
Valueify(split_setProperty),
(JSEnumerateOp)split_enumerate,
(JSResolveOp)split_resolve,
ConvertStub,
split_finalize,
NULL, /* reserved0 */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
NULL, /* xdrObject */
NULL, /* hasInstance */
split_trace,
{
Valueify(split_equality),
split_outerObject,
split_innerObject,
NULL, /* iteratorObject */
NULL, /* wrappedObject */
},
{
NULL, /* lookupProperty */
NULL, /* defineProperty */
NULL, /* getProperty */
NULL, /* setProperty */
NULL, /* getAttributes */
NULL, /* setAttributes */
NULL, /* deleteProperty */
NULL, /* enumerate */
NULL, /* typeOf */
NULL, /* fix */
split_thisObject,
NULL, /* clear */
},
};
static JSBool
split_equality(JSContext *cx, JSObject *obj, const jsval *v, JSBool *bp)
{
*bp = JS_FALSE;
if (JSVAL_IS_PRIMITIVE(*v))
return JS_TRUE;
JSObject *obj2 = JSVAL_TO_OBJECT(*v);
if (obj2->getClass() != &split_global_class)
return JS_TRUE;
ComplexObject *cpx = (ComplexObject *) JS_GetPrivate(cx, obj2);
JS_ASSERT(!cpx->isInner);
ComplexObject *ourCpx = (ComplexObject *) JS_GetPrivate(cx, obj);
JS_ASSERT(!ourCpx->isInner);
*bp = (cpx == ourCpx);
return JS_TRUE;
}
JSObject *
split_create_outer(JSContext *cx)
{
ComplexObject *cpx;
JSObject *obj;
cpx = (ComplexObject *) JS_malloc(cx, sizeof *obj);
if (!cpx)
return NULL;
cpx->isInner = JS_FALSE;
cpx->frozen = JS_TRUE;
cpx->inner = NULL;
cpx->outer = NULL;
obj = JS_NewGlobalObject(cx, Jsvalify(&split_global_class));
if (!obj) {
JS_free(cx, cpx);
return NULL;
}
if (!JS_SetPrivate(cx, obj, cpx)) {
JS_free(cx, cpx);
return NULL;
}
return obj;
}
static JSObject *
split_create_inner(JSContext *cx, JSObject *outer)
{
ComplexObject *cpx, *outercpx;
JSObject *obj;
JS_ASSERT(outer->getClass() == &split_global_class);
cpx = (ComplexObject *) JS_malloc(cx, sizeof *cpx);
if (!cpx)
return NULL;
cpx->isInner = JS_TRUE;
cpx->frozen = JS_FALSE;
cpx->inner = NULL;
cpx->outer = outer;
obj = JS_NewGlobalObject(cx, Jsvalify(&split_global_class));
if (!obj || !JS_SetPrivate(cx, obj, cpx)) {
JS_free(cx, cpx);
return NULL;
}
outercpx = (ComplexObject *) JS_GetPrivate(cx, outer);
outercpx->inner = obj;
outercpx->frozen = JS_FALSE;
return obj;
}
static ComplexObject *
split_get_private(JSContext *cx, JSObject *obj)
{
do {
if (obj->getClass() == &split_global_class)
return (ComplexObject *) JS_GetPrivate(cx, obj);
obj = JS_GetParent(cx, obj);
} while (obj);
return NULL;
}
static JSBool
sandbox_enumerate(JSContext *cx, JSObject *obj)
{
jsval v;
JSBool b;
if (!JS_GetProperty(cx, obj, "lazy", &v))
return JS_FALSE;
JS_ValueToBoolean(cx, v, &b);
return !b || JS_EnumerateStandardClasses(cx, obj);
}
static JSBool
sandbox_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
JSObject **objp)
{
jsval v;
JSBool b, resolved;
if (!JS_GetProperty(cx, obj, "lazy", &v))
return JS_FALSE;
JS_ValueToBoolean(cx, v, &b);
if (b && (flags & JSRESOLVE_ASSIGNING) == 0) {
if (!ResolveClass(cx, obj, id, &resolved))
return JS_FALSE;
if (resolved) {
*objp = obj;
return JS_TRUE;
}
}
*objp = NULL;
return JS_TRUE;
}
static JSClass sandbox_class = {
"sandbox",
JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
sandbox_enumerate, (JSResolveOp)sandbox_resolve,
JS_ConvertStub, NULL,
JSCLASS_NO_OPTIONAL_MEMBERS
};
static JSObject *
NewSandbox(JSContext *cx, bool lazy, bool split)
{
JSObject *obj = JS_NewCompartmentAndGlobalObject(cx, &sandbox_class, NULL);
if (!obj)
return NULL;
{
JSAutoEnterCompartment ac;
if (!ac.enter(cx, obj))
return NULL;
if (split) {
obj = split_setup(cx, JS_TRUE);
if (!obj)
return NULL;
}
if (!lazy && !JS_InitStandardClasses(cx, obj))
return NULL;
AutoValueRooter root(cx, BooleanValue(lazy));
if (!JS_SetProperty(cx, obj, "lazy", root.jsval_addr()))
return NULL;
if (split)
obj = split_outerObject(cx, obj);
}
AutoObjectRooter objroot(cx, obj);
if (!cx->compartment->wrap(cx, objroot.addr()))
return NULL;
return objroot.object();
}
static JSBool
EvalInContext(JSContext *cx, uintN argc, jsval *vp)
{
JSString *str;
JSObject *sobj = NULL;
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "S / o", &str, &sobj))
return false;
size_t srclen;
const jschar *src = JS_GetStringCharsAndLength(cx, str, &srclen);
if (!src)
return false;
bool split = false, lazy = false;
if (srclen == 4) {
if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') {
lazy = true;
srclen = 0;
}
} else if (srclen == 5) {
if (src[0] == 's' && src[1] == 'p' && src[2] == 'l' && src[3] == 'i' && src[4] == 't') {
split = lazy = true;
srclen = 0;
}
}
if (!sobj) {
sobj = NewSandbox(cx, lazy, split);
if (!sobj)
return false;
}
*vp = OBJECT_TO_JSVAL(sobj);
if (srclen == 0)
return true;
JSStackFrame *fp = JS_GetScriptedCaller(cx, NULL);
{
JSAutoEnterCompartment ac;
uintN flags;
JSObject *unwrapped = sobj->unwrap(&flags);
if (flags & JSWrapper::CROSS_COMPARTMENT) {
sobj = unwrapped;
if (!ac.enter(cx, sobj))
return false;
}
OBJ_TO_INNER_OBJECT(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,
fp->script()->filename,
JS_PCToLineNumber(cx, fp->script(), fp->pc(cx)),
vp)) {
return false;
}
}
return cx->compartment->wrap(cx, Valueify(vp));
}
static JSBool
EvalInFrame(JSContext *cx, uintN 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 JS_FALSE;
}
uint32 upCount = JSVAL_TO_INT(argv[0]);
JSString *str = JSVAL_TO_STRING(argv[1]);
bool saveCurrent = (argc >= 3 && JSVAL_IS_BOOLEAN(argv[2]))
? !!(JSVAL_TO_BOOLEAN(argv[2]))
: false;
JS_ASSERT(cx->hasfp());
FrameRegsIter fi(cx);
for (uint32 i = 0; i < upCount; ++i, ++fi) {
if (!fi.fp()->prev())
break;
}
JSStackFrame *const fp = fi.fp();
if (!JS_IsScriptFrame(cx, fp)) {
JS_ReportError(cx, "cannot eval in non-script frame");
return JS_FALSE;
}
JSStackFrame *oldfp = NULL;
if (saveCurrent)
oldfp = JS_SaveFrameChain(cx);
size_t length;
const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
if (!chars)
return JS_FALSE;
JSBool ok = JS_EvaluateUCInStackFrame(cx, fp, chars, length,
fp->script()->filename,
JS_PCToLineNumber(cx, fp->script(),
fi.pc()),
vp);
if (saveCurrent)
JS_RestoreFrameChain(cx, oldfp);
return ok;
}
static JSBool
ShapeOf(JSContext *cx, uintN argc, jsval *vp)
{
jsval v;
if (argc < 1 || !JSVAL_IS_OBJECT(v = JS_ARGV(cx, vp)[0])) {
JS_ReportError(cx, "shapeOf: object expected");
return JS_FALSE;
}
JSObject *obj = JSVAL_TO_OBJECT(v);
if (!obj) {
*vp = JSVAL_ZERO;
return JS_TRUE;
}
if (!obj->isNative()) {
*vp = INT_TO_JSVAL(-1);
return JS_TRUE;
}
return JS_NewNumberValue(cx, obj->shape(), vp);
}
/*
* 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, JSObject *obj, JSObject *referent, jsid id,
uintN lookupFlags, JSObject **objp)
{
JSProperty *prop;
PropertyDescriptor desc;
uintN propFlags = 0;
JSObject *obj2;
*objp = NULL;
if (referent->isNative()) {
if (js_LookupPropertyWithFlags(cx, referent, id, lookupFlags, &obj2, &prop) < 0)
return false;
if (obj2 != referent)
return true;
const Shape *shape = (Shape *) prop;
if (shape->isMethod()) {
shape = referent->methodReadBarrier(cx, *shape, &desc.value);
if (!shape)
return false;
} else 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 = PropertyStub;
desc.setter = shape->setter();
if (!desc.setter && !(desc.attrs & JSPROP_SETTER))
desc.setter = StrictPropertyStub;
desc.shortid = shape->shortid;
propFlags = shape->getFlags();
} else if (referent->isProxy()) {
PropertyDescriptor desc;
if (!JSProxy::getOwnPropertyDescriptor(cx, referent, id, false, &desc))
return false;
if (!desc.obj)
return true;
} else {
if (!referent->lookupProperty(cx, id, objp, &prop))
return false;
if (*objp != referent)
return true;
if (!referent->getProperty(cx, id, &desc.value) ||
!referent->getAttributes(cx, id, &desc.attrs)) {
return false;
}
desc.attrs &= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
desc.getter = PropertyStub;
desc.setter = StrictPropertyStub;
desc.shortid = 0;
}
*objp = obj;
return js_DefineNativeProperty(cx, obj, id, desc.value,
desc.getter, desc.setter, desc.attrs, propFlags,
desc.shortid, &prop);
}
static JSBool
resolver_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSObject **objp)
{
jsval v;
JS_ALWAYS_TRUE(JS_GetReservedSlot(cx, obj, 0, &v));
return CopyProperty(cx, obj, JSVAL_TO_OBJECT(v), id, flags, objp);
}
static JSBool
resolver_enumerate(JSContext *cx, JSObject *obj)
{
jsval v;
JS_ALWAYS_TRUE(JS_GetReservedSlot(cx, obj, 0, &v));
JSObject *referent = JSVAL_TO_OBJECT(v);
AutoIdArray ida(cx, JS_Enumerate(cx, referent));
bool ok = !!ida;
JSObject *ignore;
for (size_t i = 0; ok && i < ida.length(); i++)
ok = CopyProperty(cx, obj, referent, ida[i], JSRESOLVE_QUALIFIED, &ignore);
return ok;
}
static JSClass resolver_class = {
"resolver",
JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1),
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
resolver_enumerate, (JSResolveOp)resolver_resolve,
JS_ConvertStub, NULL,
JSCLASS_NO_OPTIONAL_MEMBERS
};
static JSBool
Resolver(JSContext *cx, uintN argc, jsval *vp)
{
JSObject *referent, *proto = NULL;
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "o/o", &referent, &proto))
return false;
JSObject *result = (argc > 1
? JS_NewObjectWithGivenProto
: JS_NewObject)(cx, &resolver_class, proto, JS_GetParent(cx, referent));
if (!result)
return false;
JS_ALWAYS_TRUE(JS_SetReservedSlot(cx, 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
* PRIntervalTime 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(PRIntervalTime t1, PRIntervalTime t2)
{
return int32(t1 - t2) < 0;
}
static JSBool
Sleep_fn(JSContext *cx, uintN argc, jsval *vp)
{
PRIntervalTime t_ticks;
if (argc == 0) {
t_ticks = 0;
} else {
jsdouble t_secs;
if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs))
return JS_FALSE;
/* NB: The next condition also filter out NaNs. */
if (!(t_secs <= MAX_TIMEOUT_INTERVAL)) {
JS_ReportError(cx, "Excessive sleep interval");
return JS_FALSE;
}
t_ticks = (t_secs <= 0.0)
? 0
: PRIntervalTime(PR_TicksPerSecond() * t_secs);
}
if (t_ticks == 0) {
JS_YieldRequest(cx);
} else {
JSAutoSuspendRequest suspended(cx);
PR_Lock(gWatchdogLock);
PRIntervalTime to_wakeup = PR_IntervalNow() + t_ticks;
for (;;) {
PR_WaitCondVar(gSleepWakeup, t_ticks);
if (gCanceled)
break;
PRIntervalTime now = PR_IntervalNow();
if (!IsBefore(now, to_wakeup))
break;
t_ticks = to_wakeup - now;
}
PR_Unlock(gWatchdogLock);
}
return !gCanceled;
}
typedef struct ScatterThreadData ScatterThreadData;
typedef struct ScatterData ScatterData;
typedef enum ScatterStatus {
SCATTER_WAIT,
SCATTER_GO,
SCATTER_CANCEL
} ScatterStatus;
struct ScatterData {
ScatterThreadData *threads;
jsval *results;
PRLock *lock;
PRCondVar *cvar;
ScatterStatus status;
};
struct ScatterThreadData {
jsint index;
ScatterData *shared;
PRThread *thr;
JSContext *cx;
jsval fn;
};
static void
DoScatteredWork(JSContext *cx, ScatterThreadData *td)
{
jsval *rval = &td->shared->results[td->index];
if (!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
*rval = JSVAL_VOID;
JS_GetPendingException(cx, rval);
JS_ClearPendingException(cx);
}
}
static void
RunScatterThread(void *arg)
{
int stackDummy;
ScatterThreadData *td;
ScatterStatus st;
JSContext *cx;
if (PR_FAILURE == PR_SetThreadPrivate(gStackBaseThreadIndex, &stackDummy))
return;
td = (ScatterThreadData *)arg;
cx = td->cx;
/* Wait for our signal. */
PR_Lock(td->shared->lock);
while ((st = td->shared->status) == SCATTER_WAIT)
PR_WaitCondVar(td->shared->cvar, PR_INTERVAL_NO_TIMEOUT);
PR_Unlock(td->shared->lock);
if (st == SCATTER_CANCEL)
return;
/* We are good to go. */
JS_SetContextThread(cx);
JS_SetNativeStackQuota(cx, gMaxStackSize);
JS_BeginRequest(cx);
DoScatteredWork(cx, td);
JS_EndRequest(cx);
JS_ClearContextThread(cx);
}
/*
* scatter(fnArray) - Call each function in `fnArray` without arguments, each
* in a different thread. When all threads have finished, return an array: the
* return values. Errors are not propagated; if any of the function calls
* fails, the corresponding element in the results array gets the exception
* object, if any, else (undefined).
*/
static JSBool
Scatter(JSContext *cx, uintN argc, jsval *vp)
{
jsuint i;
jsuint n; /* number of threads */
JSObject *inArr;
JSObject *arr;
JSObject *global;
ScatterData sd;
JSBool ok;
sd.lock = NULL;
sd.cvar = NULL;
sd.results = NULL;
sd.threads = NULL;
sd.status = SCATTER_WAIT;
if (argc == 0 || JSVAL_IS_PRIMITIVE(JS_ARGV(cx, vp)[0])) {
JS_ReportError(cx, "the first argument must be an object");
goto fail;
}
inArr = JSVAL_TO_OBJECT(JS_ARGV(cx, vp)[0]);
ok = JS_GetArrayLength(cx, inArr, &n);
if (!ok)
goto out;
if (n == 0)
goto success;
sd.lock = PR_NewLock();
if (!sd.lock)
goto fail;
sd.cvar = PR_NewCondVar(sd.lock);
if (!sd.cvar)
goto fail;
sd.results = (jsval *) malloc(n * sizeof(jsval));
if (!sd.results)
goto fail;
for (i = 0; i < n; i++) {
sd.results[i] = JSVAL_VOID;
ok = JS_AddValueRoot(cx, &sd.results[i]);
if (!ok) {
while (i-- > 0)
JS_RemoveValueRoot(cx, &sd.results[i]);
free(sd.results);
sd.results = NULL;
goto fail;
}
}
sd.threads = (ScatterThreadData *) malloc(n * sizeof(ScatterThreadData));
if (!sd.threads)
goto fail;
for (i = 0; i < n; i++) {
sd.threads[i].index = i;
sd.threads[i].shared = &sd;
sd.threads[i].thr = NULL;
sd.threads[i].cx = NULL;
sd.threads[i].fn = JSVAL_NULL;
ok = JS_AddValueRoot(cx, &sd.threads[i].fn);
if (ok && !JS_GetElement(cx, inArr, (jsint) i, &sd.threads[i].fn)) {
JS_RemoveValueRoot(cx, &sd.threads[i].fn);
ok = JS_FALSE;
}
if (!ok) {
while (i-- > 0)
JS_RemoveValueRoot(cx, &sd.threads[i].fn);
free(sd.threads);
sd.threads = NULL;
goto fail;
}
}
global = JS_GetGlobalObject(cx);
for (i = 1; i < n; i++) {
JSContext *newcx = NewContext(JS_GetRuntime(cx));
if (!newcx)
goto fail;
{
JSAutoRequest req(newcx);
JS_SetGlobalObject(newcx, global);
}
JS_ClearContextThread(newcx);
sd.threads[i].cx = newcx;
}
for (i = 1; i < n; i++) {
PRThread *t = PR_CreateThread(PR_USER_THREAD,
RunScatterThread,
&sd.threads[i],
PR_PRIORITY_NORMAL,
PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD,
0);
if (!t) {
/* Failed to start thread. */
PR_Lock(sd.lock);
sd.status = SCATTER_CANCEL;
PR_NotifyAllCondVar(sd.cvar);
PR_Unlock(sd.lock);
while (i-- > 1)
PR_JoinThread(sd.threads[i].thr);
goto fail;
}
sd.threads[i].thr = t;
}
PR_Lock(sd.lock);
sd.status = SCATTER_GO;
PR_NotifyAllCondVar(sd.cvar);
PR_Unlock(sd.lock);
DoScatteredWork(cx, &sd.threads[0]);
{
JSAutoSuspendRequest suspended(cx);
for (i = 1; i < n; i++) {
PR_JoinThread(sd.threads[i].thr);
}
}
success:
arr = JS_NewArrayObject(cx, n, sd.results);
if (!arr)
goto fail;
*vp = OBJECT_TO_JSVAL(arr);
ok = JS_TRUE;
out:
if (sd.threads) {
JSContext *acx;
for (i = 0; i < n; i++) {
JS_RemoveValueRoot(cx, &sd.threads[i].fn);
acx = sd.threads[i].cx;
if (acx) {
JS_SetContextThread(acx);
DestroyContext(acx, true);
}
}
free(sd.threads);
}
if (sd.results) {
for (i = 0; i < n; i++)
JS_RemoveValueRoot(cx, &sd.results[i]);
free(sd.results);
}
if (sd.cvar)
PR_DestroyCondVar(sd.cvar);
if (sd.lock)
PR_DestroyLock(sd.lock);
return ok;
fail:
ok = JS_FALSE;
goto out;
}
static bool
InitWatchdog(JSRuntime *rt)
{
JS_ASSERT(!gWatchdogThread);
gWatchdogLock = PR_NewLock();
if (gWatchdogLock) {
gWatchdogWakeup = PR_NewCondVar(gWatchdogLock);
if (gWatchdogWakeup) {
gSleepWakeup = PR_NewCondVar(gWatchdogLock);
if (gSleepWakeup)
return true;
PR_DestroyCondVar(gWatchdogWakeup);
}
PR_DestroyLock(gWatchdogLock);
}
return false;
}
static void
KillWatchdog()
{
PRThread *thread;
PR_Lock(gWatchdogLock);
thread = gWatchdogThread;
if (thread) {
/*
* The watchdog thread is running, tell it to terminate waking it up
* if necessary.
*/
gWatchdogThread = NULL;
PR_NotifyCondVar(gWatchdogWakeup);
}
PR_Unlock(gWatchdogLock);
if (thread)
PR_JoinThread(thread);
PR_DestroyCondVar(gSleepWakeup);
PR_DestroyCondVar(gWatchdogWakeup);
PR_DestroyLock(gWatchdogLock);
}
static void
WatchdogMain(void *arg)
{
JSRuntime *rt = (JSRuntime *) arg;
PR_Lock(gWatchdogLock);
while (gWatchdogThread) {
PRIntervalTime now = PR_IntervalNow();
if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) {
/*
* The timeout has just expired. Trigger the operation callback
* outside the lock.
*/
gWatchdogHasTimeout = false;
PR_Unlock(gWatchdogLock);
CancelExecution(rt);
PR_Lock(gWatchdogLock);
/* Wake up any threads doing sleep. */
PR_NotifyAllCondVar(gSleepWakeup);
} else {
PRIntervalTime sleepDuration = gWatchdogHasTimeout
? gWatchdogTimeout - now
: PR_INTERVAL_NO_TIMEOUT;
#ifdef DEBUG
PRStatus status =
#endif
PR_WaitCondVar(gWatchdogWakeup, sleepDuration);
JS_ASSERT(status == PR_SUCCESS);
}
}
PR_Unlock(gWatchdogLock);
}
static bool
ScheduleWatchdog(JSRuntime *rt, jsdouble t)
{
if (t <= 0) {
PR_Lock(gWatchdogLock);
gWatchdogHasTimeout = false;
PR_Unlock(gWatchdogLock);
return true;
}
PRIntervalTime interval = PRIntervalTime(ceil(t * PR_TicksPerSecond()));
PRIntervalTime timeout = PR_IntervalNow() + interval;
PR_Lock(gWatchdogLock);
if (!gWatchdogThread) {
JS_ASSERT(!gWatchdogHasTimeout);
gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
WatchdogMain,
rt,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_JOINABLE_THREAD,
0);
if (!gWatchdogThread) {
PR_Unlock(gWatchdogLock);
return false;
}
} else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) {
PR_NotifyCondVar(gWatchdogWakeup);
}
gWatchdogHasTimeout = true;
gWatchdogTimeout = timeout;
PR_Unlock(gWatchdogLock);
return true;
}
#else /* !JS_THREADSAFE */
#ifdef XP_WIN
static HANDLE gTimerHandle = 0;
VOID CALLBACK
TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
CancelExecution((JSRuntime *) lpParameter);
}
#else
static void
AlarmHandler(int sig)
{
CancelExecution(gRuntime);
}
#endif
static bool
InitWatchdog(JSRuntime *rt)
{
gRuntime = rt;
return true;
}
static void
KillWatchdog()
{
ScheduleWatchdog(gRuntime, -1);
}
static bool
ScheduleWatchdog(JSRuntime *rt, jsdouble t)
{
#ifdef XP_WIN
if (gTimerHandle) {
DeleteTimerQueueTimer(NULL, gTimerHandle, NULL);
gTimerHandle = 0;
}
if (t > 0 &&
!CreateTimerQueueTimer(&gTimerHandle,
NULL,
(WAITORTIMERCALLBACK)TimerCallback,
rt,
DWORD(ceil(t * 1000.0)),
0,
WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) {
gTimerHandle = 0;
return false;
}
#else
/* FIXME: use setitimer when available for sub-second resolution. */
if (t <= 0) {
alarm(0);
signal(SIGALRM, NULL);
} else {
signal(SIGALRM, AlarmHandler); /* set the Alarm signal capture */
alarm(ceil(t));
}
#endif
return true;
}
#endif /* !JS_THREADSAFE */
static void
CancelExecution(JSRuntime *rt)
{
gCanceled = true;
if (gExitCode == 0)
gExitCode = EXITCODE_TIMEOUT;
#ifdef JS_THREADSAFE
if (gWorkerThreadPool)
js::workers::terminateAll(rt, gWorkerThreadPool);
#endif
JS_TriggerAllOperationCallbacks(rt);
static const char msg[] = "Script runs for too long, terminating.\n";
#if defined(XP_UNIX) && !defined(JS_THREADSAFE)
/* It is not safe to call fputs from signals. */
/* Dummy assignment avoids GCC warning on "attribute warn_unused_result" */
ssize_t dummy = write(2, msg, sizeof(msg) - 1);
(void)dummy;
#else
fputs(msg, stderr);
#endif
}
static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t)
{
/* NB: The next condition also filter out NaNs. */
if (!(t <= MAX_TIMEOUT_INTERVAL)) {
JS_ReportError(cx, "Excessive timeout value");
return JS_FALSE;
}
gTimeoutInterval = t;
if (!ScheduleWatchdog(cx->runtime, t)) {
JS_ReportError(cx, "Failed to create the watchdog");
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
Timeout(JSContext *cx, uintN argc, jsval *vp)
{
if (argc == 0)
return JS_NewNumberValue(cx, gTimeoutInterval, vp);
if (argc > 1) {
JS_ReportError(cx, "Wrong number of arguments");
return JS_FALSE;
}
jsdouble t;
if (!JS_ValueToNumber(cx, JS_ARGV(cx, vp)[0], &t))
return JS_FALSE;
*vp = JSVAL_VOID;
return SetTimeoutValue(cx, t);
}
static JSBool
Elapsed(JSContext *cx, uintN argc, jsval *vp)
{
if (argc == 0) {
double d = 0.0;
JSShellContextData *data = GetContextData(cx);
if (data)
d = js_IntervalNow() - data->startTime;
return JS_NewNumberValue(cx, d, vp);
}
JS_ReportError(cx, "Wrong number of arguments");
return JS_FALSE;
}
static JSBool
Parent(JSContext *cx, uintN argc, jsval *vp)
{
if (argc != 1) {
JS_ReportError(cx, "Wrong number of arguments");
return JS_FALSE;
}
jsval v = JS_ARGV(cx, vp)[0];
if (JSVAL_IS_PRIMITIVE(v)) {
JS_ReportError(cx, "Only objects have parents!");
return JS_FALSE;
}
JSObject *parent = JS_GetParent(cx, JSVAL_TO_OBJECT(v));
*vp = OBJECT_TO_JSVAL(parent);
/* Outerize if necessary. Embrace the ugliness! */
if (parent) {
if (JSObjectOp op = parent->getClass()->ext.outerObject)
*vp = OBJECT_TO_JSVAL(op(cx, parent));
}
return JS_TRUE;
}
#ifdef XP_UNIX
#include <fcntl.h>
#include <sys/stat.h>
/*
* Returns a JS_malloc'd string (that the caller needs to JS_free)
* containing the directory (non-leaf) part of |from| prepended to |leaf|.
* If |from| is empty or a leaf, MakeAbsolutePathname returns a copy of leaf.
* Returns NULL to indicate an error.
*/
static char *
MakeAbsolutePathname(JSContext *cx, const char *from, const char *leaf)
{
size_t dirlen;
char *dir;
const char *slash = NULL, *cp;
cp = from;
while (*cp) {
if (*cp == '/') {
slash = cp;
}
++cp;
}
if (!slash) {
/* We were given a leaf or |from| was empty. */
return JS_strdup(cx, leaf);
}
/* Else, we were given a real pathname, return that + the leaf. */
dirlen = slash - from + 1;
dir = (char*) JS_malloc(cx, dirlen + strlen(leaf) + 1);
if (!dir)
return NULL;
strncpy(dir, from, dirlen);
strcpy(dir + dirlen, leaf); /* Note: we can't use strcat here. */
return dir;
}
#endif // XP_UNIX
static JSBool
Compile(JSContext *cx, uintN argc, jsval *vp)
{
if (argc < 1) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
"compile", "0", "s");
return JS_FALSE;
}
jsval arg0 = JS_ARGV(cx, vp)[0];
if (!JSVAL_IS_STRING(arg0)) {
const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, arg0));
JS_ReportError(cx, "expected string to compile, got %s", typeName);
return JS_FALSE;
}
JSString *scriptContents = JSVAL_TO_STRING(arg0);
if (!JS_CompileUCScript(cx, NULL, JS_GetStringCharsZ(cx, scriptContents),
JS_GetStringLength(scriptContents), "<string>", 0)) {
return false;
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
Parse(JSContext *cx, uintN argc, jsval *vp)
{
if (argc < 1) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
"compile", "0", "s");
return JS_FALSE;
}
jsval arg0 = JS_ARGV(cx, vp)[0];
if (!JSVAL_IS_STRING(arg0)) {
const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, arg0));
JS_ReportError(cx, "expected string to parse, got %s", typeName);
return JS_FALSE;
}
JSString *scriptContents = JSVAL_TO_STRING(arg0);
js::Parser parser(cx);
parser.init(JS_GetStringCharsZ(cx, scriptContents), JS_GetStringLength(scriptContents),
"<string>", 0, cx->findVersion());
if (!parser.parse(NULL))
return JS_FALSE;
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSBool
Snarf(JSContext *cx, uintN argc, jsval *vp)
{
JSString *str;
const char *pathname;
JSStackFrame *fp;
if (!argc)
return JS_FALSE;
str = JS_ValueToString(cx, JS_ARGV(cx, vp)[0]);
if (!str)
return JS_FALSE;
JSAutoByteString filename(cx, str);
if (!filename)
return JS_FALSE;
/* Get the currently executing script's name. */
fp = JS_GetScriptedCaller(cx, NULL);
JS_ASSERT(fp && fp->script()->filename);
#ifdef XP_UNIX
pathname = MakeAbsolutePathname(cx, fp->script()->filename, filename.ptr());
if (!pathname)
return JS_FALSE;
#else
pathname = filename.ptr();
#endif
str = FileAsString(cx, pathname);
#ifdef XP_UNIX
JS_free(cx, (void*)pathname);
#endif
if (!str)
return JS_FALSE;
*vp = STRING_TO_JSVAL(str);
return JS_TRUE;
}
JSBool
Wrap(JSContext *cx, uintN argc, jsval *vp)
{
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
if (JSVAL_IS_PRIMITIVE(v)) {
JS_SET_RVAL(cx, vp, v);
return true;
}
JSObject *obj = JSVAL_TO_OBJECT(v);
JSObject *wrapped = JSWrapper::New(cx, obj, obj->getProto(), obj->getGlobal(),
&JSWrapper::singleton);
if (!wrapped)
return false;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(wrapped));
return true;
}
JSBool
Serialize(JSContext *cx, uintN argc, jsval *vp)
{
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
uint64 *datap;
size_t nbytes;
if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL))
return false;
JSObject *arrayobj = js_CreateTypedArray(cx, TypedArray::TYPE_UINT8, nbytes);
if (!arrayobj) {
JS_free(cx, datap);
return false;
}
TypedArray *array = TypedArray::fromJSObject(arrayobj);
JS_ASSERT((uintptr_t(array->data) & 7) == 0);
memcpy(array->data, datap, nbytes);
JS_free(cx, datap);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(arrayobj));
return true;
}
JSBool
Deserialize(JSContext *cx, uintN argc, jsval *vp)
{
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
JSObject *obj;
if (JSVAL_IS_PRIMITIVE(v) || !js_IsTypedArray((obj = JSVAL_TO_OBJECT(v)))) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "deserialize");
return false;
}
TypedArray *array = TypedArray::fromJSObject(obj);
if ((array->byteLength & 7) != 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "deserialize");
return false;
}
if ((uintptr_t(array->data) & 7) != 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_BAD_ALIGNMENT);
return false;
}
if (!JS_ReadStructuredClone(cx, (uint64 *) array->data, array->byteLength,
JS_STRUCTURED_CLONE_VERSION, &v, NULL, NULL)) {
return false;
}
JS_SET_RVAL(cx, vp, v);
return true;
}
JSBool
MJitStats(JSContext *cx, uintN argc, jsval *vp)
{
#ifdef JS_METHODJIT
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->mjitMemoryUsed));
#else
JS_SET_RVAL(cx, vp, JSVAL_VOID);
#endif
return true;
}
JSBool
StringStats(JSContext *cx, uintN argc, jsval *vp)
{
JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->stringMemoryUsed));
return true;
}
enum CompartmentKind { SAME_COMPARTMENT, NEW_COMPARTMENT };
static JSObject *
NewGlobalObject(JSContext *cx, CompartmentKind compartment);
JSBool
NewGlobal(JSContext *cx, uintN argc, jsval *vp)
{
if (argc != 1 || !JSVAL_IS_STRING(JS_ARGV(cx, vp)[0])) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "newGlobal");
return false;
}
JSString *str = JSVAL_TO_STRING(JS_ARGV(cx, vp)[0]);
JSBool equalSame = JS_FALSE, equalNew = JS_FALSE;
if (!JS_StringEqualsAscii(cx, str, "same-compartment", &equalSame) ||
!JS_StringEqualsAscii(cx, str, "new-compartment", &equalNew)) {
return false;
}
if (!equalSame && !equalNew) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "newGlobal");
return false;
}
JSObject *global = NewGlobalObject(cx, equalSame ? SAME_COMPARTMENT : NEW_COMPARTMENT);
if (!global)
return false;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(global));
return true;
}
static JSFunctionSpec shell_functions[] = {
JS_FN("version", Version, 0,0),
JS_FN("revertVersion", RevertVersion, 0,0),
JS_FN("options", Options, 0,0),
JS_FN("load", Load, 1,0),
JS_FN("evaluate", Evaluate, 1,0),
JS_FN("run", Run, 1,0),
JS_FN("readline", ReadLine, 0,0),
JS_FN("print", Print, 0,0),
JS_FN("putstr", PutStr, 0,0),
JS_FN("dateNow", Now, 0,0),
JS_FN("help", Help, 0,0),
JS_FN("quit", Quit, 0,0),
JS_FN("assertEq", AssertEq, 2,0),
JS_FN("assertJit", AssertJit, 0,0),
JS_FN("gc", ::GC, 0,0),
#ifdef JS_GCMETER
JS_FN("gcstats", GCStats, 0,0),
#endif
JS_FN("gcparam", GCParameter, 2,0),
JS_FN("countHeap", CountHeap, 0,0),
JS_FN("makeFinalizeObserver", MakeFinalizeObserver, 0,0),
JS_FN("finalizeCount", FinalizeCount, 0,0),
#ifdef JS_GC_ZEAL
JS_FN("gczeal", GCZeal, 1,0),
#endif
JS_FN("setDebug", SetDebug, 1,0),
JS_FN("setDebuggerHandler", SetDebuggerHandler, 1,0),
JS_FN("setThrowHook", SetThrowHook, 1,0),
JS_FN("trap", Trap, 3,0),
JS_FN("untrap", Untrap, 2,0),
JS_FN("line2pc", LineToPC, 0,0),
JS_FN("pc2line", PCToLine, 0,0),
JS_FN("stackQuota", StackQuota, 0,0),
JS_FN("stringsAreUTF8", StringsAreUTF8, 0,0),
JS_FN("testUTF8", TestUTF8, 1,0),
JS_FN("throwError", ThrowError, 0,0),
#ifdef DEBUG
JS_FN("disassemble", DisassembleToString, 1,0),
JS_FN("dis", Disassemble, 1,0),
JS_FN("disfile", DisassFile, 1,0),
JS_FN("dissrc", DisassWithSrc, 1,0),
JS_FN("dumpHeap", DumpHeap, 0,0),
JS_FN("dumpObject", DumpObject, 1,0),
JS_FN("notes", Notes, 1,0),
JS_FN("tracing", Tracing, 0,0),
JS_FN("stats", DumpStats, 1,0),
#endif
#ifdef TEST_CVTARGS
JS_FN("cvtargs", ConvertArgs, 0,0),
#endif
JS_FN("build", BuildDate, 0,0),
JS_FN("clear", Clear, 0,0),
JS_FN("intern", Intern, 1,0),
JS_FN("clone", Clone, 1,0),
JS_FN("getpda", GetPDA, 1,0),
JS_FN("getslx", GetSLX, 1,0),
JS_FN("toint32", ToInt32, 1,0),
JS_FN("evalcx", EvalInContext, 1,0),
JS_FN("evalInFrame", EvalInFrame, 2,0),
JS_FN("shapeOf", ShapeOf, 1,0),
JS_FN("resolver", Resolver, 1,0),
#ifdef MOZ_CALLGRIND
JS_FN("startCallgrind", js_StartCallgrind, 0,0),
JS_FN("stopCallgrind", js_StopCallgrind, 0,0),
JS_FN("dumpCallgrind", js_DumpCallgrind, 1,0),
#endif
#ifdef MOZ_VTUNE
JS_FN("startVtune", js_StartVtune, 1,0),
JS_FN("stopVtune", js_StopVtune, 0,0),
JS_FN("pauseVtune", js_PauseVtune, 0,0),
JS_FN("resumeVtune", js_ResumeVtune, 0,0),
#endif
#ifdef MOZ_TRACEVIS
JS_FN("startTraceVis", StartTraceVisNative, 1,0),
JS_FN("stopTraceVis", StopTraceVisNative, 0,0),
#endif
#ifdef DEBUG
JS_FN("arrayInfo", js_ArrayInfo, 1,0),
#endif
#ifdef JS_THREADSAFE
JS_FN("sleep", Sleep_fn, 1,0),
JS_FN("scatter", Scatter, 1,0),
#endif
JS_FN("snarf", Snarf, 0,0),
JS_FN("read", Snarf, 0,0),
JS_FN("compile", Compile, 1,0),
JS_FN("parse", Parse, 1,0),
JS_FN("timeout", Timeout, 1,0),
JS_FN("elapsed", Elapsed, 0,0),
JS_FN("parent", Parent, 1,0),
JS_FN("wrap", Wrap, 1,0),
JS_FN("serialize", Serialize, 1,0),
JS_FN("deserialize", Deserialize, 1,0),
#ifdef JS_METHODJIT
JS_FN("mjitstats", MJitStats, 0,0),
#endif
JS_FN("stringstats", StringStats, 0,0),
JS_FN("newGlobal", NewGlobal, 1,0),
JS_FS_END
};
static const char shell_help_header[] =
"Command Description\n"
"======= ===========\n";
static const char *const shell_help_messages[] = {
"version([number]) Get or force a script compilation version number",
"revertVersion() Revert previously set version number",
"options([option ...]) Get or toggle JavaScript options",
"load(['foo.js' ...]) Load files named by string arguments",
"evaluate(code) Evaluate code as though it were the contents of a file",
"run('foo.js')\n"
" Run the file named by the first argument, returning the number of\n"
" of milliseconds spent compiling and executing it",
"readline() Read a single line from stdin",
"print([exp ...]) Evaluate and print expressions",
"putstr([exp]) Evaluate and print expression without newline",
"dateNow() Return the current time with sub-ms precision",
"help([name ...]) Display usage and help messages",
"quit() Quit the shell",
"assertEq(actual, expected[, msg])\n"
" Throw if the first two arguments are not the same (both +0 or both -0,\n"
" both NaN, or non-zero and ===)",
"assertJit() Throw if the calling function failed to JIT",
"gc() Run the garbage collector",
#ifdef JS_GCMETER
"gcstats() Print garbage collector statistics",
#endif
"gcparam(name, value)\n"
" Wrapper for JS_SetGCParameter. The name must be either 'maxBytes' or\n"
" 'maxMallocBytes' and the value must be convertable to a positive uint32",
"countHeap([start[, kind]])\n"
" Count the number of live GC things in the heap or things reachable from\n"
" start when it is given and is not null. kind is either 'all' (default) to\n"
" count all things or one of 'object', 'double', 'string', 'function',\n"
" 'qname', 'namespace', 'xml' to count only things of that kind",
"makeFinalizeObserver()\n"
" get a special object whose finalization increases the counter returned\n"
" by the finalizeCount function",
"finalizeCount()\n"
" return the current value of the finalization counter that is incremented\n"
" each time an object returned by the makeFinalizeObserver is finalized",
#ifdef JS_GC_ZEAL
"gczeal(level) How zealous the garbage collector should be",
#endif
"setDebug(debug) Set debug mode",
"setDebuggerHandler(f) Set handler for debugger keyword to f",
"setThrowHook(f) Set throw hook to f",
"trap([fun, [pc,]] exp) Trap bytecode execution",
"untrap(fun[, pc]) Remove a trap",
"line2pc([fun,] line) Map line number to PC",
"pc2line(fun[, pc]) Map PC to line number",
"stackQuota([number]) Query/set script stack quota",
"stringsAreUTF8() Check if strings are UTF-8 encoded",
"testUTF8(mode) Perform UTF-8 tests (modes are 1 to 4)",
"throwError() Throw an error from JS_ReportError",
#ifdef DEBUG
"disassemble([fun]) Return the disassembly for the given function",
"dis([fun]) Disassemble functions into bytecodes",
"disfile('foo.js') Disassemble script file into bytecodes\n"
" dis and disfile take these options as preceeding string arguments\n"
" \"-r\" (disassemble recursively)\n"
" \"-l\" (show line numbers)",
"dissrc([fun]) Disassemble functions with source lines",
"dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])\n"
" Interface to JS_DumpHeap with output sent to file",
"dumpObject() Dump an internal representation of an object",
"notes([fun]) Show source notes for functions",
"tracing([true|false|filename]) Turn bytecode execution tracing on/off.\n"
" With filename, send to file.",
"stats([string ...]) Dump 'arena', 'atom', 'global' stats",
#endif
#ifdef TEST_CVTARGS
"cvtargs(arg1..., arg12) Test argument formatter",
#endif
"build() Show build date and time",
"clear([obj]) Clear properties of object",
"intern(str) Internalize str in the atom table",
"clone(fun[, scope]) Clone function object",
"getpda(obj) Get the property descriptors for obj",
"getslx(obj) Get script line extent",
"toint32(n) Testing hook for JS_ValueToInt32",
"evalcx(s[, o])\n"
" Evaluate s in optional sandbox object o\n"
" if (s == '' && !o) return new o with eager standard classes\n"
" if (s == 'lazy' && !o) return new o with lazy standard classes\n"
" if (s == 'split' && !o) return new split-object o with lazy standard classes",
"evalInFrame(n,str,save) Evaluate 'str' in the nth up frame.\n"
" If 'save' (default false), save the frame chain",
"shapeOf(obj) Get the shape of obj (an implementation detail)",
"resolver(src[, proto]) Create object with resolve hook that copies properties\n"
" from src. If proto is omitted, use Object.prototype.",
#ifdef MOZ_CALLGRIND
"startCallgrind() Start callgrind instrumentation",
"stopCallgrind() Stop callgrind instrumentation",
"dumpCallgrind([name]) Dump callgrind counters",
#endif
#ifdef MOZ_VTUNE
"startVtune([filename]) Start vtune instrumentation",
"stopVtune() Stop vtune instrumentation",
"pauseVtune() Pause vtune collection",
"resumeVtune() Resume vtune collection",
#endif
#ifdef MOZ_TRACEVIS
"startTraceVis(filename) Start TraceVis recording (stops any current recording)",
"stopTraceVis() Stop TraceVis recording",
#endif
#ifdef DEBUG
"arrayInfo(a1, a2, ...) Report statistics about arrays",
#endif
#ifdef JS_THREADSAFE
"sleep(dt) Sleep for dt seconds",
"scatter(fns) Call functions concurrently (ignoring errors)",
#endif
"snarf(filename) Read filename into returned string",
"read(filename) Synonym for snarf",
"compile(code) Compiles a string to bytecode, potentially throwing",
"parse(code) Parses a string, potentially throwing",
"timeout([seconds])\n"
" Get/Set the limit in seconds for the execution time for the current context.\n"
" A negative value (default) means that the execution time is unlimited.",
"elapsed() Execution time elapsed for the current context.",
"parent(obj) Returns the parent of obj.",
"wrap(obj) Wrap an object into a noop wrapper.",
"serialize(sd) Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.",
"deserialize(a) Deserialize data generated by serialize.",
#ifdef JS_METHODJIT
"mjitstats() Return stats on mjit memory usage.",
#endif
"stringstats() Return stats on string memory usage.",
"newGlobal(kind) Return a new global object, in the current\n"
" compartment if kind === 'same-compartment' or in a\n"
" new compartment if kind === 'new-compartment'",
/* Keep these last: see the static assertion below. */
#ifdef MOZ_PROFILING
"startProfiling() Start a profiling session.\n"
" Profiler must be running with programatic sampling",
"stopProfiling() Stop a running profiling session\n"
#endif
};
#ifdef MOZ_PROFILING
#define PROFILING_FUNCTION_COUNT 2
#else
#define PROFILING_FUNCTION_COUNT 0
#endif
/* Help messages must match shell functions. */
JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) - PROFILING_FUNCTION_COUNT ==
JS_ARRAY_LENGTH(shell_functions) - 1 /* JS_FS_END */);
#ifdef DEBUG
static void
CheckHelpMessages()
{
const char *const *m;
const char *lp;
/* Messages begin with "function_name(" prefix and don't end with \n. */
for (m = shell_help_messages; m != JS_ARRAY_END(shell_help_messages) - PROFILING_FUNCTION_COUNT; ++m) {
lp = strchr(*m, '(');
JS_ASSERT(lp);
JS_ASSERT(memcmp(shell_functions[m - shell_help_messages].name,
*m, lp - *m) == 0);
JS_ASSERT((*m)[strlen(*m) - 1] != '\n');
}
}
#else
# define CheckHelpMessages() ((void) 0)
#endif
#undef PROFILING_FUNCTION_COUNT
static JSBool
Help(JSContext *cx, uintN argc, jsval *vp)
{
uintN i, j;
int did_header, did_something;
JSType type;
JSFunction *fun;
JSString *str;
fprintf(gOutFile, "%s\n", JS_GetImplementationVersion());
if (argc == 0) {
fputs(shell_help_header, gOutFile);
for (i = 0; shell_functions[i].name; i++)
fprintf(gOutFile, "%s\n", shell_help_messages[i]);
} else {
did_header = 0;
jsval *argv = JS_ARGV(cx, vp);
for (i = 0; i < argc; i++) {
did_something = 0;
type = JS_TypeOfValue(cx, argv[i]);
if (type == JSTYPE_FUNCTION) {
fun = JS_ValueToFunction(cx, argv[i]);
str = fun->atom;
} else if (type == JSTYPE_STRING) {
str = JSVAL_TO_STRING(argv[i]);
} else {
str = NULL;
}
if (str) {
JSFlatString *flatStr = JS_FlattenString(cx, str);
if (!flatStr)
return JS_FALSE;
for (j = 0; shell_functions[j].name; j++) {
if (JS_FlatStringEqualsAscii(flatStr, shell_functions[j].name)) {
if (!did_header) {
did_header = 1;
fputs(shell_help_header, gOutFile);
}
did_something = 1;
fprintf(gOutFile, "%s\n", shell_help_messages[j]);
break;
}
}
}
if (!did_something) {
str = JS_ValueToString(cx, argv[i]);
if (!str)
return JS_FALSE;
fputs("Sorry, no help for ", gErrFile);
JS_FileEscapedString(gErrFile, str, 0);
putc('\n', gErrFile);
}
}
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
static JSObject *
split_setup(JSContext *cx, JSBool evalcx)
{
JSObject *outer, *inner, *arguments;
outer = split_create_outer(cx);
if (!outer)
return NULL;
AutoObjectRooter root(cx, outer);
if (!evalcx)
JS_SetGlobalObject(cx, outer);
inner = split_create_inner(cx, outer);
if (!inner)
return NULL;
if (!evalcx) {
if (!JS_DefineFunctions(cx, inner, shell_functions) ||
!JS_DefineProfilingFunctions(cx, inner)) {
return NULL;
}
/* Create a dummy arguments object. */
arguments = JS_NewArrayObject(cx, 0, NULL);
if (!arguments ||
!JS_DefineProperty(cx, inner, "arguments", OBJECT_TO_JSVAL(arguments),
NULL, NULL, 0)) {
return NULL;
}
}
JS_ClearScope(cx, outer);
#ifndef LAZY_STANDARD_CLASSES
if (!JS_InitStandardClasses(cx, inner))
return NULL;
#endif
return inner;
}
/*
* Define a JS object called "it". Give it class operations that printf why
* they're being called for tutorial purposes.
*/
enum its_tinyid {
ITS_COLOR, ITS_HEIGHT, ITS_WIDTH, ITS_FUNNY, ITS_ARRAY, ITS_RDONLY,
ITS_CUSTOM, ITS_CUSTOMRDONLY
};
static JSBool
its_getter(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
jsval *val = (jsval *) JS_GetPrivate(cx, obj);
*vp = val ? *val : JSVAL_VOID;
return JS_TRUE;
}
static JSBool
its_setter(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
{
jsval *val = (jsval *) JS_GetPrivate(cx, obj);
if (val) {
*val = *vp;
return JS_TRUE;
}
val = new jsval;
if (!val) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
if (!JS_AddValueRoot(cx, val)) {
delete val;
return JS_FALSE;
}
if (!JS_SetPrivate(cx, obj, (void*)val)) {
JS_RemoveValueRoot(cx, val);
delete val;
return JS_FALSE;
}
*val = *vp;
return JS_TRUE;
}
static JSPropertySpec its_props[] = {
{"color", ITS_COLOR, JSPROP_ENUMERATE, NULL, NULL},
{"height", ITS_HEIGHT, JSPROP_ENUMERATE, NULL, NULL},
{"width", ITS_WIDTH, JSPROP_ENUMERATE, NULL, NULL},
{"funny", ITS_FUNNY, JSPROP_ENUMERATE, NULL, NULL},
{"array", ITS_ARRAY, JSPROP_ENUMERATE, NULL, NULL},
{"rdonly", ITS_RDONLY, JSPROP_READONLY, NULL, NULL},
{"custom", ITS_CUSTOM, JSPROP_ENUMERATE,
its_getter, its_setter},
{"customRdOnly", ITS_CUSTOMRDONLY, JSPROP_ENUMERATE | JSPROP_READONLY,
its_getter, its_setter},
{NULL,0,0,NULL,NULL}
};
static JSBool
its_bindMethod(JSContext *cx, uintN argc, jsval *vp)
{
JSString *name;
JSObject *method;
JSObject *thisobj = JS_THIS_OBJECT(cx, vp);
if (!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "So", &name, &method))
return JS_FALSE;
*vp = OBJECT_TO_JSVAL(method);
if (JS_TypeOfValue(cx, *vp) != JSTYPE_FUNCTION) {
JSAutoByteString nameBytes(cx, name);
if (!!nameBytes) {
JSString *valstr = JS_ValueToString(cx, *vp);
if (valstr) {
JSAutoByteString valBytes(cx, valstr);
if (!!valBytes) {
JS_ReportError(cx, "can't bind method %s to non-callable object %s",
nameBytes.ptr(), valBytes.ptr());
}
}
}
return JS_FALSE;
}
jsid id;
if (!JS_ValueToId(cx, STRING_TO_JSVAL(name), &id))
return JS_FALSE;
if (!JS_DefinePropertyById(cx, thisobj, id, *vp, NULL, NULL, JSPROP_ENUMERATE))
return JS_FALSE;
return JS_SetParent(cx, method, thisobj);
}
static JSFunctionSpec its_methods[] = {
{"bindMethod", its_bindMethod, 2,0},
{NULL,NULL,0,0}
};
#ifdef JSD_LOWLEVEL_SOURCE
/*
* This facilitates sending source to JSD (the debugger system) in the shell
* where the source is loaded using the JSFILE hack in jsscan. The function
* below is used as a callback for the jsdbgapi JS_SetSourceHandler hook.
* A more normal embedding (e.g. mozilla) loads source itself and can send
* source directly to JSD without using this hook scheme.
*/
static void
SendSourceToJSDebugger(const char *filename, uintN lineno,
jschar *str, size_t length,
void **listenerTSData, JSDContext* jsdc)
{
JSDSourceText *jsdsrc = (JSDSourceText *) *listenerTSData;
if (!jsdsrc) {
if (!filename)
filename = "typein";
if (1 == lineno) {
jsdsrc = JSD_NewSourceText(jsdc, filename);
} else {
jsdsrc = JSD_FindSourceForURL(jsdc, filename);
if (jsdsrc && JSD_SOURCE_PARTIAL !=
JSD_GetSourceStatus(jsdc, jsdsrc)) {
jsdsrc = NULL;
}
}
}
if (jsdsrc) {
jsdsrc = JSD_AppendUCSourceText(jsdc,jsdsrc, str, length,
JSD_SOURCE_PARTIAL);
}
*listenerTSData = jsdsrc;
}
#endif /* JSD_LOWLEVEL_SOURCE */
static JSBool its_noisy; /* whether to be noisy when finalizing it */
static JSBool its_enum_fail;/* whether to fail when enumerating it */
static JSBool
its_addProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
if (!its_noisy)
return JS_TRUE;
IdToString idString(cx, id);
fprintf(gOutFile, "adding its property %s,", idString.getBytes());
ToString valueString(cx, *vp);
fprintf(gOutFile, " initial value %s\n", valueString.getBytes());
return JS_TRUE;
}
static JSBool
its_delProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
if (!its_noisy)
return JS_TRUE;
IdToString idString(cx, id);
fprintf(gOutFile, "deleting its property %s,", idString.getBytes());
ToString valueString(cx, *vp);
fprintf(gOutFile, " initial value %s\n", valueString.getBytes());
return JS_TRUE;
}
static JSBool
its_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
if (!its_noisy)
return JS_TRUE;
IdToString idString(cx, id);
fprintf(gOutFile, "getting its property %s,", idString.getBytes());
ToString valueString(cx, *vp);
fprintf(gOutFile, " initial value %s\n", valueString.getBytes());
return JS_TRUE;
}
static JSBool
its_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
{
IdToString idString(cx, id);
if (its_noisy) {
fprintf(gOutFile, "setting its property %s,", idString.getBytes());
ToString valueString(cx, *vp);
fprintf(gOutFile, " new value %s\n", valueString.getBytes());
}
if (!JSID_IS_ATOM(id))
return JS_TRUE;
if (!strcmp(idString.getBytes(), "noisy"))
JS_ValueToBoolean(cx, *vp, &its_noisy);
else if (!strcmp(idString.getBytes(), "enum_fail"))
JS_ValueToBoolean(cx, *vp, &its_enum_fail);
return JS_TRUE;
}
/*
* Its enumerator, implemented using the "new" enumerate API,
* see class flags.
*/
static JSBool
its_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
jsval *statep, jsid *idp)
{
JSObject *iterator;
switch (enum_op) {
case JSENUMERATE_INIT:
case JSENUMERATE_INIT_ALL:
if (its_noisy)
fprintf(gOutFile, "enumerate its properties\n");
iterator = JS_NewPropertyIterator(cx, obj);
if (!iterator)
return JS_FALSE;
*statep = OBJECT_TO_JSVAL(iterator);
if (idp)
*idp = INT_TO_JSID(0);
break;
case JSENUMERATE_NEXT:
if (its_enum_fail) {
JS_ReportError(cx, "its enumeration failed");
return JS_FALSE;
}
iterator = (JSObject *) JSVAL_TO_OBJECT(*statep);
if (!JS_NextProperty(cx, iterator, idp))
return JS_FALSE;
if (!JSID_IS_VOID(*idp))
break;
/* Fall through. */
case JSENUMERATE_DESTROY:
/* Allow our iterator object to be GC'd. */
*statep = JSVAL_NULL;
break;
}
return JS_TRUE;
}
static JSBool
its_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
JSObject **objp)
{
if (its_noisy) {
IdToString idString(cx, id);
fprintf(gOutFile, "resolving its property %s, flags {%s,%s,%s}\n",
idString.getBytes(),
(flags & JSRESOLVE_QUALIFIED) ? "qualified" : "",
(flags & JSRESOLVE_ASSIGNING) ? "assigning" : "",
(flags & JSRESOLVE_DETECTING) ? "detecting" : "");
}
return JS_TRUE;
}
static JSBool
its_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
{
if (its_noisy)
fprintf(gOutFile, "converting it to %s type\n", JS_GetTypeName(cx, type));
return JS_TRUE;
}
static void
its_finalize(JSContext *cx, JSObject *obj)
{
jsval *rootedVal;
if (its_noisy)
fprintf(gOutFile, "finalizing it\n");
rootedVal = (jsval *) JS_GetPrivate(cx, obj);
if (rootedVal) {
JS_RemoveValueRoot(cx, rootedVal);
JS_SetPrivate(cx, obj, NULL);
delete rootedVal;
}
}
static JSClass its_class = {
"It", JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE | JSCLASS_HAS_PRIVATE,
its_addProperty, its_delProperty, its_getProperty, its_setProperty,
(JSEnumerateOp)its_enumerate, (JSResolveOp)its_resolve,
its_convert, its_finalize,
JSCLASS_NO_OPTIONAL_MEMBERS
};
JSErrorFormatString jsShell_ErrorFormatString[JSErr_Limit] = {
#define MSG_DEF(name, number, count, exception, format) \
{ format, count, JSEXN_ERR } ,
#include "jsshell.msg"
#undef MSG_DEF
};
static const JSErrorFormatString *
my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber)
{
if ((errorNumber > 0) && (errorNumber < JSShellErr_Limit))
return &jsShell_ErrorFormatString[errorNumber];
return NULL;
}
static void
my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
int i, j, k, n;
char *prefix, *tmp;
const char *ctmp;
if (!report) {
fprintf(gErrFile, "%s\n", message);
return;
}
/* Conditionally ignore reported warnings. */
if (JSREPORT_IS_WARNING(report->flags) && !reportWarnings)
return;
prefix = NULL;
if (report->filename)
prefix = JS_smprintf("%s:", report->filename);
if (report->lineno) {
tmp = prefix;
prefix = JS_smprintf("%s%u: ", tmp ? tmp : "", report->lineno);
JS_free(cx, tmp);
}
if (JSREPORT_IS_WARNING(report->flags)) {
tmp = prefix;
prefix = JS_smprintf("%s%swarning: ",
tmp ? tmp : "",
JSREPORT_IS_STRICT(report->flags) ? "strict " : "");
JS_free(cx, tmp);
}
/* embedded newlines -- argh! */
while ((ctmp = strchr(message, '\n')) != 0) {
ctmp++;
if (prefix)
fputs(prefix, gErrFile);
fwrite(message, 1, ctmp - message, gErrFile);
message = ctmp;
}
/* If there were no filename or lineno, the prefix might be empty */
if (prefix)
fputs(prefix, gErrFile);
fputs(message, gErrFile);
if (!report->linebuf) {
fputc('\n', gErrFile);
goto out;
}
/* report->linebuf usually ends with a newline. */
n = strlen(report->linebuf);
fprintf(gErrFile, ":\n%s%s%s%s",
prefix,
report->linebuf,
(n > 0 && report->linebuf[n-1] == '\n') ? "" : "\n",
prefix);
n = report->tokenptr - report->linebuf;
for (i = j = 0; i < n; i++) {
if (report->linebuf[i] == '\t') {
for (k = (j + 8) & ~7; j < k; j++) {
fputc('.', gErrFile);
}
continue;
}
fputc('.', gErrFile);
j++;
}
fputs("^\n", gErrFile);
out:
if (!JSREPORT_IS_WARNING(report->flags)) {
if (report->errorNumber == JSMSG_OUT_OF_MEMORY) {
gExitCode = EXITCODE_OUT_OF_MEMORY;
} else {
gExitCode = EXITCODE_RUNTIME_ERROR;
}
}
JS_free(cx, prefix);
}
#if defined(SHELL_HACK) && defined(DEBUG) && defined(XP_UNIX)
static JSBool
Exec(JSContext *cx, uintN argc, jsval *vp)
{
JSFunction *fun;
const char *name, **nargv;
uintN i, nargc;
JSString *str;
bool ok;
pid_t pid;
int status;
JS_SET_RVAL(cx, vp, JSVAL_VOID);
fun = JS_ValueToFunction(cx, vp[0]);
if (!fun)
return JS_FALSE;
if (!fun->atom)
return JS_TRUE;
nargc = 1 + argc;
/* nargc + 1 accounts for the terminating NULL. */
nargv = new (char *)[nargc + 1];
if (!nargv)
return JS_FALSE;
memset(nargv, 0, sizeof(nargv[0]) * (nargc + 1));
nargv[0] = name;
jsval *argv = JS_ARGV(cx, vp);
for (i = 0; i < nargc; i++) {
str = (i == 0) ? fun->atom : JS_ValueToString(cx, argv[i-1]);
if (!str) {
ok = false;
goto done;
}
nargv[i] = JS_EncodeString(cx, str);
if (!nargv[i]) {
ok = false;
goto done;
}
}
pid = fork();
switch (pid) {
case -1:
perror("js");
break;
case 0:
(void) execvp(name, (char **)nargv);
perror("js");
exit(127);
default:
while (waitpid(pid, &status, 0) < 0 && errno == EINTR)
continue;
break;
}
ok = true;
done:
for (i = 0; i < nargc; i++)
JS_free(cx, nargv[i]);
delete[] nargv;
return ok;
}
#endif
static JSBool
global_enumerate(JSContext *cx, JSObject *obj)
{
#ifdef LAZY_STANDARD_CLASSES
return JS_EnumerateStandardClasses(cx, obj);
#else
return JS_TRUE;
#endif
}
static JSBool
global_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
JSObject **objp)
{
#ifdef LAZY_STANDARD_CLASSES
JSBool resolved;
if (!ResolveClass(cx, obj, id, &resolved))
return JS_FALSE;
if (resolved) {
*objp = obj;
return JS_TRUE;
}
#endif
#if defined(SHELL_HACK) && defined(DEBUG) && defined(XP_UNIX)
if (!(flags & JSRESOLVE_QUALIFIED)) {
/*
* Do this expensive hack only for unoptimized Unix builds, which are
* not used for benchmarking.
*/
char *path, *comp, *full;
const char *name;
JSBool ok, found;
JSFunction *fun;
if (!JSVAL_IS_STRING(id))
return JS_TRUE;
path = getenv("PATH");
if (!path)
return JS_TRUE;
path = JS_strdup(cx, path);
if (!path)
return JS_FALSE;
JSAutoByteString name(cx, JSVAL_TO_STRING(id));
if (!name)
return JS_FALSE;
ok = JS_TRUE;
for (comp = strtok(path, ":"); comp; comp = strtok(NULL, ":")) {
if (*comp != '\0') {
full = JS_smprintf("%s/%s", comp, name.ptr());
if (!full) {
JS_ReportOutOfMemory(cx);
ok = JS_FALSE;
break;
}
} else {
full = (char *)name;
}
found = (access(full, X_OK) == 0);
if (*comp != '\0')
free(full);
if (found) {
fun = JS_DefineFunction(cx, obj, name, Exec, 0,
JSPROP_ENUMERATE);
ok = (fun != NULL);
if (ok)
*objp = obj;
break;
}
}
JS_free(cx, path);
return ok;
}
#else
return JS_TRUE;
#endif
}
JSClass global_class = {
"global", JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
global_enumerate, (JSResolveOp) global_resolve,
JS_ConvertStub, its_finalize,
JSCLASS_NO_OPTIONAL_MEMBERS
};
static JSBool
env_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
{
/* XXX porting may be easy, but these don't seem to supply setenv by default */
#if !defined XP_OS2 && !defined SOLARIS
int rv;
IdToString idstr(cx, id, JS_TRUE);
if (idstr.threw())
return JS_FALSE;
ToString valstr(cx, *vp, JS_TRUE);
if (valstr.threw())
return JS_FALSE;
#if defined XP_WIN || defined HPUX || defined OSF1 || defined IRIX
{
char *waste = JS_smprintf("%s=%s", idstr.getBytes(), valstr.getBytes());
if (!waste) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
rv = putenv(waste);
#ifdef XP_WIN
/*
* HPUX9 at least still has the bad old non-copying putenv.
*
* Per mail from <s.shanmuganathan@digital.com>, OSF1 also has a putenv
* that will crash if you pass it an auto char array (so it must place
* its argument directly in the char *environ[] array).
*/
JS_smprintf_free(waste);
#endif
}
#else
rv = setenv(idstr.getBytes(), valstr.getBytes(), 1);
#endif
if (rv < 0) {
JS_ReportError(cx, "can't set env variable %s to %s", idstr.getBytes(), valstr.getBytes());
return JS_FALSE;
}
*vp = valstr.getJSVal();
#endif /* !defined XP_OS2 && !defined SOLARIS */
return JS_TRUE;
}
static JSBool
env_enumerate(JSContext *cx, JSObject *obj)
{
static JSBool reflected;
char **evp, *name, *value;
JSString *valstr;
JSBool ok;
if (reflected)
return JS_TRUE;
for (evp = (char **)JS_GetPrivate(cx, obj); (name = *evp) != NULL; evp++) {
value = strchr(name, '=');
if (!value)
continue;
*value++ = '\0';
valstr = JS_NewStringCopyZ(cx, value);
if (!valstr) {
ok = JS_FALSE;
} else {
ok = JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr),
NULL, NULL, JSPROP_ENUMERATE);
}
value[-1] = '=';
if (!ok)
return JS_FALSE;
}
reflected = JS_TRUE;
return JS_TRUE;
}
static JSBool
env_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
JSObject **objp)
{
JSString *valstr;
const char *name, *value;
if (flags & JSRESOLVE_ASSIGNING)
return JS_TRUE;
IdToString idstr(cx, id, JS_TRUE);
if (idstr.threw())
return JS_FALSE;
name = idstr.getBytes();
value = getenv(name);
if (value) {
valstr = JS_NewStringCopyZ(cx, value);
if (!valstr)
return JS_FALSE;
if (!JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr),
NULL, NULL, JSPROP_ENUMERATE)) {
return JS_FALSE;
}
*objp = obj;
}
return JS_TRUE;
}
static JSClass env_class = {
"environment", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, env_setProperty,
env_enumerate, (JSResolveOp) env_resolve,
JS_ConvertStub, NULL,
JSCLASS_NO_OPTIONAL_MEMBERS
};
/*
* Avoid a reentrancy hazard.
*
* The non-JS_THREADSAFE shell uses a signal handler to implement timeout().
* The JS engine is not really reentrant, but JS_TriggerAllOperationCallbacks
* is mostly safe--the only danger is that we might interrupt JS_NewContext or
* JS_DestroyContext while the context list is being modified. Therefore we
* disable the signal handler around calls to those functions.
*/
#ifdef JS_THREADSAFE
# define WITH_SIGNALS_DISABLED(x) x
#else
# define WITH_SIGNALS_DISABLED(x) \
JS_BEGIN_MACRO \
ScheduleWatchdog(gRuntime, -1); \
x; \
ScheduleWatchdog(gRuntime, gTimeoutInterval); \
JS_END_MACRO
#endif
static JSContext *
NewContext(JSRuntime *rt)
{
JSContext *cx;
WITH_SIGNALS_DISABLED(cx = JS_NewContext(rt, gStackChunkSize));
if (!cx)
return NULL;
JSShellContextData *data = NewContextData();
if (!data) {
DestroyContext(cx, false);
return NULL;
}
JS_SetContextPrivate(cx, data);
JS_SetErrorReporter(cx, my_ErrorReporter);
JS_SetVersion(cx, JSVERSION_LATEST);
SetContextOptions(cx);
if (enableTraceJit)
JS_ToggleOptions(cx, JSOPTION_JIT);
if (enableMethodJit)
JS_ToggleOptions(cx, JSOPTION_METHODJIT);
return cx;
}
static void
DestroyContext(JSContext *cx, bool withGC)
{
JSShellContextData *data = GetContextData(cx);
JS_SetContextPrivate(cx, NULL);
free(data);
WITH_SIGNALS_DISABLED(withGC ? JS_DestroyContext(cx) : JS_DestroyContextNoGC(cx));
}
static JSObject *
NewGlobalObject(JSContext *cx, CompartmentKind compartment)
{
JSObject *glob = (compartment == NEW_COMPARTMENT)
? JS_NewCompartmentAndGlobalObject(cx, &global_class, NULL)
: JS_NewGlobalObject(cx, &global_class);
if (!glob)
return NULL;
{
JSAutoEnterCompartment ac;
if (!ac.enter(cx, glob))
return NULL;
#ifndef LAZY_STANDARD_CLASSES
if (!JS_InitStandardClasses(cx, glob))
return NULL;
#endif
#ifdef JS_HAS_CTYPES
if (!JS_InitCTypesClass(cx, glob))
return NULL;
#endif
if (!JS::RegisterPerfMeasurement(cx, glob))
return NULL;
if (!JS_DefineFunctions(cx, glob, shell_functions) ||
!JS_DefineProfilingFunctions(cx, glob)) {
return NULL;
}
JSObject *it = JS_DefineObject(cx, glob, "it", &its_class, NULL, 0);
if (!it)
return NULL;
if (!JS_DefineProperties(cx, it, its_props))
return NULL;
if (!JS_DefineFunctions(cx, it, its_methods))
return NULL;
if (!JS_DefineProperty(cx, glob, "custom", JSVAL_VOID, its_getter,
its_setter, 0))
return NULL;
if (!JS_DefineProperty(cx, glob, "customRdOnly", JSVAL_VOID, its_getter,
its_setter, JSPROP_READONLY))
return NULL;
}
if (compartment == NEW_COMPARTMENT && !JS_WrapObject(cx, &glob))
return NULL;
return glob;
}
int
Shell(JSContext *cx, int argc, char **argv, char **envp)
{
JSAutoRequest ar(cx);
JSObject *glob = NewGlobalObject(cx, NEW_COMPARTMENT);
if (!glob)
return 1;
JSAutoEnterCompartment ac;
if (!ac.enter(cx, glob))
return 1;
JS_SetGlobalObject(cx, glob);
JSObject *envobj = JS_DefineObject(cx, glob, "environment", &env_class, NULL, 0);
if (!envobj || !JS_SetPrivate(cx, envobj, envp))
return 1;
#ifdef JSDEBUGGER
/*
* XXX A command line option to enable debugging (or not) would be good
*/
jsdc = JSD_DebuggerOnForUser(rt, NULL, NULL);
if (!jsdc)
return 1;
JSD_JSContextInUse(jsdc, cx);
#ifdef JSD_LOWLEVEL_SOURCE
JS_SetSourceHandler(rt, SendSourceToJSDebugger, jsdc);
#endif /* JSD_LOWLEVEL_SOURCE */
#ifdef JSDEBUGGER_JAVA_UI
jsdjc = JSDJ_CreateContext();
if (! jsdjc)
return 1;
JSDJ_SetJSDContext(jsdjc, jsdc);
java_env = JSDJ_CreateJavaVMAndStartDebugger(jsdjc);
/*
* XXX This would be the place to wait for the debugger to start.
* Waiting would be nice in general, but especially when a js file
* is passed on the cmd line.
*/
#endif /* JSDEBUGGER_JAVA_UI */
#ifdef JSDEBUGGER_C_UI
jsdbc = JSDB_InitDebugger(rt, jsdc, 0);
#endif /* JSDEBUGGER_C_UI */
#endif /* JSDEBUGGER */
#ifdef JS_THREADSAFE
class ShellWorkerHooks : public js::workers::WorkerHooks {
public:
JSObject *newGlobalObject(JSContext *cx) {
return NewGlobalObject(cx, NEW_COMPARTMENT);
}
};
ShellWorkerHooks hooks;
if (!JS_AddNamedObjectRoot(cx, &gWorkers, "Workers") ||
(gWorkerThreadPool = js::workers::init(cx, &hooks, glob, &gWorkers)) == NULL) {
return 1;
}
#endif
int result = ProcessArgs(cx, glob, argv, argc);
#ifdef JS_THREADSAFE
js::workers::finish(cx, gWorkerThreadPool);
JS_RemoveObjectRoot(cx, &gWorkers);
if (result == 0)
result = gExitCode;
#endif
#ifdef JSDEBUGGER
if (jsdc) {
#ifdef JSDEBUGGER_C_UI
if (jsdbc)
JSDB_TermDebugger(jsdc);
#endif /* JSDEBUGGER_C_UI */
JSD_DebuggerOff(jsdc);
}
#endif /* JSDEBUGGER */
return result;
}
static void
MaybeOverrideOutFileFromEnv(const char* const envVar,
FILE* defaultOut,
FILE** outFile)
{
const char* outPath = getenv(envVar);
if (!outPath || !*outPath || !(*outFile = fopen(outPath, "w"))) {
*outFile = defaultOut;
}
}
int
main(int argc, char **argv, char **envp)
{
#ifdef DEBUG
/* Check the allocation count first, or else we'll miss allocations. */
for (int i = 0; i < argc; i++)
{
if (strlen(argv[i]) == 2 && argv[i][0] == '-' && argv[i][1] == 'A')
{
if (++i == argc)
return usage();
OOM_maxAllocations = atoi(argv[i]);
break;
}
}
#endif
int stackDummy;
JSRuntime *rt;
JSContext *cx;
int result;
#ifdef JSDEBUGGER
JSDContext *jsdc;
#ifdef JSDEBUGGER_JAVA_UI
JNIEnv *java_env;
JSDJContext *jsdjc;
#endif
#ifdef JSDEBUGGER_C_UI
JSBool jsdbc;
#endif /* JSDEBUGGER_C_UI */
#endif /* JSDEBUGGER */
#ifdef XP_WIN
{
const char *crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
if (crash_option && strncmp(crash_option, "1", 1)) {
DWORD oldmode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
SetErrorMode(oldmode | SEM_NOGPFAULTERRORBOX);
}
}
#endif
CheckHelpMessages();
#ifdef HAVE_SETLOCALE
setlocale(LC_ALL, "");
#endif
#ifdef JS_THREADSAFE
if (PR_FAILURE == PR_NewThreadPrivateIndex(&gStackBaseThreadIndex, NULL) ||
PR_FAILURE == PR_SetThreadPrivate(gStackBaseThreadIndex, &stackDummy)) {
return 1;
}
#else
gStackBase = (jsuword) &stackDummy;
#endif
#ifdef XP_OS2
/* these streams are normally line buffered on OS/2 and need a \n, *
* so we need to unbuffer then to get a reasonable prompt */
setbuf(stdout,0);
setbuf(stderr,0);
#endif
MaybeOverrideOutFileFromEnv("JS_STDERR", stderr, &gErrFile);
MaybeOverrideOutFileFromEnv("JS_STDOUT", stdout, &gOutFile);
argc--;
argv++;
#ifdef XP_WIN
// Set the timer calibration delay count to 0 so we get high
// resolution right away, which we need for precise benchmarking.
extern int CALIBRATION_DELAY_COUNT;
CALIBRATION_DELAY_COUNT = 0;
#endif
rt = JS_NewRuntime(160L * 1024L * 1024L);
if (!rt)
return 1;
if (!InitWatchdog(rt))
return 1;
cx = NewContext(rt);
if (!cx)
return 1;
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_ANONFUNFIX);
JS_SetGCParameterForThread(cx, JSGC_MAX_CODE_CACHE_BYTES, 16 * 1024 * 1024);
result = Shell(cx, argc, argv, envp);
#ifdef DEBUG
if (OOM_printAllocationCount)
printf("OOM max count: %u\n", OOM_counter);
#endif
DestroyContext(cx, true);
KillWatchdog();
JS_DestroyRuntime(rt);
JS_ShutDown();
return result;
}